trees.vue 12 KB

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