trees.vue 14 KB

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