feat: 添加components-diy\diy-webview.vue
This commit is contained in:
499
components-diy/diy-webview.vue
Normal file
499
components-diy/diy-webview.vue
Normal file
@@ -0,0 +1,499 @@
|
|||||||
|
<template>
|
||||||
|
<view data-component-name="diy-webview" class="diy-webview" :style="containerStyle" @tap="handleTap">
|
||||||
|
<template v-if="src">
|
||||||
|
<!-- #ifdef H5 -->
|
||||||
|
<iframe
|
||||||
|
v-if="!lazyPending"
|
||||||
|
class="diy-webview-iframe"
|
||||||
|
:src="src"
|
||||||
|
:scrolling="scrollMode"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
frameborder="0"
|
||||||
|
allow="fullscreen"
|
||||||
|
loading="lazy"
|
||||||
|
@load="handleIframeLoad"
|
||||||
|
@error="handleIframeError"
|
||||||
|
></iframe>
|
||||||
|
<!-- 后台配置的占位图 / 懒加载未进入视口时的展示 -->
|
||||||
|
<image
|
||||||
|
v-if="placeholderUrl && (lazyPending || !loaded)"
|
||||||
|
class="diy-webview-placeholder"
|
||||||
|
:src="placeholderUrl"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
<!-- 加载中遮罩:iframe 触发 load 前展示,避免白屏观感 -->
|
||||||
|
<view v-if="!loaded && !loadError && !lazyPending" class="webview-loading" :style="errorStyle">
|
||||||
|
<view class="webview-loading-spinner"></view>
|
||||||
|
<text class="webview-loading-text">网页加载中...</text>
|
||||||
|
</view>
|
||||||
|
<!-- H5 下 iframe 内部点击不会冒泡到父容器,提供角标按钮保证"点击跳转"可用 -->
|
||||||
|
<view class="webview-open-chip" @tap.stop="mpGo">
|
||||||
|
<text class="iconfont icon-fuzhilianjie"></text>
|
||||||
|
<text class="webview-open-chip-text">{{ mpGoText }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- #ifndef H5 -->
|
||||||
|
<!-- 小程序/APP:内嵌 web-view 会整页覆盖原生层,改为卡片展示 + 点击跳转 -->
|
||||||
|
<view class='webview-mp-go' :style='mpGoStyle' @tap.stop='mpGo'>
|
||||||
|
<text class='iconfont icon-fuzhilianjie'></text>
|
||||||
|
<text class='webview-mp-go-text'>{{ mpGoText }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- 加载失败兜底(如目标站禁止被嵌入/内核过旧加载失败):可点击直接打开 -->
|
||||||
|
<view v-if="loadError" class="webview-error" :style="errorStyle" @tap.stop="mpGo">
|
||||||
|
<text class="iconfont icon-fuzhilianjie"></text>
|
||||||
|
<text class="error-text">{{ errorText }}</text>
|
||||||
|
<text class="error-open">点击此处打开网页</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<view v-else class="webview-empty" :style="emptyStyle">
|
||||||
|
<text class="iconfont icon-fuzhilianjie"></text>
|
||||||
|
<text class="empty-text">请配置网页链接</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 网页/WebView 组件:后台配置 URL 后嵌入展示,H5 使用 iframe,小程序/APP 使用原生 web-view
|
||||||
|
import DiyMinx from './minx.js'
|
||||||
|
export default {
|
||||||
|
name: 'diy-webview',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
src: '',
|
||||||
|
loaded: false,
|
||||||
|
loadError: false,
|
||||||
|
// 后台开启"懒加载"时,iframe 需等容器滚动进入视口后才真正渲染
|
||||||
|
lazyPending: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mixins: [DiyMinx],
|
||||||
|
created() {
|
||||||
|
this.initWebview();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this._mountedFlag = true;
|
||||||
|
// #ifdef H5
|
||||||
|
if (this.lazyPending) this._setupLazyObserver();
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this._observer) {
|
||||||
|
try { this._observer.disconnect(); } catch (e) {}
|
||||||
|
this._observer = null;
|
||||||
|
}
|
||||||
|
this._clearLoadTimer();
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.initWebview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
containerStyle() {
|
||||||
|
let style = '';
|
||||||
|
// 后台高度为 px 值,与项目其他组件一致 ×2 转 rpx(diy-horz-blank 同款换算)
|
||||||
|
const height = Number(this.value?.height) || 300;
|
||||||
|
style += `height:${height * 2}rpx;`;
|
||||||
|
// 组件角度 round 时按后台圆角值渲染(×2 rpx)
|
||||||
|
if (this.value?.componentAngle === 'round') {
|
||||||
|
const topR = Number(this.value?.topAroundRadius) || 0;
|
||||||
|
const bottomR = Number(this.value?.bottomAroundRadius) || 0;
|
||||||
|
style += `border-radius:${topR * 2}rpx ${topR * 2}rpx ${bottomR * 2}rpx ${bottomR * 2}rpx;`;
|
||||||
|
}
|
||||||
|
if (this.value?.bgColor) {
|
||||||
|
style += `background:${this.value.bgColor};`;
|
||||||
|
}
|
||||||
|
if (this.value?.marginTop) {
|
||||||
|
style += `margin-top:${this.value.marginTop}rpx;`;
|
||||||
|
}
|
||||||
|
if (this.value?.marginBottom) {
|
||||||
|
style += `margin-bottom:${this.value.marginBottom}rpx;`;
|
||||||
|
}
|
||||||
|
if (this.value?.padding) {
|
||||||
|
style += `padding:${this.value.padding}rpx;`;
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
emptyStyle() {
|
||||||
|
let style = '';
|
||||||
|
if (this.value?.bgColor) {
|
||||||
|
style += `background:${this.value.bgColor};`;
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
errorStyle() {
|
||||||
|
let style = '';
|
||||||
|
if (this.value?.bgColor) {
|
||||||
|
style += `background:${this.value.bgColor};`;
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
mpGoText() {
|
||||||
|
// 注意:this.$lang(key) 在 key 不存在时返回 key 字符串本身,不能仅靠 this.$lang 是否存在判断。
|
||||||
|
// 取到 key 与传入 key 不同才视为命中语言包,否则用 fallback。
|
||||||
|
const t = this.$lang && this.$lang('components-diy.diy-webview.mpGo');
|
||||||
|
return (t && t !== 'components-diy.diy-webview.mpGo') ? t : '点击查看网页内容';
|
||||||
|
},
|
||||||
|
mpGoStyle() {
|
||||||
|
let style = '';
|
||||||
|
if (this.value && this.value.bgColor) {
|
||||||
|
style += 'background:' + this.value.bgColor + ';';
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
errorText() {
|
||||||
|
const t = this.$lang && this.$lang('components-diy.diy-webview.loadError');
|
||||||
|
return (t && t !== 'components-diy.diy-webview.loadError') ? t : '网页加载失败,请检查链接';
|
||||||
|
},
|
||||||
|
// iframe 滚动条开关(后台"滚动"配置:true 显示 / false 隐藏)
|
||||||
|
scrollMode() {
|
||||||
|
const s = this.value?.scrolling;
|
||||||
|
return (s === false || s === 'false' || s === 0) ? 'no' : 'yes';
|
||||||
|
},
|
||||||
|
// 占位图(后台 imageUrl,iframe 加载完成前展示)
|
||||||
|
placeholderUrl() {
|
||||||
|
const url = this.value?.imageUrl;
|
||||||
|
return url ? (this.$util.img ? this.$util.img(url) : url) : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initWebview() {
|
||||||
|
const config = this.value || {};
|
||||||
|
this.loadError = false;
|
||||||
|
this.loaded = false;
|
||||||
|
// 调试用:把后台下发的完整 value 对象打印到控制台,方便定位 URL 字段名
|
||||||
|
console.log('[diy-webview] value:', JSON.stringify(config));
|
||||||
|
// 兼容后台可能使用的多种链接字段名/嵌套结构
|
||||||
|
const url = this.extractUrl(config);
|
||||||
|
// 懒加载:开启时等容器进入视口再渲染 iframe
|
||||||
|
this.lazyPending = !!(config.lazyRender || config.lazyLoad);
|
||||||
|
// #ifdef H5
|
||||||
|
// 数据热更新时(后台重新保存)若尚未进入过视口,需重建观察器(仅在挂载后才可建立)
|
||||||
|
if (this.lazyPending && this._mountedFlag) this._setupLazyObserver();
|
||||||
|
// #endif
|
||||||
|
// 优先使用后台直接下发的 url;若配置的是接口来源(source=api),则通过接口拉取
|
||||||
|
if (config.source === 'api' && config.apiUrl) {
|
||||||
|
this.loadUrlFromApi(config.apiUrl);
|
||||||
|
} else if (url) {
|
||||||
|
this.src = this.buildSrc(url);
|
||||||
|
console.log('[diy-webview] src:', this.src);
|
||||||
|
this._startLoadTimer();
|
||||||
|
} else {
|
||||||
|
this.src = '';
|
||||||
|
console.warn('[diy-webview] 未找到有效链接字段');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 在格式化后的 URL 基础上,拼接后台配置的"附加参数"与"携带登录态"(token / member_id)
|
||||||
|
buildSrc(url) {
|
||||||
|
let src = this.formatUrl(url);
|
||||||
|
const config = this.value || {};
|
||||||
|
const extra = [];
|
||||||
|
// 附加参数(后台 urlParams 字段,格式 "a=1&b=2",多组参数用 & 连接)
|
||||||
|
const rawExtra = config.urlParams || config.extraParams || config.appendParams || config.additionalParams || config.params;
|
||||||
|
if (rawExtra && typeof rawExtra === 'string') {
|
||||||
|
extra.push(rawExtra.replace(/^[?&]+/, ''));
|
||||||
|
}
|
||||||
|
// 携带登录态(后台 carryToken 字段:none 不携带 / token / member_id)
|
||||||
|
const loginState = config.carryToken || config.loginState || config.login_state;
|
||||||
|
if (loginState === 'token') {
|
||||||
|
const token = (this.$store && this.$store.state && this.$store.state.token) || '';
|
||||||
|
if (token) extra.push('token=' + encodeURIComponent(token));
|
||||||
|
} else if (loginState === 'member_id') {
|
||||||
|
const memberInfo = (this.$store && this.$store.state && this.$store.state.memberInfo) || {};
|
||||||
|
const memberId = memberInfo.member_id || memberInfo.memberId || '';
|
||||||
|
if (memberId) extra.push('member_id=' + memberId);
|
||||||
|
}
|
||||||
|
if (extra.length) {
|
||||||
|
src += (src.indexOf('?') !== -1 ? '&' : '?') + extra.filter(Boolean).join('&');
|
||||||
|
}
|
||||||
|
return src;
|
||||||
|
},
|
||||||
|
// #ifdef H5
|
||||||
|
// 懒加载:容器滚动进入视口(含预加载缓冲 200px)后才渲染 iframe
|
||||||
|
_setupLazyObserver() {
|
||||||
|
try {
|
||||||
|
if (this._observer) {
|
||||||
|
this._observer.disconnect();
|
||||||
|
this._observer = null;
|
||||||
|
}
|
||||||
|
this._observer = uni.createIntersectionObserver(this, { thresholds: [0] });
|
||||||
|
this._observer.relativeToViewport({ bottom: 200 }).observe('.diy-webview', () => {
|
||||||
|
this.lazyPending = false;
|
||||||
|
if (this._observer) {
|
||||||
|
this._observer.disconnect();
|
||||||
|
this._observer = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// 观察器异常时直接渲染,避免内容永远空白
|
||||||
|
this.lazyPending = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
extractUrl(config) {
|
||||||
|
if (!config || typeof config !== 'object') return '';
|
||||||
|
// 优先读取常见字段名(兼容后台不同命名习惯)
|
||||||
|
let direct = config.url || config.link || config.linkUrl || config.pageUrl || config.webUrl || config.web_url || config.externalUrl || config.targetUrl || config.src || config.href || config.path || config.address;
|
||||||
|
// 兼容装修链接选择器下发的对象形态:link = { wap_url / url / h5_url ... }
|
||||||
|
if (direct && typeof direct === 'object') {
|
||||||
|
direct = direct.wap_url || direct.h5_url || direct.url || direct.app_url || direct.link || '';
|
||||||
|
}
|
||||||
|
if (direct && typeof direct === 'string') return direct.trim();
|
||||||
|
// 兼容嵌套结构:如 value.value.url / value.config.url / value.data.url
|
||||||
|
for (const key of ['value', 'config', 'data', 'content', 'params']) {
|
||||||
|
if (config[key] && typeof config[key] === 'object') {
|
||||||
|
const nested = this.extractUrl(config[key]);
|
||||||
|
if (nested) return nested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
formatUrl(url) {
|
||||||
|
if (!url || typeof url !== 'string') return '';
|
||||||
|
let formatted = url.trim();
|
||||||
|
// 如果后台把 URL 编码存储,做一次解码
|
||||||
|
if (formatted.indexOf('%') !== -1) {
|
||||||
|
try { formatted = decodeURIComponent(formatted); } catch (e) {}
|
||||||
|
}
|
||||||
|
// 支持以 http:// 或 https:// 开头的完整地址
|
||||||
|
if (/^https?:\/\//i.test(formatted)) return formatted;
|
||||||
|
// 支持相对路径或站点内链接,自动补全
|
||||||
|
return this.$util.img ? this.$util.img(formatted) : formatted;
|
||||||
|
},
|
||||||
|
loadUrlFromApi(apiUrl) {
|
||||||
|
this.$api.sendRequest({
|
||||||
|
url: apiUrl,
|
||||||
|
success: res => {
|
||||||
|
if (res.code == 0 && res.data) {
|
||||||
|
const data = typeof res.data === 'string' ? { url: res.data } : res.data;
|
||||||
|
this.src = this.formatUrl(data.url || data.link || '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
this.src = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateNavbarTitle(e) {
|
||||||
|
// 参照 pages-tool/webview.vue:当 web-view 页面标题更新时,可同步设置导航栏标题
|
||||||
|
const title = e?.detail?.title || e?.title || '';
|
||||||
|
if (title) {
|
||||||
|
uni.setNavigationBarTitle({ title });
|
||||||
|
}
|
||||||
|
this.$emit('titleUpdate', e);
|
||||||
|
},
|
||||||
|
// 点击网页容器整体 → 跳转到配置的链接(走统一 diyRedirectTo,外链自动进 web-view / 新窗口)
|
||||||
|
handleTap() {
|
||||||
|
if (this.src) this.mpGo();
|
||||||
|
},
|
||||||
|
mpGo() {
|
||||||
|
if (!this.src) return;
|
||||||
|
if (this.$util && this.$util.diyRedirectTo) {
|
||||||
|
this.$util.diyRedirectTo({
|
||||||
|
wap_url: this.src,
|
||||||
|
title: (this.value && this.value.title) || (this.value && this.value.name) || '网页',
|
||||||
|
name: (this.value && this.value.name) ? this.value.name : '网页'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.navigateTo({ url: '/pages_tool/webview/webview?src=' + encodeURIComponent(this.src) });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 注意:方法名不能叫 onLoad/onError —— uni-app 会把组件里的 onLoad 当页面
|
||||||
|
// 生命周期提升出 methods,导致 @load="onLoad" 渲染时报 handler undefined。
|
||||||
|
handleIframeLoad(e) {
|
||||||
|
this.loaded = true;
|
||||||
|
this.loadError = false;
|
||||||
|
this._clearLoadTimer();
|
||||||
|
this.$emit('load', e);
|
||||||
|
},
|
||||||
|
handleIframeError(e) {
|
||||||
|
this.loadError = true;
|
||||||
|
this.loaded = true;
|
||||||
|
this._clearLoadTimer();
|
||||||
|
console.error('[diy-webview] 加载失败:', this.src, e);
|
||||||
|
this.$emit('error', e);
|
||||||
|
},
|
||||||
|
// 加载超时兜底:部分浏览器内核(如 HBuilderX 内置浏览器的老 CEF 内核)对跨域 iframe
|
||||||
|
// 请求失败时不会触发 error 事件,用定时器兜住,避免容器一直白屏
|
||||||
|
_startLoadTimer() {
|
||||||
|
this._clearLoadTimer();
|
||||||
|
// #ifdef H5
|
||||||
|
this._loadTimer = setTimeout(() => {
|
||||||
|
if (!this.loaded) {
|
||||||
|
this.loadError = true;
|
||||||
|
console.warn('[diy-webview] 加载超时(10s),显示兜底内容:', this.src);
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
_clearLoadTimer() {
|
||||||
|
if (this._loadTimer) {
|
||||||
|
clearTimeout(this._loadTimer);
|
||||||
|
this._loadTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onMessage(e) {
|
||||||
|
// 接收 web-view / iframe 发来的消息,可用于与后台数据交互
|
||||||
|
this.$emit('message', e);
|
||||||
|
// 同时通过事件总线广播,方便页面级监听
|
||||||
|
this.__$emitEvent({
|
||||||
|
eventName: 'diy-webview-message',
|
||||||
|
data: {
|
||||||
|
componentName: this.value?.componentName,
|
||||||
|
moduleIndex: this.value?.moduleIndex,
|
||||||
|
message: e?.detail?.data || e?.data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.diy-webview {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.diy-webview-iframe,
|
||||||
|
.diy-webview-native {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 占位图 / 懒加载未进入视口时的展示 */
|
||||||
|
.diy-webview-placeholder {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* H5 角标跳转按钮:iframe 内部点击不冒泡,用它兜底"点击跳转" */
|
||||||
|
.webview-open-chip {
|
||||||
|
position: absolute;
|
||||||
|
right: 16rpx;
|
||||||
|
bottom: 16rpx;
|
||||||
|
z-index: 3;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size: 24rpx;
|
||||||
|
margin-right: 6rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载中遮罩 */
|
||||||
|
.webview-loading {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f7f8fa;
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-loading-spinner {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border: 4rpx solid #e5e6eb;
|
||||||
|
border-top-color: #576b95;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
animation: webview-spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes webview-spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-empty,
|
||||||
|
.webview-mp-go {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #576B95;
|
||||||
|
font-size: 26rpx;
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size: 48rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-error {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #999;
|
||||||
|
font-size: 26rpx;
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size: 48rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-error {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
background: rgba(255, 255, 255, 0.96);
|
||||||
|
color: #ff6b35;
|
||||||
|
|
||||||
|
.error-open {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #576b95;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user