trees.vue 13 KB

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