Files
2025-04-30 14:04:34 +08:00

701 lines
16 KiB
Vue

<template>
<view :class="'interlayer ' + (c || '')" :style="s">
<block v-for="(n, i) in nodes" v-bind:key="i">
<!--图片-->
<view
v-if="n.name == 'img'"
:class="'_img ' + n.attrs.class"
:style="n.attrs.style"
:data-attrs="n.attrs"
@tap.stop="imgtap"
>
<rich-text
v-if="ctrl[i] != 0"
:nodes="[
{
attrs: {
src:
loading && (ctrl[i] || 0) < 2
? loading
: lazyLoad && !ctrl[i]
? placeholder
: ctrl[i] == 3
? errorImg
: n.attrs.src || '',
alt: n.attrs.alt || '',
width: n.attrs.width || '',
style:
'-webkit-touch-callout:none;max-width:100%;display:block' +
(n.attrs.height ? ';height:' + n.attrs.height : ''),
},
name: 'img',
},
]"
/>
<image
class="_image"
:src="lazyLoad && !ctrl[i] ? placeholder : n.attrs.src"
:lazy-load="lazyLoad"
:show-menu-by-longpress="!n.attrs.ignore"
:data-i="i"
:data-index="n.attrs.i"
data-source="img"
@load="loadImg"
@error="error"
/>
</view>
<!--文本-->
<text v-else-if="n.type == 'text'" decode>{{ n.text }}</text>
<!--#ifndef MP-BAIDU-->
<text v-else-if="n.name == 'br'">\n</text>
<!--#endif-->
<!--视频-->
<view
v-else-if="
((n.lazyLoad && !n.attrs.autoplay) ||
(n.name == 'video' && !loadVideo)) &&
ctrl[i] == undefined
"
:id="n.attrs.id"
:class="'_video ' + (n.attrs.class || '')"
:style="n.attrs.style"
:data-i="i"
@tap.stop="_loadVideo"
/>
<video
v-else-if="n.name == 'video'"
:id="n.attrs.id"
:class="n.attrs.class"
:style="n.attrs.style"
:autoplay="n.attrs.autoplay || ctrl[i] == 0"
:controls="n.attrs.controls"
:loop="n.attrs.loop"
:muted="n.attrs.muted"
:poster="n.attrs.poster"
:src="n.attrs.source[ctrl[i] || 0]"
:unit-id="n.attrs['unit-id']"
:data-id="n.attrs.id"
:data-i="i"
data-source="video"
@error="error"
@play="play"
/>
<!--音频-->
<audio
v-else-if="n.name == 'audio'"
:ref="n.attrs.id"
:class="n.attrs.class"
:style="n.attrs.style"
:author="n.attrs.author"
:autoplay="n.attrs.autoplay"
:controls="n.attrs.controls"
:loop="n.attrs.loop"
:name="n.attrs.name"
:poster="n.attrs.poster"
:src="n.attrs.source[ctrl[i] || 0]"
:data-i="i"
:data-id="n.attrs.id"
data-source="audio"
@error.native="error"
@play.native="play"
/>
<!--链接-->
<view
v-else-if="n.name == 'a'"
:id="n.attrs.id"
:class="'_a ' + (n.attrs.class || '')"
hover-class="_hover"
:style="n.attrs.style"
:data-attrs="n.attrs"
@tap.stop="linkpress"
>
<trees class="_span" c="_span" :nodes="n.children" />
</view>
<!--广告-->
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']" :appid="n.attrs.appid" :apid="n.attrs.apid" :type="n.attrs.type" :adpid="n.attrs.adpid" data-source="ad" @error="error" />-->
<!--列表-->
<view
v-else-if="n.name == 'li'"
:id="n.attrs.id"
:class="n.attrs.class"
:style="(n.attrs.style || '') + ';display:flex;flex-direction:row'"
>
<view v-if="n.type == 'ol'" class="_ol-bef">{{ n.num }}</view>
<view v-else class="_ul-bef">
<view v-if="n.floor % 3 == 0" class="_ul-p1"></view>
<view v-else-if="n.floor % 3 == 2" class="_ul-p2" />
<view v-else class="_ul-p1" style="border-radius: 50%"></view>
</view>
<trees
class="_li"
c="_li"
:nodes="n.children"
:lazyLoad="lazyLoad"
:loading="loading"
/>
</view>
<!--表格-->
<view
v-else-if="n.name == 'table' && n.c && n.flag"
:id="n.attrs.id"
:class="n.attrs.class"
:style="(n.attrs.style || '') + ';display:grid'"
>
<trees
v-for="(cell, n) in n.children"
v-bind:key="n"
:class="cell.attrs.class"
:c="cell.attrs.class"
:style="cell.attrs.style"
:s="cell.attrs.style"
:nodes="cell.children"
/>
</view>
<view
v-else-if="n.name == 'table' && n.c"
:id="n.attrs.id"
:class="n.attrs.class"
:style="(n.attrs.style || '') + ';display:table'"
>
<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')
: '')
"
>
<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')
: '')
"
>
<trees v-if="tr.name == 'td'" :nodes="tr.children" />
<trees
v-else
v-for="(td, q) in tr.children"
v-bind:key="q"
:class="td.attrs.class"
:c="td.attrs.class"
:style="
(td.attrs.style || '') +
(td.name[0] == 't'
? ';display:table-' + (td.name == 'tr' ? 'row' : 'cell')
: '')
"
:s="
(td.attrs.style || '') +
(td.name[0] == 't'
? ';display:table-' + (td.name == 'tr' ? 'row' : 'cell')
: '')
"
:nodes="td.children"
/>
</view>
</view>
</view>
<!--#ifdef APP-PLUS-->
<iframe
v-else-if="n.name == 'iframe'"
:style="n.attrs.style"
:allowfullscreen="n.attrs.allowfullscreen"
:frameborder="n.attrs.frameborder"
:width="n.attrs.width"
:height="n.attrs.height"
:src="n.attrs.src"
/>
<embed
v-else-if="n.name == 'embed'"
:style="n.attrs.style"
:width="n.attrs.width"
:height="n.attrs.height"
:src="n.attrs.src"
/>
<!--#endif-->
<!--富文本-->
<!--#ifdef MP-WEIXIN || MP-QQ || APP-PLUS-->
<rich-text
v-else-if="handler.use(n)"
:id="n.attrs.id"
:class="'_p __' + n.name"
:nodes="[n]"
/>
<!--#endif-->
<!--#ifndef MP-WEIXIN || MP-QQ || APP-PLUS-->
<rich-text
v-else-if="!n.c"
:id="n.attrs.id"
:nodes="[n]"
style="display: inline"
/>
<!--#endif-->
<trees
v-else
:class="
(n.attrs.id || '') + ' _' + n.name + ' ' + (n.attrs.class || '')
"
:c="(n.attrs.id || '') + ' _' + n.name + ' ' + (n.attrs.class || '')"
:style="n.attrs.style"
:s="n.attrs.style"
:nodes="n.children"
:lazyLoad="lazyLoad"
:loading="loading"
/>
</block>
</view>
</template>
<script module="handler" lang="wxs" src="./handler.wxs"></script>
<script>
global.Parser = {};
import trees from "./trees";
import config from "./config.js";
const errorImg = config.errorImg;
export default {
components: {
trees,
},
name: "trees",
data() {
return {
ctrl: [],
placeholder:
'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="225"/>',
errorImg,
loadVideo: typeof plus == "undefined",
// #ifndef MP-ALIPAY
c: "",
s: "",
// #endif
};
},
props: {
nodes: Array,
lazyLoad: Boolean,
loading: String,
// #ifdef MP-ALIPAY
c: String,
s: String,
// #endif
},
mounted() {
for (
this.top = this.$parent;
this.top.$options.name != "jyf-parser";
this.top = this.top.$parent
);
this.init();
},
// #ifdef APP-PLUS
beforeDestroy() {
this.observer && this.observer.disconnect();
},
// #endif
methods: {
init() {
for (var i = this.nodes.length, n; (n = this.nodes[--i]); ) {
if (n.name == "img") {
this.top.imgList.setItem(
n.attrs.i,
n.attrs["original-src"] || n.attrs.src
);
// #ifdef APP-PLUS
if (this.lazyLoad && !this.observer) {
this.observer = uni
.createIntersectionObserver(this)
.relativeToViewport({
top: 500,
bottom: 500,
});
setTimeout(() => {
this.observer.observe("._img", (res) => {
if (res.intersectionRatio) {
for (var j = this.nodes.length; j--; )
if (this.nodes[j].name == "img") this.$set(this.ctrl, j, 1);
this.observer.disconnect();
}
});
}, 0);
}
// #endif
} else if (n.name == "video" || n.name == "audio") {
var ctx;
if (n.name == "video") {
ctx = uni.createVideoContext(
n.attrs.id,
// #ifndef MP-BAIDU
this
// #endif
);
} else if (this.$refs[n.attrs.id]) ctx = this.$refs[n.attrs.id][0];
if (ctx) {
ctx.id = n.attrs.id;
this.top.videoContexts.push(ctx);
}
}
}
// #ifdef APP-PLUS
// APP 上避免 video 错位需要延时渲染
setTimeout(() => {
this.loadVideo = true;
}, 1000);
// #endif
},
play(e) {
var contexts = this.top.videoContexts;
if (contexts.length > 1 && this.top.autopause)
for (var i = contexts.length; i--; )
if (contexts[i].id != e.currentTarget.dataset.id) contexts[i].pause();
},
imgtap(e) {
var attrs = e.currentTarget.dataset.attrs;
if (!attrs.ignore) {
var preview = true,
data = {
id: e.target.id,
src: attrs.src,
ignore: () => (preview = false),
};
global.Parser.onImgtap && global.Parser.onImgtap(data);
this.top.$emit("imgtap", data);
if (preview) {
var urls = this.top.imgList,
current = urls[attrs.i]
? parseInt(attrs.i)
: ((urls = [attrs.src]), 0);
uni.previewImage({
current,
urls,
});
}
}
},
loadImg(e) {
var i = e.currentTarget.dataset.i;
if (this.lazyLoad && !this.ctrl[i]) {
// #ifdef QUICKAPP-WEBVIEW
this.$set(this.ctrl, i, 0);
this.$nextTick(function () {
// #endif
// #ifndef APP-PLUS
this.$set(this.ctrl, i, 1);
// #endif
// #ifdef QUICKAPP-WEBVIEW
});
// #endif
} else if (this.loading && this.ctrl[i] != 2) {
// #ifdef QUICKAPP-WEBVIEW
this.$set(this.ctrl, i, 0);
this.$nextTick(function () {
// #endif
this.$set(this.ctrl, i, 2);
// #ifdef QUICKAPP-WEBVIEW
});
// #endif
}
},
linkpress(e) {
var jump = true,
attrs = e.currentTarget.dataset.attrs;
attrs.ignore = () => (jump = false);
global.Parser.onLinkpress && global.Parser.onLinkpress(attrs);
this.top.$emit("linkpress", attrs);
if (jump) {
// #ifdef MP
if (attrs["app-id"]) {
return uni.navigateToMiniProgram({
appId: attrs["app-id"],
path: attrs.path,
});
}
// #endif
if (attrs.href) {
if (attrs.href[0] == "#") {
if (this.top.useAnchor)
this.top.navigateTo({
id: attrs.href.substring(1),
});
} else if (
attrs.href.indexOf("http") == 0 ||
attrs.href.indexOf("//") == 0
) {
// #ifdef APP-PLUS
plus.runtime.openWeb(attrs.href);
// #endif
// #ifndef APP-PLUS
uni.setClipboardData({
data: attrs.href,
success: () =>
uni.showToast({
title: "链接已复制",
}),
});
// #endif
} else
uni.navigateTo({
url: attrs.href,
fail() {
uni.switchTab({
url: attrs.href,
});
},
});
}
}
},
error(e) {
var target = e.currentTarget,
source = target.dataset.source,
i = target.dataset.i;
if (source == "video" || source == "audio") {
// 加载其他 source
var index = this.ctrl[i] ? this.ctrl[i].i + 1 : 1;
if (index < this.nodes[i].attrs.source.length)
this.$set(this.ctrl, i, index);
if (e.detail.__args__) e.detail = e.detail.__args__[0];
} else if (errorImg && source == "img") {
this.top.imgList.setItem(target.dataset.index, errorImg);
this.$set(this.ctrl, i, 3);
}
this.top &&
this.top.$emit("error", {
source,
target,
errMsg: e.detail.errMsg,
});
},
_loadVideo(e) {
this.$set(this.ctrl, e.target.dataset.i, 0);
},
},
};
</script>
<style>
/* 在这里引入自定义样式 */
/* 链接和图片效果 */
._a {
display: inline;
padding: 1.5px 0 1.5px 0;
color: #366092;
word-break: break-all;
}
._hover {
text-decoration: underline;
opacity: 0.7;
}
._img {
display: inline-block;
max-width: 100%;
overflow: hidden;
}
/* #ifdef MP-WEIXIN */
:host {
display: inline;
}
/* #endif */
/* #ifndef MP-ALIPAY || APP-PLUS */
.interlayer {
display: inherit;
flex-direction: inherit;
flex-wrap: inherit;
align-content: inherit;
align-items: inherit;
justify-content: inherit;
width: 100%;
white-space: inherit;
}
/* #endif */
._b,
._strong {
font-weight: bold;
}
/* #ifndef MP-ALIPAY */
._blockquote,
._div,
._p,
._ol,
._ul,
._li {
display: block;
}
/* #endif */
._code {
font-family: monospace;
}
._del {
text-decoration: line-through;
}
._em,
._i {
font-style: italic;
}
._h1 {
font-size: 2em;
}
._h2 {
font-size: 1.5em;
}
._h3 {
font-size: 1.17em;
}
._h5 {
font-size: 0.83em;
}
._h6 {
font-size: 0.67em;
}
._h1,
._h2,
._h3,
._h4,
._h5,
._h6 {
display: block;
font-weight: bold;
}
._image {
display: block;
width: 100%;
height: 360px;
margin-top: -360px;
opacity: 0;
}
._ins {
text-decoration: underline;
}
._li {
flex: 1;
width: 0;
}
._ol-bef {
width: 36px;
margin-right: 5px;
text-align: right;
}
._ul-bef {
display: block;
margin: 0 12px 0 23px;
line-height: normal;
}
._ol-bef,
._ul-bef {
flex: none;
user-select: none;
}
._ul-p1 {
display: inline-block;
width: 0.3em;
height: 0.3em;
overflow: hidden;
line-height: 0.3em;
}
._ul-p2 {
display: inline-block;
width: 0.23em;
height: 0.23em;
border: 0.05em solid black;
border-radius: 50%;
}
._q::before {
content: '"';
}
._q::after {
content: '"';
}
._sub {
font-size: smaller;
vertical-align: sub;
}
._sup {
font-size: smaller;
vertical-align: super;
}
/* #ifdef MP-ALIPAY || APP-PLUS || QUICKAPP-WEBVIEW */
._abbr,
._b,
._code,
._del,
._em,
._i,
._ins,
._label,
._q,
._span,
._strong,
._sub,
._sup {
display: inline;
}
/* #endif */
/* #ifdef MP-WEIXIN || MP-QQ */
.__bdo,
.__bdi,
.__ruby,
.__rt {
display: inline-block;
}
/* #endif */
._video {
position: relative;
display: inline-block;
width: 300px;
height: 225px;
background-color: black;
}
._video::after {
position: absolute;
top: 50%;
left: 50%;
margin: -15px 0 0 -15px;
content: "";
border-color: transparent transparent transparent white;
border-style: solid;
border-width: 15px 0 15px 30px;
}
</style>