trees.vue 13 KB

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