jyf-parser.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. <template>
  2. <view>
  3. <slot v-if="!nodes.length" />
  4. <!--#ifdef APP-PLUS-NVUE-->
  5. <web-view id="top" ref="web" :style="'margin-top:-2px;height:'+height+'px'" @onPostMessage="_message" />
  6. <!--#endif-->
  7. <!--#ifndef APP-PLUS-NVUE-->
  8. <view id="top" :style="showAm+(selectable?';user-select:text;-webkit-user-select:text':'')">
  9. <!--#ifdef H5 || MP-360-->
  10. <div :id="'rtf'+uid"></div>
  11. <!--#endif-->
  12. <!--#ifndef H5 || MP-360-->
  13. <trees :nodes="nodes" :lazyLoad="lazyLoad" :loading="loadingImg" />
  14. <!--#endif-->
  15. </view>
  16. <!--#endif-->
  17. </view>
  18. </template>
  19. <script>
  20. // #ifndef H5 || APP-PLUS-NVUE || MP-360
  21. import trees from './libs/trees';
  22. var cache = {},
  23. // #ifdef MP-WEIXIN || MP-TOUTIAO
  24. fs = uni.getFileSystemManager ? uni.getFileSystemManager() : null,
  25. // #endif
  26. Parser = require('./libs/MpHtmlParser.js');
  27. var dom;
  28. // 计算 cache 的 key
  29. function hash(str) {
  30. for (var i = str.length, val = 5381; i--;)
  31. val += (val << 5) + str.charCodeAt(i);
  32. return val;
  33. }
  34. // #endif
  35. // #ifdef H5 || APP-PLUS-NVUE || MP-360
  36. var rpx = uni.getSystemInfoSync().windowWidth / 750,
  37. cfg = require('./libs/config.js');
  38. // #endif
  39. // #ifdef APP-PLUS-NVUE
  40. var weexDom = weex.requireModule('dom');
  41. // #endif
  42. /**
  43. * Parser 富文本组件
  44. * @tutorial https://github.com/jin-yufeng/Parser
  45. * @property {String|Array} html 富文本数据
  46. * @property {Boolean} autopause 是否在播放一个视频时自动暂停其他视频
  47. * @property {Boolean} autoscroll 是否自动给所有表格添加一个滚动层
  48. * @property {Boolean} autosetTitle 是否自动将 title 标签中的内容设置到页面标题
  49. * @property {Number} compress 压缩等级
  50. * @property {String} domain 图片、视频等链接的主域名
  51. * @property {Boolean} lazyLoad 是否开启图片懒加载
  52. * @property {String} loadingImg 图片加载完成前的占位图
  53. * @property {Boolean} selectable 是否开启长按复制
  54. * @property {Object} tagStyle 标签的默认样式
  55. * @property {Boolean} showWithAnimation 是否使用渐显动画
  56. * @property {Boolean} useAnchor 是否使用锚点
  57. * @property {Boolean} useCache 是否缓存解析结果
  58. * @event {Function} parse 解析完成事件
  59. * @event {Function} load dom 加载完成事件
  60. * @event {Function} ready 所有图片加载完毕事件
  61. * @event {Function} error 错误事件
  62. * @event {Function} imgtap 图片点击事件
  63. * @event {Function} linkpress 链接点击事件
  64. * @example <jyf-parser :html="html"></jyf-parser>
  65. * @author JinYufeng
  66. * @version 20200528
  67. * @listens MIT
  68. */
  69. export default {
  70. name: 'parser',
  71. data() {
  72. return {
  73. // #ifdef H5 || MP-360
  74. uid: this._uid,
  75. // #endif
  76. // #ifdef APP-PLUS-NVUE
  77. height: 1,
  78. // #endif
  79. // #ifndef APP-PLUS-NVUE
  80. showAm: '',
  81. // #endif
  82. nodes: []
  83. }
  84. },
  85. // #ifndef H5 || APP-PLUS-NVUE || MP-360
  86. components: {
  87. trees
  88. },
  89. // #endif
  90. props: {
  91. html: null,
  92. autopause: {
  93. type: Boolean,
  94. default: true
  95. },
  96. autoscroll: Boolean,
  97. autosetTitle: {
  98. type: Boolean,
  99. default: true
  100. },
  101. // #ifndef H5 || APP-PLUS-NVUE || MP-360
  102. compress: Number,
  103. loadingImg: String,
  104. useCache: Boolean,
  105. // #endif
  106. domain: String,
  107. lazyLoad: Boolean,
  108. selectable: Boolean,
  109. tagStyle: Object,
  110. showWithAnimation: Boolean,
  111. useAnchor: Boolean
  112. },
  113. watch: {
  114. html(html) {
  115. this.setContent(html);
  116. }
  117. },
  118. mounted() {
  119. // 图片数组
  120. this.imgList = [];
  121. this.imgList.each = function(f) {
  122. for (var i = 0, len = this.length; i < len; i++)
  123. this.setItem(i, f(this[i], i, this));
  124. }
  125. this.imgList.setItem = function(i, src) {
  126. if (i == void 0 || !src) return;
  127. // #ifndef MP-ALIPAY || APP-PLUS
  128. // 去重
  129. if (src.indexOf('http') == 0 && this.includes(src)) {
  130. var newSrc = '';
  131. for (var j = 0, c; c = src[j]; j++) {
  132. if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break;
  133. newSrc += Math.random() > 0.5 ? c.toUpperCase() : c;
  134. }
  135. newSrc += src.substr(j);
  136. return this[i] = newSrc;
  137. }
  138. // #endif
  139. this[i] = src;
  140. // 暂存 data src
  141. if (src.includes('data:image')) {
  142. var filePath, info = src.match(/data:image\/(\S+?);(\S+?),(.+)/);
  143. if (!info) return;
  144. // #ifdef MP-WEIXIN || MP-TOUTIAO
  145. filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`;
  146. fs && fs.writeFile({
  147. filePath,
  148. data: info[3],
  149. encoding: info[2],
  150. success: () => this[i] = filePath
  151. })
  152. // #endif
  153. // #ifdef APP-PLUS
  154. filePath = `_doc/parser_tmp/${Date.now()}.${info[1]}`;
  155. var bitmap = new plus.nativeObj.Bitmap();
  156. bitmap.loadBase64Data(src, () => {
  157. bitmap.save(filePath, {}, () => {
  158. bitmap.clear()
  159. this[i] = filePath;
  160. })
  161. })
  162. // #endif
  163. }
  164. }
  165. // #ifdef H5 || MP-360
  166. this.document = document.getElementById('rtf' + this._uid);
  167. // #endif
  168. // #ifndef H5 || APP-PLUS-NVUE || MP-360
  169. if (dom) this.document = new dom(this);
  170. // #endif
  171. // #ifdef APP-PLUS-NVUE
  172. this.document = this.$refs.web;
  173. setTimeout(() => {
  174. // #endif
  175. if (this.html) this.setContent(this.html);
  176. // #ifdef APP-PLUS-NVUE
  177. }, 30)
  178. // #endif
  179. },
  180. beforeDestroy() {
  181. // #ifdef H5 || MP-360
  182. if (this._observer) this._observer.disconnect();
  183. // #endif
  184. this.imgList.each(src => {
  185. // #ifdef APP-PLUS
  186. if (src && src.includes('_doc')) {
  187. plus.io.resolveLocalFileSystemURL(src, entry => {
  188. entry.remove();
  189. });
  190. }
  191. // #endif
  192. // #ifdef MP-WEIXIN || MP-TOUTIAO
  193. if (src && src.includes(uni.env.USER_DATA_PATH))
  194. fs && fs.unlink({
  195. filePath: src
  196. })
  197. // #endif
  198. })
  199. clearInterval(this._timer);
  200. },
  201. methods: {
  202. // #ifdef H5 || APP-PLUS-NVUE || MP-360
  203. _Dom2Str(nodes) {
  204. var str = '';
  205. for (var node of nodes) {
  206. if (node.type == 'text')
  207. str += node.text;
  208. else {
  209. str += ('<' + node.name);
  210. for (var attr in node.attrs || {})
  211. str += (' ' + attr + '="' + node.attrs[attr] + '"');
  212. if (!node.children || !node.children.length) str += '>';
  213. else str += ('>' + this._Dom2Str(node.children) + '</' + node.name + '>');
  214. }
  215. }
  216. return str;
  217. },
  218. _handleHtml(html, append) {
  219. if (typeof html != 'string') html = this._Dom2Str(html.nodes || html);
  220. if (!append) {
  221. // 处理 tag-style 和 userAgentStyles
  222. var style = '<style>@keyframes show{0%{opacity:0}100%{opacity:1}}img{max-width:100%}';
  223. for (var item in cfg.userAgentStyles)
  224. style += `${item}{${cfg.userAgentStyles[item]}}`;
  225. for (item in this.tagStyle)
  226. style += `${item}{${this.tagStyle[item]}}`;
  227. style += '</style>';
  228. html = style + html;
  229. }
  230. // 处理 rpx
  231. if (html.includes('rpx'))
  232. html = html.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * rpx + 'px');
  233. return html;
  234. },
  235. // #endif
  236. setContent(html, append) {
  237. // #ifdef APP-PLUS-NVUE
  238. if (!html)
  239. return this.height = 1;
  240. if (append)
  241. this.$refs.web.evalJs("var b=document.createElement('div');b.innerHTML='" + html.replace(/'/g, "\\'") +
  242. "';document.getElementById('parser').appendChild(b)");
  243. else {
  244. html =
  245. '<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><base href="' +
  246. this.domain + '"><div id="parser"' + (this.selectable ? '>' : ' style="user-select:none">') + this._handleHtml(html).replace(/\n/g, '\\n') +
  247. '</div><script>"use strict";function e(e){if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){var t={data:[e]};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(t):window.__dcloud_weex_.postMessage(JSON.stringify(t))}}' +
  248. (this.showWithAnimation ? 'document.body.style.animation="show .5s",' : '') +
  249. 'setTimeout(function(){e({action:"load",text:document.body.innerText,height:document.getElementById("parser").scrollHeight+16})},50);\x3c/script>';
  250. this.$refs.web.evalJs("document.write('" + html.replace(/'/g, "\\'") + "');document.close()");
  251. }
  252. this.$refs.web.evalJs(
  253. 'var t=document.getElementsByTagName("title");t.length&&e({action:"getTitle",title:t[0].innerText});for(var o,n=document.getElementsByTagName("style"),r=0;o=n[r++];)o.innerHTML=o.innerHTML.replace(/body/g,"#parser");for(var i,a=document.getElementsByTagName("img"),s=[],c=0,l=0;i=a[c];c++)i.onerror=function(){' +
  254. (cfg.errorImg ? 'this.src="' + cfg.errorImg + '",' : '') +
  255. 'e({action:"error",source:"img",target:this})},i.hasAttribute("ignore")||"A"==i.parentElement.nodeName||(i.i=l++,s.push(i.src),i.onclick=function(){e({action:"preview",img:{i:this.i,src:this.src}})});e({action:"getImgList",imgList:s});for(var d,u=document.getElementsByTagName("a"),g=0;d=u[g];g++)d.onclick=function(){var t,o=this.getAttribute("href");if("#"==o[0]){var n=document.getElementById(o.substr(1));n&&(t=n.offsetTop)}return e({action:"linkpress",href:o,offset:t}),!1};for(var m,f=document.getElementsByTagName("video"),h=0;m=f[h];h++)m.style.maxWidth="100%",m.onerror=function(){e({action:"error",source:"video",target:this})}' +
  256. (this.autopause ? ',m.onplay=function(){for(var e,t=0;e=f[t];t++)e!=this&&e.pause()}' : '') +
  257. ';for(var v,y=document.getElementsByTagName("audio"),_=0;v=y[_];_++)v.onerror=function(){e({action:"error",source:"audio",target:this})};' +
  258. (this.autoscroll ? 'for(var p,w=document.getElementsByTagName("table"),T=0;p=w[T];T++){var E=document.createElement("div");E.style.overflow="scroll",p.parentNode.replaceChild(E,p),E.appendChild(p)}' : '') +
  259. '(function(){return new Promise(function(e){var t=document.getElementById("parser"),o=t.scrollHeight,n=setInterval(function(){o==t.scrollHeight?(clearInterval(n),e(o)):o=t.scrollHeight},500)})})().then(function(t){e({action:"ready",height:t+16})})'
  260. )
  261. this.nodes = [1];
  262. // #endif
  263. // #ifdef H5 || MP-360
  264. if (!html) {
  265. if (this.rtf && !append) this.rtf.parentNode.removeChild(this.rtf);
  266. return;
  267. }
  268. var div = document.createElement('div');
  269. if (!append) {
  270. if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
  271. this.rtf = div;
  272. } else {
  273. if (!this.rtf) this.rtf = div;
  274. else this.rtf.appendChild(div);
  275. }
  276. div.innerHTML = this._handleHtml(html, append);
  277. for (var styles = this.rtf.getElementsByTagName('style'), i = 0, style; style = styles[i++];) {
  278. style.innerHTML = style.innerHTML.replace(/body/g, '#rtf' + this._uid);
  279. style.setAttribute('scoped', 'true');
  280. }
  281. // 懒加载
  282. if (!this._observer && this.lazyLoad && IntersectionObserver) {
  283. this._observer = new IntersectionObserver(changes => {
  284. for (let item, i = 0; item = changes[i++];) {
  285. if (item.isIntersecting) {
  286. item.target.src = item.target.getAttribute('data-src');
  287. item.target.removeAttribute('data-src');
  288. this._observer.unobserve(item.target);
  289. }
  290. }
  291. }, {
  292. rootMargin: '500px 0px 500px 0px'
  293. })
  294. }
  295. var _ts = this;
  296. // 获取标题
  297. var title = this.rtf.getElementsByTagName('title');
  298. if (title.length && this.autosetTitle)
  299. uni.setNavigationBarTitle({
  300. title: title[0].innerText
  301. })
  302. // 图片处理
  303. this.imgList.length = 0;
  304. var imgs = this.rtf.getElementsByTagName('img');
  305. for (let i = 0, j = 0, img; img = imgs[i]; i++) {
  306. var src = img.getAttribute('src');
  307. if (this.domain && src) {
  308. if (src[0] == '/') {
  309. if (src[1] == '/')
  310. img.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
  311. else img.src = this.domain + src;
  312. } else if (!src.includes('://')) img.src = this.domain + '/' + src;
  313. }
  314. if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
  315. img.i = j++;
  316. _ts.imgList.push(img.src || img.getAttribute('data-src'));
  317. img.onclick = function() {
  318. var preview = true;
  319. this.ignore = () => preview = false;
  320. _ts.$emit('imgtap', this);
  321. if (preview) {
  322. uni.previewImage({
  323. current: this.i,
  324. urls: _ts.imgList
  325. });
  326. }
  327. }
  328. }
  329. img.onerror = function() {
  330. if (cfg.errorImg)
  331. _ts.imgList[this.i] = this.src = cfg.errorImg;
  332. _ts.$emit('error', {
  333. source: 'img',
  334. target: this
  335. });
  336. }
  337. if (_ts.lazyLoad && this._observer && img.src && img.i != 0) {
  338. img.setAttribute('data-src', img.src);
  339. img.removeAttribute('src');
  340. this._observer.observe(img);
  341. }
  342. }
  343. // 链接处理
  344. var links = this.rtf.getElementsByTagName('a');
  345. for (var link of links) {
  346. link.onclick = function() {
  347. var jump = true,
  348. href = this.getAttribute('href');
  349. _ts.$emit('linkpress', {
  350. href,
  351. ignore: () => jump = false
  352. });
  353. if (jump && href) {
  354. if (href[0] == '#') {
  355. if (_ts.useAnchor) {
  356. _ts.navigateTo({
  357. id: href.substr(1)
  358. })
  359. }
  360. } else if (href.indexOf('http') == 0 || href.indexOf('//') == 0)
  361. return true;
  362. else
  363. uni.navigateTo({
  364. url: href
  365. })
  366. }
  367. return false;
  368. }
  369. }
  370. // 视频处理
  371. var videos = this.rtf.getElementsByTagName('video');
  372. _ts.videoContexts = videos;
  373. for (let video, i = 0; video = videos[i++];) {
  374. video.style.maxWidth = '100%';
  375. video.onerror = function() {
  376. _ts.$emit('error', {
  377. source: 'video',
  378. target: this
  379. });
  380. }
  381. video.onplay = function() {
  382. if (_ts.autopause)
  383. for (let item, i = 0; item = _ts.videoContexts[i++];)
  384. if (item != this) item.pause();
  385. }
  386. }
  387. // 音频处理
  388. var audios = this.rtf.getElementsByTagName('audio');
  389. for (var audio of audios)
  390. audio.onerror = function() {
  391. _ts.$emit('error', {
  392. source: 'audio',
  393. target: this
  394. });
  395. }
  396. // 表格处理
  397. if (this.autoscroll) {
  398. var tables = this.rtf.getElementsByTagName('table');
  399. for (var table of tables) {
  400. let div = document.createElement('div');
  401. div.style.overflow = 'scroll';
  402. table.parentNode.replaceChild(div, table);
  403. div.appendChild(table);
  404. }
  405. }
  406. if (!append) this.document.appendChild(this.rtf);
  407. this.$nextTick(() => {
  408. this.nodes = [1];
  409. this.$emit('load');
  410. });
  411. setTimeout(() => this.showAm = '', 500);
  412. // #endif
  413. // #ifndef APP-PLUS-NVUE
  414. // #ifndef H5 || MP-360
  415. var nodes;
  416. if (!html)
  417. return this.nodes = [];
  418. else if (typeof html == 'string') {
  419. let parser = new Parser(html, this);
  420. // 缓存读取
  421. if (this.useCache) {
  422. var hashVal = hash(html);
  423. if (cache[hashVal])
  424. nodes = cache[hashVal];
  425. else {
  426. nodes = parser.parse();
  427. cache[hashVal] = nodes;
  428. }
  429. } else nodes = parser.parse();
  430. this.$emit('parse', nodes);
  431. } else if (Object.prototype.toString.call(html) == '[object Array]') {
  432. // 非本插件产生的 array 需要进行一些转换
  433. if (html.length && html[0].PoweredBy != 'Parser') {
  434. let parser = new Parser(html, this);
  435. (function f(ns) {
  436. for (var i = 0, n; n = ns[i]; i++) {
  437. if (n.type == 'text') continue;
  438. n.attrs = n.attrs || {};
  439. for (var item in n.attrs)
  440. if (typeof n.attrs[item] != 'string') n.attrs[item] = n.attrs[item].toString();
  441. parser.matchAttr(n, parser);
  442. if (n.children && n.children.length) {
  443. parser.STACK.push(n);
  444. f(n.children);
  445. parser.popNode(parser.STACK.pop());
  446. } else n.children = void 0;
  447. }
  448. })(html);
  449. }
  450. nodes = html;
  451. } else if (typeof html == 'object' && html.nodes) {
  452. nodes = html.nodes;
  453. } else
  454. return console.warn('错误的 html 类型:' + typeof html);
  455. if (append) this.nodes = this.nodes.concat(nodes);
  456. else this.nodes = nodes;
  457. if (nodes.length && nodes[0].title && this.autosetTitle)
  458. uni.setNavigationBarTitle({
  459. title: nodes[0].title
  460. })
  461. if (this.imgList) this.imgList.length = 0;
  462. this.videoContexts = [];
  463. this.$nextTick(() => {
  464. this.$emit('load');
  465. })
  466. // #endif
  467. var height;
  468. clearInterval(this._timer);
  469. this._timer = setInterval(() => {
  470. // #ifdef H5 || MP-360
  471. this.rect = this.rtf.getBoundingClientRect();
  472. // #endif
  473. // #ifndef H5 || MP-360
  474. // #ifdef APP-PLUS
  475. uni.createSelectorQuery().in(this)
  476. // #endif
  477. // #ifndef APP-PLUS
  478. this.createSelectorQuery()
  479. // #endif
  480. .select('#top').boundingClientRect().exec(res => {
  481. if (!res) return;
  482. this.rect = res[0];
  483. // #endif
  484. if (this.rect.height == height) {
  485. this.$emit('ready', this.rect)
  486. clearInterval(this._timer);
  487. }
  488. height = this.rect.height;
  489. // #ifndef H5 || MP-360
  490. });
  491. // #endif
  492. }, 350);
  493. if (this.showWithAnimation && !append) this.showAm = 'animation:show .5s';
  494. // #endif
  495. },
  496. getText(ns = this.nodes) {
  497. var txt = '';
  498. // #ifdef APP-PLUS-NVUE
  499. txt = this._text;
  500. // #endif
  501. // #ifdef H5 || MP-360
  502. txt = this.rtf.innerText;
  503. // #endif
  504. // #ifndef H5 || APP-PLUS-NVUE || MP-360
  505. for (var i = 0, n; n = ns[i++];) {
  506. if (n.type == 'text') txt += n.text.replace(/&nbsp;/g, '\u00A0').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
  507. .replace(/&amp;/g, '&');
  508. else if (n.type == 'br') txt += '\n';
  509. else {
  510. // 块级标签前后加换行
  511. var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
  512. '0' && n.name[1] < '7');
  513. if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
  514. if (n.children) txt += this.getText(n.children);
  515. if (block && txt[txt.length - 1] != '\n') txt += '\n';
  516. else if (n.name == 'td' || n.name == 'th') txt += '\t';
  517. }
  518. }
  519. // #endif
  520. return txt;
  521. },
  522. navigateTo(obj) {
  523. if (!this.useAnchor)
  524. return obj.fail && obj.fail({
  525. errMsg: 'Anchor is disabled'
  526. })
  527. // #ifdef APP-PLUS-NVUE
  528. if (!obj.id)
  529. weexDom.scrollToElement(this.$refs.web);
  530. else
  531. this.$refs.web.evalJs('var pos=document.getElementById("' + obj.id +
  532. '");if(pos)post({action:"linkpress",href:"#",offset:pos.offsetTop+' + (obj.offset || 0) + '})');
  533. obj.success && obj.success({
  534. errMsg: 'pageScrollTo:ok'
  535. });
  536. // #endif
  537. // #ifdef H5 || MP-360
  538. if (!obj.id) {
  539. window.scrollTo(0, this.rtf.offsetTop);
  540. return obj.success && obj.success({
  541. errMsg: 'pageScrollTo:ok'
  542. });
  543. }
  544. var target = document.getElementById(obj.id);
  545. if (!target) return obj.fail && obj.fail({
  546. errMsg: 'Label not found'
  547. });
  548. obj.scrollTop = this.rtf.offsetTop + target.offsetTop + (obj.offset || 0);
  549. uni.pageScrollTo(obj);
  550. // #endif
  551. // #ifndef H5 || APP-PLUS-NVUE || MP-360
  552. var Scroll = (selector, component) => {
  553. uni.createSelectorQuery().in(component ? component : this).select(selector).boundingClientRect().selectViewport()
  554. .scrollOffset()
  555. .exec(res => {
  556. if (!res || !res[0])
  557. return obj.fail && obj.fail({
  558. errMsg: 'Label not found'
  559. });
  560. obj.scrollTop = res[1].scrollTop + res[0].top + (obj.offset || 0);
  561. uni.pageScrollTo(obj);
  562. })
  563. }
  564. if (!obj.id) Scroll('#top');
  565. else {
  566. // #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
  567. Scroll('#top >>> #' + obj.id + ', #top >>> .' + obj.id);
  568. // #endif
  569. // #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
  570. for (var anchor of this.anchors)
  571. if (anchor.id == obj.id)
  572. Scroll('#' + obj.id + ', .' + obj.id, anchor.node);
  573. // #endif
  574. }
  575. // #endif
  576. },
  577. getVideoContext(id) {
  578. // #ifndef APP-PLUS-NVUE
  579. if (!id) return this.videoContexts;
  580. else
  581. for (var i = this.videoContexts.length; i--;)
  582. if (this.videoContexts[i].id == id) return this.videoContexts[i];
  583. // #endif
  584. },
  585. // #ifdef APP-PLUS-NVUE
  586. _message(e) {
  587. // 接收 web-view 消息
  588. var data = e.detail.data[0];
  589. if (data.action == 'load') {
  590. this.$emit('load');
  591. this.height = data.height;
  592. this._text = data.text;
  593. } else if (data.action == 'getTitle') {
  594. if (this.autosetTitle)
  595. uni.setNavigationBarTitle({
  596. title: data.title
  597. })
  598. } else if (data.action == 'getImgList') {
  599. this.imgList.length = 0;
  600. for (var i = data.imgList.length; i--;)
  601. this.imgList.setItem(i, data.imgList[i]);
  602. } else if (data.action == 'preview') {
  603. var preview = true;
  604. data.img.ignore = () => preview = false;
  605. this.$emit('imgtap', data.img);
  606. if (preview)
  607. uni.previewImage({
  608. current: data.img.i,
  609. urls: this.imgList
  610. })
  611. } else if (data.action == 'linkpress') {
  612. var jump = true,
  613. href = data.href;
  614. this.$emit('linkpress', {
  615. href,
  616. ignore: () => jump = false
  617. })
  618. if (jump && href) {
  619. if (href[0] == '#') {
  620. if (this.useAnchor)
  621. weexDom.scrollToElement(this.$refs.web, {
  622. offset: data.offset
  623. })
  624. } else if (href.includes('://'))
  625. plus.runtime.openWeb(href);
  626. else
  627. uni.navigateTo({
  628. url: href
  629. })
  630. }
  631. } else if (data.action == 'error') {
  632. if (data.source == 'img' && cfg.errorImg)
  633. this.imgList.setItem(data.target.i, cfg.errorImg);
  634. this.$emit('error', {
  635. source: data.source,
  636. target: data.target
  637. })
  638. } else if (data.action == 'ready') {
  639. this.height = data.height;
  640. this.$nextTick(() => {
  641. uni.createSelectorQuery().in(this).select('#top').boundingClientRect().exec(res => {
  642. this.rect = res[0];
  643. this.$emit('ready', res[0]);
  644. })
  645. })
  646. }
  647. },
  648. // #endif
  649. }
  650. }
  651. </script>
  652. <style>
  653. @keyframes show {
  654. 0% {
  655. opacity: 0;
  656. }
  657. 100% {
  658. opacity: 1;
  659. }
  660. }
  661. /* #ifdef MP-WEIXIN */
  662. :host {
  663. display: block;
  664. overflow: scroll;
  665. -webkit-overflow-scrolling: touch;
  666. }
  667. /* #endif */
  668. </style>