index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <template>
  2. <view>
  3. <!--#ifdef H5-->
  4. <slot v-if="!html"></slot>
  5. <iframe id="contain" :style="'width:100%;'+(selectable?'user-select:text;-webkit-user-select:text':'')+(showWithAnimation?('opacity:0;'+showAnimation):'')"
  6. frameborder="0"></iframe>
  7. <!--#endif-->
  8. <!--#ifndef H5-->
  9. <slot v-if="!(html.nodes||((html&&(html[0].name||html[0].type))?1:nodes.length))"></slot>
  10. <!--#endif-->
  11. <!--#ifdef MP-ALIPAY || H5-->
  12. <view class="contain" :style="(showWithAnimation?'opacity:0;':'')+(selectable?'user-select:text;-webkit-user-select:text':'')"
  13. :animation="showAnimation">
  14. <trees :nodes="html.nodes||((html&&(html[0].name||html[0].type))?html:nodes)" :imgMode="imgMode" />
  15. </view>
  16. <!--#endif-->
  17. <!--#ifndef MP-ALIPAY || H5-->
  18. <trees class="contain" :style="'display:block'+(showWithAnimation?'opacity:0;':'')+(selectable?'user-select:text;-webkit-user-select:text':'')"
  19. :animation="showAnimation" :nodes="html.nodes||((html[0].name||html[0].type)?html:nodes)" :imgMode="imgMode"
  20. :lazyLoad="lazyLoad" :loadVideo="loadVideo" />
  21. <!--#endif-->
  22. </view>
  23. </template>
  24. <script>
  25. import trees from "./trees"
  26. const html2nodes = require("./Parser.js");
  27. // #ifdef MP-WEIXIN || MP-QQ
  28. const CanIUseObserver = require("./api.js").versionHigherThan('1.9.3');
  29. // #endif
  30. // #ifdef APP-PLUS
  31. const CanIUseObserver = true;
  32. // #endif
  33. var Document; // 使用document补丁包时将此句改为 const Document = require('./document.js');
  34. export default {
  35. name: 'parser',
  36. data() {
  37. return {
  38. nodes: [],
  39. showAnimation: {},
  40. // #ifdef APP-PLUS
  41. loadVideo: false,
  42. // #endif
  43. }
  44. },
  45. components: {
  46. trees
  47. },
  48. props: {
  49. 'html': {
  50. type: null,
  51. default: ''
  52. },
  53. 'autocopy': {
  54. type: Boolean,
  55. default: true
  56. },
  57. // #ifndef MP-ALIPAY
  58. 'autopause': {
  59. type: Boolean,
  60. default: true
  61. },
  62. // #endif
  63. 'autopreview': {
  64. type: Boolean,
  65. default: true
  66. },
  67. 'autosetTitle': {
  68. type: Boolean,
  69. default: true
  70. },
  71. 'domain': {
  72. type: String,
  73. default: ''
  74. },
  75. 'imgMode': {
  76. type: String,
  77. default: 'default'
  78. },
  79. // #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
  80. 'lazyLoad': {
  81. type: Boolean,
  82. default: false
  83. },
  84. // #endif
  85. 'selectable': {
  86. type: Boolean,
  87. default: false
  88. },
  89. 'tagStyle': {
  90. type: Object,
  91. default: () => {
  92. return {};
  93. }
  94. },
  95. 'showWithAnimation': {
  96. type: Boolean,
  97. default: false
  98. },
  99. 'animationDuration': {
  100. type: Number,
  101. default: 400
  102. },
  103. 'useAnchor': {
  104. type: Boolean,
  105. default: false
  106. }
  107. },
  108. mounted() {
  109. this.execHtml(this.html);
  110. // #ifndef MP-ALIPAY || H5
  111. this.videoContext = [];
  112. // #endif
  113. // #ifdef MP-BAIDU || MP-ALIPAY
  114. this.anchors = [];
  115. // #endif
  116. },
  117. methods: {
  118. execHtml(html) {
  119. // #ifdef H5
  120. var iframe = document.getElementById("contain");
  121. // 支持 iframe.srcdoc
  122. if (typeof(iframe.srcdoc) == "string") {
  123. var script =
  124. '<script>"use strict";function calcPageHeight(t){var e=Math.max(t.body.clientHeight,t.documentElement.clientHeight),n=Math.max(t.body.scrollHeight,t.documentElement.scrollHeight);return Math.max(e,n)}document.addEventListener("DOMContentLoaded",function(){for(var t=document.getElementsByTagName("img"),e=[],n=0;n<t.length;n++){var r=t[n];r.style+=";max-width:100%",e.push(r.src),r.index=n,"A"!=r.parentElement.nodeName&&(r.onclick=function(){parent.document.previewEvent(this,e)}),r.onerror=function(){parent.document.errorEvent(this,"img")};var o=document.getElementsByTagName("a"),a=!0,i=!1,c=void 0;try{for(var u,l=o[Symbol.iterator]();!(a=(u=l.next()).done);a=!0){u.value.onclick=function(t){if("#"==this.getAttribute("href")[0]){var e=document.getElementById(this.getAttribute("href").substring(1));return parent.document.tapEvent(this,e?e.offsetTop:-1)}return parent.document.tapEvent(this)}}}catch(t){i=!0,c=t}finally{try{!a&&l.return&&l.return()}finally{if(i)throw c}}var d=document.getElementsByTagName("video"),m=!0,h=!1,s=void 0;try{for(var v,y=d[Symbol.iterator]();!(m=(v=y.next()).done);m=!0){var f=v.value;f.style+=";max-width:100%",f.onerror=function(){parent.document.errorEvent(this,"video")},f.onplay=function(){parent.document.playEvent(this)}}}catch(t){h=!0,s=t}finally{try{!m&&y.return&&y.return()}finally{if(h)throw s}}parent.document.setVideoContext(d);var g=document.getElementsByTagName("audios"),p=!0,E=!1,x=void 0;try{for(var b,w=g[Symbol.iterator]();!(p=(b=w.next()).done);p=!0){b.value.onerror=function(t){parent.document.errorEvent(this,"audio")}}}catch(t){E=!0,x=t}finally{try{!p&&w.return&&w.return()}finally{if(E)throw x}}}},!1),window.onload=function(){var t=calcPageHeight(document);parent.document.getElementById("contain").style.height=t+"px",parent.document.setTitle(document.title)};<\/script>';
  125. if (!html) return;
  126. if (typeof html != 'string') {
  127. if (typeof html == 'object') {
  128. var str = "";
  129. for (var node of (html.nodes || html))
  130. str += this.Dom2Str(node);
  131. html = str;
  132. } else {
  133. this.$emit('error', {
  134. source: "parse",
  135. errMsg: "传入的html格式不正确!"
  136. });
  137. return;
  138. }
  139. }
  140. // 处理 rpx
  141. if (/[0-9.]*?rpx/.test(html)) {
  142. var rpx = uni.getSystemInfoSync().screenWidth / 750;
  143. html = html.replace(/([0-9.]*?)rpx/g, function() {
  144. return parseFloat(arguments[1]) * rpx + "px";
  145. })
  146. }
  147. document.previewEvent = (img, imgList) => {
  148. if (!img.hasAttribute('ignore')) {
  149. var preview = true;
  150. img.ignore = () => preview = false;
  151. this.$emit('imgtap', img);
  152. if (preview && this.autopreview) {
  153. uni.previewImage({
  154. current: img.index,
  155. urls: imgList
  156. });
  157. }
  158. }
  159. }
  160. document.tapEvent = (link, offsetTop) => {
  161. var jump = true;
  162. this.$emit('linkpress', {
  163. href: link.getAttribute("href"),
  164. ignore: () => jump = false
  165. });
  166. if (jump && link.getAttribute("href")) {
  167. if (link.getAttribute("href")[0] == '#') {
  168. if (this.useAnchor)
  169. window.scrollTo(0, iframe.offsetTop + offsetTop);
  170. } else if (/^http/.test(link.getAttribute("href"))) {
  171. if (this.autocopy)
  172. window.location.href = link.href;
  173. } else {
  174. uni.navigateTo({
  175. url: link.getAttribute("href")
  176. })
  177. }
  178. }
  179. return false;
  180. }
  181. document.setTitle = (title) => {
  182. if (title && this.autosetTitle) {
  183. uni.setNavigationBarTitle({
  184. title: title
  185. })
  186. }
  187. if (html)
  188. uni.createSelectorQuery().in(this).select("#contain").boundingClientRect(res => {
  189. this.$emit('ready', res);
  190. }).exec()
  191. }
  192. document.errorEvent = (target, source) => {
  193. this.$emit('error', {
  194. source,
  195. target
  196. });
  197. }
  198. document.setVideoContext = (videos) => {
  199. this.videoContext = videos;
  200. }
  201. document.playEvent = (v) => {
  202. if (this.autopause) {
  203. for (var video of this.videoContext) {
  204. if (video != v)
  205. video.pause();
  206. }
  207. }
  208. }
  209. iframe.srcdoc = script + html;
  210. this.showAnimation =
  211. "opacity: 1; transition: opacity 400ms ease 0ms, -webkit-transform 400ms ease 0ms, transform 400ms ease 0ms; transform-origin: 50% 50% 0px;";
  212. return;
  213. }
  214. // #endif
  215. let showAnimation = {};
  216. if (this.showWithAnimation) {
  217. showAnimation = uni.createAnimation({
  218. duration: this.animationDuration,
  219. timingFunction: "ease"
  220. }).opacity(1).step().export();
  221. }
  222. if (!html) {
  223. this.nodes = [];
  224. } else if (typeof html == 'string') {
  225. html2nodes(html, this).then(res => {
  226. // #ifdef APP-PLUS
  227. this.loadVideo = false;
  228. // #endif
  229. this.nodes = res.nodes;
  230. this.showAnimation = showAnimation;
  231. this.imgList = res.imgList;
  232. if (Document) this.document = new Document("nodes", res.nodes, this);
  233. if (res.title && this.autosetTitle) {
  234. uni.setNavigationBarTitle({
  235. title: res.title
  236. })
  237. }
  238. this.$emit('parser', res);
  239. this.ready();
  240. }).catch(err => {
  241. this.$emit('error', {
  242. source: "parse",
  243. errMsg: err
  244. });
  245. })
  246. } else if (html.constructor == Array) {
  247. this.showAnimation = showAnimation;
  248. this.imgList = [];
  249. // #ifdef APP-PLUS
  250. this.loadVideo = false;
  251. // #endif
  252. if (Document) this.document = new Document("html", html, this);
  253. this.ready();
  254. } else if (typeof html == 'object') {
  255. if (!html.nodes || html.nodes.constructor != Array) {
  256. if ((html.name && html.children && html.attrs) || (html.type == "text"))
  257. return;
  258. this.$emit('error', {
  259. source: "parse",
  260. errMsg: "传入的nodes数组格式不正确!应该传入的类型是array,实际传入的类型是:" + typeof html.nodes
  261. });
  262. return;
  263. }
  264. this.showAnimation = showAnimation;
  265. this.imgList = html.imgList || [];
  266. // #ifdef APP-PLUS
  267. this.loadVideo = false;
  268. // #endif
  269. if (Document) this.document = new Document("html.nodes", html.nodes, this);
  270. if (html.title && this.autosetTitle)
  271. uni.setNavigationBarTitle({
  272. title: html.title
  273. })
  274. this.ready();
  275. } else {
  276. this.$emit('error', {
  277. source: "parse",
  278. errMsg: "错误的html类型:" + typeof html
  279. });
  280. }
  281. },
  282. // #ifdef H5
  283. Dom2Str(node) {
  284. if (node.type == "text")
  285. return node.text;
  286. var elem = '<' + node.name;
  287. for (var attr in node.attrs)
  288. elem += (' ' + atts + '="' + node.attrs[attr] + '"');
  289. elem += ">";
  290. for (var child of node.children)
  291. elem += Dom2Str(child);
  292. elem += ("</" + node.name + ">");
  293. return elem;
  294. },
  295. // #endif
  296. // #ifndef H5
  297. getContext(components) {
  298. for (let component of components) {
  299. let observered = false;
  300. if (!component.nodes)
  301. return this.getContext(component.$children);
  302. for (let item of component.nodes) {
  303. // #ifndef MP-ALIPAY
  304. if (item.name == 'img' && !observered) {
  305. observered = true;
  306. if (component.lazyLoad && CanIUseObserver) {
  307. component._observer = uni.createIntersectionObserver(component);
  308. component._observer.relativeToViewport({
  309. top: 1000,
  310. bottom: 1000
  311. }).observe('.img', res => {
  312. component.imgLoad = true;
  313. component._observer.disconnect();
  314. component._observer = null;
  315. })
  316. } else
  317. component.imgLoad = true;
  318. } else if (item.name == 'video') {
  319. this.videoContext.push({
  320. id: item.attrs.id,
  321. context: uni.createVideoContext(item.attrs.id, component)
  322. });
  323. }
  324. // #endif
  325. // #ifdef MP-BAIDU || MP-ALIPAY
  326. if (item.attrs && item.attrs.id) {
  327. this.anchors.push({
  328. id: item.attrs.id,
  329. node: component
  330. })
  331. }
  332. // #endif
  333. }
  334. this.getContext(component.$children);
  335. }
  336. },
  337. // #endif
  338. ready() {
  339. this.$nextTick(() => {
  340. this.navigateTo = (obj) => {
  341. obj.success = obj.success || function() {};
  342. obj.fail = obj.fail || function() {};
  343. var Scroll = (selector,component) => {
  344. const query = uni.createSelectorQuery().in(component?component:this);
  345. query.select(selector).boundingClientRect();
  346. query.selectViewport().scrollOffset();
  347. query.exec(res => {
  348. if (!res || !res[0])
  349. return obj.fail({
  350. errMsg: "Label Not Found"
  351. });
  352. uni.pageScrollTo({
  353. scrollTop: res[1].scrollTop + res[0].top,
  354. success: obj.success,
  355. fail: obj.fail
  356. })
  357. })
  358. }
  359. if (!obj.id) Scroll(".contain");
  360. else {
  361. // #ifndef MP-BAIDU || MP-ALIPAY
  362. Scroll('.contain >>> #' + obj.id);
  363. // #endif
  364. // #ifdef MP-BAIDU || MP-ALIPAY
  365. for (var anchor of this.anchors) {
  366. if (anchor.id == obj.id) {
  367. Scroll("#" + obj.id, anchor.node);
  368. }
  369. }
  370. // #endif
  371. }
  372. }
  373. uni.createSelectorQuery().in(this).select(".contain").boundingClientRect(res => {
  374. this.$emit("ready", res);
  375. }).exec()
  376. // #ifndef H5
  377. this.getContext(this.$children);
  378. // #endif
  379. // #ifdef APP-PLUS
  380. setTimeout(() => {
  381. this.loadVideo = true;
  382. }, 2000);
  383. // #endif
  384. })
  385. }
  386. },
  387. watch: {
  388. html(html) {
  389. this.execHtml(html);
  390. }
  391. }
  392. }
  393. </script>
  394. <style>
  395. /* #ifndef MP-BAIDU */
  396. :host {
  397. display: block;
  398. overflow: scroll;
  399. -webkit-overflow-scrolling: touch;
  400. }
  401. /* #endif */
  402. </style>