trees.vue 13 KB

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