You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

501 lines
13 KiB

  1. <template>
  2. <view :class="'interlayer '+(c||'')" :style="s">
  3. <block v-for="(n, i) in nodes" v-bind:key="i">
  4. <!--图片-->
  5. <view v-if="n.name=='img'" :class="'_img '+n.attrs.class" :style="n.attrs.style" :data-attrs="n.attrs" @tap="imgtap">
  6. <rich-text v-if="controls[i]!=0" :nodes="[{attrs:{src:loading&&(controls[i]||0)<2?loading:(lazyLoad&&!controls[i]?placeholder:(controls[i]==3?errorImg:n.attrs.src||'')),alt:n.attrs.alt||'',width:n.attrs.width||'',style:'-webkit-touch-callout:none;max-width:100%;display:block'+(n.attrs.height?';height:'+n.attrs.height:'')},name:'img'}]" />
  7. <image class="_image" :src="lazyLoad&&!controls[i]?placeholder:n.attrs.src" :lazy-load="lazyLoad"
  8. :show-menu-by-longpress="!n.attrs.ignore" :data-i="i" :data-index="n.attrs.i" data-source="img" @load="loadImg"
  9. @error="error" />
  10. </view>
  11. <!--文本-->
  12. <text v-else-if="n.type=='text'" decode>{{n.text}}</text>
  13. <!--#ifndef MP-BAIDU-->
  14. <text v-else-if="n.name=='br'">\n</text>
  15. <!--#endif-->
  16. <!--视频-->
  17. <view v-else-if="((n.lazyLoad&&!n.attrs.autoplay)||(n.name=='video'&&!loadVideo))&&controls[i]==undefined" :id="n.attrs.id" :class="'_video '+(n.attrs.class||'')"
  18. :style="n.attrs.style" :data-i="i" @tap="_loadVideo" />
  19. <video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||controls[i]==0"
  20. :controls="!n.attrs.autoplay||n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[controls[i]||0]"
  21. :unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" :data-i="i" data-source="video" @error="error" @play="play" />
  22. <!--音频-->
  23. <audio v-else-if="n.name=='audio'" :ref="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author"
  24. :autoplay="n.attrs.autoplay" :controls="!n.attrs.autoplay||n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster"
  25. :src="n.attrs.source[controls[i]||0]" :data-i="i" :data-id="n.attrs.id" data-source="audio"
  26. @error.native="error" @play.native="play" />
  27. <!--链接-->
  28. <view v-else-if="n.name=='a'" :id="n.attrs.id" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style"
  29. :data-attrs="n.attrs" @tap="linkpress">
  30. <trees class="_span" c="_span" :nodes="n.children" />
  31. </view>
  32. <!--广告-->
  33. <!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']" :appid="n.attrs.appid" :apid="n.attrs.apid" :type="n.attrs.type" :adpid="n.attrs.adpid" data-source="ad" @error="error" />-->
  34. <!--列表-->
  35. <view v-else-if="n.name=='li'" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:flex'">
  36. <view v-if="n.type=='ol'" class="_ol-bef">{{n.num}}</view>
  37. <view v-else class="_ul-bef">
  38. <view v-if="n.floor%3==0" class="_ul-p1">█</view>
  39. <view v-else-if="n.floor%3==2" class="_ul-p2" />
  40. <view v-else class="_ul-p1" style="border-radius:50%">█</view>
  41. </view>
  42. <trees class="_li" c="_li" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
  43. </view>
  44. <!--表格-->
  45. <view v-else-if="n.name=='table'&&n.c" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:table'">
  46. <view v-for="(tbody, o) in n.children" v-bind:key="o" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')">
  47. <view v-for="(tr, p) in tbody.children" v-bind:key="p" :class="tr.attrs.class" :style="(tr.attrs.style||'')+(tr.name[0]=='t'?';display:table-'+(tr.name=='tr'?'row':'cell'):'')">
  48. <trees v-if="tr.name=='td'" :nodes="tr.children" />
  49. <trees v-else v-for="(td, q) in tr.children" v-bind:key="q" :class="td.attrs.class" :c="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')"
  50. :s="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')" :nodes="td.children" />
  51. </view>
  52. </view>
  53. </view>
  54. <!--#ifdef APP-PLUS-->
  55. <iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder"
  56. :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
  57. <embed v-else-if="n.name=='embed'" :style="n.attrs.style" :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
  58. <!--#endif-->
  59. <!--富文本-->
  60. <!--#ifdef MP-WEIXIN || MP-QQ || APP-PLUS-->
  61. <rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" />
  62. <!--#endif-->
  63. <!--#ifndef MP-WEIXIN || MP-QQ || APP-PLUS-->
  64. <rich-text v-else-if="!n.c" :id="n.attrs.id" :nodes="[n]" style="display:inline" />
  65. <!--#endif-->
  66. <trees v-else :class="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" :c="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')"
  67. :style="n.attrs.style" :s="n.attrs.style" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
  68. </block>
  69. </view>
  70. </template>
  71. <script module="handler" lang="wxs" src="./handler.wxs"></script>
  72. <script>
  73. global.Parser = {};
  74. import trees from './trees'
  75. const errorImg = require('../libs/config.js').errorImg;
  76. export default {
  77. components: {
  78. trees
  79. },
  80. name: 'trees',
  81. data() {
  82. return {
  83. controls: [],
  84. placeholder: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="225"/>',
  85. errorImg,
  86. loadVideo: typeof plus == 'undefined',
  87. // #ifndef MP-ALIPAY
  88. c: '',
  89. s: ''
  90. // #endif
  91. }
  92. },
  93. props: {
  94. nodes: Array,
  95. lazyLoad: Boolean,
  96. loading: String,
  97. // #ifdef MP-ALIPAY
  98. c: String,
  99. s: String
  100. // #endif
  101. },
  102. mounted() {
  103. for (this.top = this.$parent; this.top.$options.name != 'parser'; this.top = this.top.$parent);
  104. this.init();
  105. },
  106. // #ifdef APP-PLUS
  107. beforeDestroy() {
  108. this.observer && this.observer.disconnect();
  109. },
  110. // #endif
  111. methods: {
  112. init() {
  113. for (var i = this.nodes.length, n; n = this.nodes[--i];) {
  114. if (n.name == 'img') {
  115. this.top.imgList.setItem(n.attrs.i, n.attrs.src);
  116. // #ifdef APP-PLUS
  117. if (this.lazyLoad && !this.observer) {
  118. this.observer = uni.createIntersectionObserver(this).relativeToViewport({
  119. top: 500,
  120. bottom: 500
  121. });
  122. setTimeout(() => {
  123. this.observer.observe('._img', res => {
  124. if (res.intersectionRatio) {
  125. for (var j = this.nodes.length; j--;)
  126. if (this.nodes[j].name == 'img')
  127. this.$set(this.controls, j, 1);
  128. this.observer.disconnect();
  129. }
  130. })
  131. }, 0)
  132. }
  133. // #endif
  134. } else if (n.name == 'video' || n.name == 'audio') {
  135. var ctx;
  136. if (n.name == 'video') {
  137. ctx = uni.createVideoContext(n.attrs.id
  138. // #ifndef MP-BAIDU
  139. , this
  140. // #endif
  141. );
  142. } else if (this.$refs[n.attrs.id])
  143. ctx = this.$refs[n.attrs.id][0];
  144. if (ctx) {
  145. ctx.id = n.attrs.id;
  146. this.top.videoContexts.push(ctx);
  147. }
  148. }
  149. }
  150. // #ifdef APP-PLUS
  151. // APP 上避免 video 错位需要延时渲染
  152. setTimeout(() => {
  153. this.loadVideo = true;
  154. }, 1000)
  155. // #endif
  156. },
  157. play(e) {
  158. var contexts = this.top.videoContexts;
  159. if (contexts.length > 1 && this.top.autopause)
  160. for (var i = contexts.length; i--;)
  161. if (contexts[i].id != e.currentTarget.dataset.id)
  162. contexts[i].pause();
  163. },
  164. imgtap(e) {
  165. var attrs = e.currentTarget.dataset.attrs;
  166. if (!attrs.ignore) {
  167. var preview = true,
  168. data = {
  169. id: e.target.id,
  170. src: attrs.src,
  171. ignore: () => preview = false
  172. };
  173. global.Parser.onImgtap && global.Parser.onImgtap(data);
  174. this.top.$emit('imgtap', data);
  175. if (preview) {
  176. var urls = this.top.imgList,
  177. current = urls[attrs.i] ? parseInt(attrs.i) : (urls = [attrs.src], 0);
  178. uni.previewImage({
  179. current,
  180. urls
  181. })
  182. }
  183. }
  184. },
  185. loadImg(e) {
  186. var i = e.currentTarget.dataset.i;
  187. if (this.lazyLoad && !this.controls[i]) {
  188. // #ifdef QUICKAPP-WEBVIEW
  189. this.$set(this.controls, i, 0);
  190. this.$nextTick(function() {
  191. // #endif
  192. // #ifndef APP-PLUS
  193. this.$set(this.controls, i, 1);
  194. // #endif
  195. // #ifdef QUICKAPP-WEBVIEW
  196. })
  197. // #endif
  198. } else if (this.loading && this.controls[i] != 2) {
  199. // #ifdef QUICKAPP-WEBVIEW
  200. this.$set(this.controls, i, 0);
  201. this.$nextTick(function() {
  202. // #endif
  203. this.$set(this.controls, i, 2);
  204. // #ifdef QUICKAPP-WEBVIEW
  205. })
  206. // #endif
  207. }
  208. },
  209. linkpress(e) {
  210. var jump = true,
  211. attrs = e.currentTarget.dataset.attrs;
  212. attrs.ignore = () => jump = false;
  213. global.Parser.onLinkpress && global.Parser.onLinkpress(attrs);
  214. this.top.$emit('linkpress', attrs);
  215. if (jump) {
  216. // #ifdef MP
  217. if (attrs['app-id']) {
  218. return uni.navigateToMiniProgram({
  219. appId: attrs['app-id'],
  220. path: attrs.path
  221. })
  222. }
  223. // #endif
  224. if (attrs.href) {
  225. if (attrs.href[0] == '#') {
  226. if (this.top.useAnchor)
  227. this.top.navigateTo({
  228. id: attrs.href.substring(1)
  229. })
  230. } else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0) {
  231. // #ifdef APP-PLUS
  232. plus.runtime.openWeb(attrs.href);
  233. // #endif
  234. // #ifndef APP-PLUS
  235. uni.setClipboardData({
  236. data: attrs.href,
  237. success: () =>
  238. uni.showToast({
  239. title: '链接已复制'
  240. })
  241. })
  242. // #endif
  243. } else
  244. uni.navigateTo({
  245. url: attrs.href,
  246. fail() {
  247. uni.switchTab({
  248. url: attrs.href,
  249. })
  250. }
  251. })
  252. }
  253. }
  254. },
  255. error(e) {
  256. var target = e.currentTarget,
  257. source = target.dataset.source,
  258. i = target.dataset.i;
  259. if (source == 'video' || source == 'audio') {
  260. // 加载其他 source
  261. var index = this.controls[i] ? this.controls[i].i + 1 : 1;
  262. if (index < this.nodes[i].attrs.source.length)
  263. this.$set(this.controls, i, index);
  264. if (e.detail.__args__)
  265. e.detail = e.detail.__args__[0];
  266. } else if (errorImg && source == 'img') {
  267. this.top.imgList.setItem(target.dataset.index, errorImg);
  268. this.$set(this.controls, i, 3);
  269. }
  270. this.top && this.top.$emit('error', {
  271. source,
  272. target,
  273. errMsg: e.detail.errMsg
  274. });
  275. },
  276. _loadVideo(e) {
  277. this.$set(this.controls, e.target.dataset.i, 0);
  278. }
  279. }
  280. }
  281. </script>
  282. <style>
  283. /* 在这里引入自定义样式 */
  284. /* 链接和图片效果 */
  285. ._a {
  286. display: inline;
  287. padding: 1.5px 0 1.5px 0;
  288. color: #366092;
  289. word-break: break-all;
  290. }
  291. ._hover {
  292. text-decoration: underline;
  293. opacity: 0.7;
  294. }
  295. ._img {
  296. display: inline-block;
  297. max-width: 100%;
  298. overflow: hidden;
  299. }
  300. /* #ifdef MP-WEIXIN */
  301. :host {
  302. display: inline;
  303. }
  304. /* #endif */
  305. /* #ifndef MP-ALIPAY || APP-PLUS */
  306. .interlayer {
  307. display: inherit;
  308. flex-direction: inherit;
  309. flex-wrap: inherit;
  310. align-content: inherit;
  311. align-items: inherit;
  312. justify-content: inherit;
  313. width: 100%;
  314. white-space: inherit;
  315. }
  316. /* #endif */
  317. ._b,
  318. ._strong {
  319. font-weight: bold;
  320. }
  321. /* #ifndef MP-ALIPAY */
  322. ._blockquote,
  323. ._div,
  324. ._p,
  325. ._ol,
  326. ._ul,
  327. ._li {
  328. display: block;
  329. }
  330. /* #endif */
  331. ._code {
  332. font-family: monospace;
  333. }
  334. ._del {
  335. text-decoration: line-through;
  336. }
  337. ._em,
  338. ._i {
  339. font-style: italic;
  340. }
  341. ._h1 {
  342. font-size: 2em;
  343. }
  344. ._h2 {
  345. font-size: 1.5em;
  346. }
  347. ._h3 {
  348. font-size: 1.17em;
  349. }
  350. ._h5 {
  351. font-size: 0.83em;
  352. }
  353. ._h6 {
  354. font-size: 0.67em;
  355. }
  356. ._h1,
  357. ._h2,
  358. ._h3,
  359. ._h4,
  360. ._h5,
  361. ._h6 {
  362. display: block;
  363. font-weight: bold;
  364. }
  365. ._image {
  366. display: block;
  367. width: 100%;
  368. height: 360px;
  369. margin-top: -360px;
  370. opacity: 0;
  371. }
  372. ._ins {
  373. text-decoration: underline;
  374. }
  375. ._li {
  376. flex: 1;
  377. width: 0;
  378. }
  379. ._ol-bef {
  380. width: 36px;
  381. margin-right: 5px;
  382. text-align: right;
  383. }
  384. ._ul-bef {
  385. margin: 0 12px 0 23px;
  386. line-height: normal;
  387. }
  388. ._ol-bef,
  389. ._ul_bef {
  390. flex: none;
  391. user-select: none;
  392. }
  393. ._ul-p1 {
  394. display: inline-block;
  395. width: 0.3em;
  396. height: 0.3em;
  397. overflow: hidden;
  398. line-height: 0.3em;
  399. }
  400. ._ul-p2 {
  401. display: inline-block;
  402. width: 0.23em;
  403. height: 0.23em;
  404. border: 0.05em solid black;
  405. border-radius: 50%;
  406. }
  407. ._q::before {
  408. content: '"';
  409. }
  410. ._q::after {
  411. content: '"';
  412. }
  413. ._sub {
  414. font-size: smaller;
  415. vertical-align: sub;
  416. }
  417. ._sup {
  418. font-size: smaller;
  419. vertical-align: super;
  420. }
  421. /* #ifdef MP-ALIPAY || APP-PLUS || QUICKAPP-WEBVIEW */
  422. ._abbr,
  423. ._b,
  424. ._code,
  425. ._del,
  426. ._em,
  427. ._i,
  428. ._ins,
  429. ._label,
  430. ._q,
  431. ._span,
  432. ._strong,
  433. ._sub,
  434. ._sup {
  435. display: inline;
  436. }
  437. /* #endif */
  438. /* #ifdef MP-WEIXIN || MP-QQ */
  439. .__bdo,
  440. .__bdi,
  441. .__ruby,
  442. .__rt {
  443. display: inline-block;
  444. }
  445. /* #endif */
  446. ._video {
  447. position: relative;
  448. display: inline-block;
  449. width: 300px;
  450. height: 225px;
  451. background-color: black;
  452. }
  453. ._video::after {
  454. position: absolute;
  455. top: 50%;
  456. left: 50%;
  457. margin: -15px 0 0 -15px;
  458. content: '';
  459. border-color: transparent transparent transparent white;
  460. border-style: solid;
  461. border-width: 15px 0 15px 30px;
  462. }
  463. </style>