chore:新加源代码pages_tool/files/detail.vue,文件列表链接正常打开PDF
This commit is contained in:
589
pages_tool/files/detail.vue
Normal file
589
pages_tool/files/detail.vue
Normal file
@@ -0,0 +1,589 @@
|
||||
<template>
|
||||
<view class="files-detail" :style="themeColor">
|
||||
<view class="file-card">
|
||||
<view class="pdf-icon">
|
||||
<text class="pdf-icon-p">P</text>
|
||||
<view class="pdf-icon-fold"></view>
|
||||
<view class="pdf-icon-lines">
|
||||
<view class="pdf-icon-line"></view>
|
||||
<view class="pdf-icon-line"></view>
|
||||
<view class="pdf-icon-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-wrap">
|
||||
<view class="title">{{ displayTitle }}</view>
|
||||
<view v-if="!loaded" class="read-wrap">
|
||||
<text>{{ $lang('files.list.loading') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="fileUrl" class="action-area">
|
||||
<button class="action-btn primary" @tap="openFile">{{ $lang('files.list.viewPdf') }}</button>
|
||||
</view>
|
||||
|
||||
<view v-if="failed" class="error-tip">{{ $lang('files.list.fileNotExist') }}</view>
|
||||
|
||||
<loading-cover ref="loadingCover"></loading-cover>
|
||||
<!-- 隐私弹窗 -->
|
||||
<privacy-popup ref="privacyPopup"></privacy-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 文件详情页 —— 承接 DIY 装修里的文件链接: /pages_tool/files/detail?files_id=43&title=xxx
|
||||
*
|
||||
* 背景: 后台装修数据(如"图标导航/文本导航"的 FILES_xx 链接)把文件指向本页,
|
||||
* 此前页面缺失导致点击后打不开。本页负责:
|
||||
* 1) 按 files_id 取到文件信息(files_url / files_title)
|
||||
* 2) 进入页面即自动按平台打开文件(与 files/list 点击行为一致)
|
||||
* 3) 自动打开失败时,可点击"查看PDF文件"按钮重试
|
||||
*
|
||||
* 注1: 本页文案只使用语言包中已存在的 key(files.list.*),避免新增 key 未同步到服务端
|
||||
* (lang 为 remote-only 模式,运行时取 /api/lang/bundle)。
|
||||
* 注2: Files 控制器没有单文件详情接口,只能从列表接口里按 files_id 取,详见 fetchFile()。
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
filesId: '',
|
||||
filesTitle: '',
|
||||
detail: null,
|
||||
fileUrl: '',
|
||||
loaded: false,
|
||||
failed: false,
|
||||
// 排查用:记录列表接口扫过的文件(files_id:标题)
|
||||
scanned: [],
|
||||
titleTimers: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
displayTitle() {
|
||||
if (this.filesTitle) return this.filesTitle;
|
||||
if (this.detail && (this.detail.files_title || this.detail.title)) {
|
||||
return this.detail.files_title || this.detail.title;
|
||||
}
|
||||
return this.$lang('files.list.title');
|
||||
},
|
||||
themeColor() {
|
||||
if (this.$shop && this.$shop.themeColor) {
|
||||
return `--theme-color: ${this.$shop.themeColor};`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
options = options || {};
|
||||
// page_params 可能是 JSON 字符串 / 对象 / 数组(装修数据里常见 [])
|
||||
const params = this.$util.parsePageParams(options.page_params) || {};
|
||||
const rawId = options.files_id || options.file_id || options.filesId || options.id
|
||||
|| params.files_id || params.file_id || params.id || '';
|
||||
this.filesId = rawId === '' || rawId === undefined || rawId === null ? '' : String(rawId);
|
||||
|
||||
const rawTitle = options.title || params.title || '';
|
||||
if (rawTitle) this.filesTitle = this.decodeTitle(rawTitle);
|
||||
this.scheduleTitleSync();
|
||||
this.getData();
|
||||
},
|
||||
onUnload() {
|
||||
this.clearTitleTimers();
|
||||
},
|
||||
watch: {
|
||||
// main.js 的全局 mixin 监听 componentRefresh,变化时调用 $langConfig.refresh(),
|
||||
// 而 refresh() 会用 $lang('title') 重置导航标题。本页标题就是文件名、且远端语言包里
|
||||
// 没有 files/detail 词条(remote-only 模式),$lang('title') 会退化成字面量 "title"。
|
||||
// refresh() 对每个组件都会跑一遍、"最后一个生效",所以这里延时补写几次兜底。
|
||||
componentRefresh() {
|
||||
if (this.isCurrentPage()) this.scheduleTitleSync();
|
||||
}
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: this.displayTitle,
|
||||
path: `/pages_tool/files/detail?files_id=${this.filesId}`
|
||||
};
|
||||
},
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: this.displayTitle,
|
||||
query: `files_id=${this.filesId}`
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
syncNavigationTitle() {
|
||||
this.$langConfig.title(this.displayTitle);
|
||||
},
|
||||
// 导航标题兜底:立即写一次,再延时补几次(等全局 refresh() 都跑完)
|
||||
scheduleTitleSync() {
|
||||
this.clearTitleTimers();
|
||||
this.syncNavigationTitle();
|
||||
[100, 500, 1500].forEach(delay => {
|
||||
this.titleTimers.push(setTimeout(() => {
|
||||
if (this.isCurrentPage()) this.syncNavigationTitle();
|
||||
}, delay));
|
||||
});
|
||||
},
|
||||
clearTitleTimers() {
|
||||
this.titleTimers.forEach(id => clearTimeout(id));
|
||||
this.titleTimers = [];
|
||||
},
|
||||
// 本页是否在最上层(避免本页在后台时去改别的页面标题)
|
||||
isCurrentPage() {
|
||||
try {
|
||||
const pages = getCurrentPages();
|
||||
const current = pages[pages.length - 1];
|
||||
if (!current) return false;
|
||||
const route = current.route || (current.$page && current.$page.path) || '';
|
||||
return route.indexOf('files/detail') !== -1;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
decodeTitle(value) {
|
||||
const str = String(value || '');
|
||||
// 多数端 uni 已自动 decode 一次,这里只在明显还是百分号编码时才解码,避免二次解码破坏文案
|
||||
if (!str || str.indexOf('%') === -1) return str;
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
},
|
||||
hideLoadingCover() {
|
||||
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
|
||||
},
|
||||
async getData() {
|
||||
if (!this.filesId) {
|
||||
this.finishLoad(null);
|
||||
return;
|
||||
}
|
||||
// 优先走后端单文件详情接口: 按 files_id 直接取, 无需翻列表(文件多时省几十个请求)
|
||||
let detail = await this.fetchGetfile(this.filesId);
|
||||
// 兜底: 列表逐页扫(可能为空/被过滤) / 企业文件接口
|
||||
if (!detail) detail = await this.fetchFile(this.filesId) || await this.fetchFromPersonnel();
|
||||
if (!detail || !this.normalizeUrl(detail.files_url)) {
|
||||
this.finishLoad(null);
|
||||
this.$util.showToast({
|
||||
title: this.$lang('files.list.fileNotExist'),
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.detail = detail;
|
||||
if (!this.filesTitle) this.filesTitle = detail.files_title || detail.title || '';
|
||||
this.syncNavigationTitle();
|
||||
this.fileUrl = this.normalizeUrl(detail.files_url);
|
||||
this.finishLoad(detail);
|
||||
// 进入详情页即自动打开,与点击文件列表项行为一致
|
||||
this.openFile();
|
||||
},
|
||||
finishLoad(detail) {
|
||||
this.loaded = true;
|
||||
this.failed = !detail;
|
||||
this.hideLoadingCover();
|
||||
},
|
||||
/**
|
||||
* 取单个文件信息(优先链路)。
|
||||
* 后端接口: GET /api/files/getfile?files_id=xxx
|
||||
* 内部 getFilesInfo([['files_id','=',$files_id]]) → model('files')->getInfo(),
|
||||
* 返回整条记录(files_title / files_url / files_desc / category_id 等),
|
||||
* files_url 已 img() 补全域名; files 表带 site_id, bootSiteGuard 自动注入当前站点,多租户不串号。
|
||||
* 仅 files_id 一个参数即可,后端无需改造。
|
||||
*/
|
||||
async fetchGetfile(filesId) {
|
||||
const res = await this.request('/api/files/getfile', { files_id: filesId });
|
||||
if (!res || res.code !== 0 || !res.data) return null;
|
||||
const d = res.data;
|
||||
return {
|
||||
files_id: d.files_id || filesId,
|
||||
files_title: d.files_title || d.title || '',
|
||||
files_url: d.files_url || '',
|
||||
files_desc: d.files_desc,
|
||||
category_id: d.category_id,
|
||||
category_name: d.category_name
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 兜底链路(优先链路 fetchGetfile 失败时): 逐页拉列表按 files_id 精确匹配。
|
||||
* 该 addon 之前 Files 控制器只提供列表接口(实测 /api/files/info、/detail、/getinfo 均 404:
|
||||
* "method not exists:app\api\controller\Files->info()"),所以用 getpage 翻页扫。
|
||||
*/
|
||||
async fetchFile(filesId) {
|
||||
// 1) 有标题时先拿标题当关键词搜一次(命中就 1 个请求解决,不必深翻页)
|
||||
if (this.filesTitle) {
|
||||
const searched = await this.queryList({ page: 1, page_size: 100, keyword: this.filesTitle }, filesId, true);
|
||||
if (searched && searched.hit) return searched.hit;
|
||||
}
|
||||
// 2) 逐页遍历全量列表(后端只返回 10 条/页时最多翻 30 页)
|
||||
for (let page = 1; page <= 30; page++) {
|
||||
const queried = await this.queryList({ page: page, page_size: 100 }, filesId);
|
||||
if (queried && queried.hit) return queried.hit;
|
||||
if (!queried || queried.empty) return null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
/**
|
||||
* 兜底链路: 从企业文件接口(/api/member/personnel, 电子名片页"企业文件"同源)取文件。
|
||||
* 该接口返回 res.file_list, 每项形如 { name, url }(可能带 files_id/file_id/id)。
|
||||
* 先按 files_id 精确匹配, 再按标题精确匹配, 最后按标题互相包含模糊匹配。
|
||||
*/
|
||||
async fetchFromPersonnel() {
|
||||
const res = await this.request('/api/member/personnel', {});
|
||||
if (!res || res.code !== 0) return null;
|
||||
const list = res.file_list || (res.data && res.data.file_list) || [];
|
||||
if (!Array.isArray(list)) return null;
|
||||
const title = (this.filesTitle || '').toString();
|
||||
const getId = item => item.files_id || item.file_id || item.id || '';
|
||||
let fuzzyHit = null;
|
||||
for (const item of list) {
|
||||
if (!item) continue;
|
||||
// 1) files_id 精确匹配
|
||||
const id = getId(item);
|
||||
if (id !== '' && String(id) === String(this.filesId)) return this.wrapPersonnelFile(item);
|
||||
// 2) 标题精确匹配
|
||||
const name = (item.name || item.files_title || item.title || '').toString();
|
||||
if (title && name === title) return this.wrapPersonnelFile(item);
|
||||
// 3) 标题互相包含(记下第一个命中, 优先级最低)
|
||||
if (title && !fuzzyHit && (name.indexOf(title) !== -1 || title.indexOf(name) !== -1)) {
|
||||
fuzzyHit = item;
|
||||
}
|
||||
}
|
||||
return fuzzyHit ? this.wrapPersonnelFile(fuzzyHit) : null;
|
||||
},
|
||||
wrapPersonnelFile(item) {
|
||||
return {
|
||||
files_id: item.files_id || item.file_id || item.id || this.filesId,
|
||||
files_title: item.name || item.files_title || item.title || '',
|
||||
files_url: item.url || item.files_url || ''
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 请求一页文件列表,并按 files_id 精确匹配(入参与 files/list 页保持一致)
|
||||
* @param {Boolean} allowTitleMatch 该页是标题搜索结果时,允许退一步用标题全等匹配
|
||||
* @returns {Object|null} 命中返回 { hit };本页无数据返回 { empty: true };请求失败返回 null
|
||||
*/
|
||||
async queryList(data, filesId, allowTitleMatch) {
|
||||
const res = await this.request('/api/files/getpage', Object.assign({
|
||||
category_id: '',
|
||||
keyword: ''
|
||||
}, data));
|
||||
if (!res || res.code !== 0 || !res.data || !Array.isArray(res.data.list)) return null;
|
||||
const list = res.data.list;
|
||||
let titleHit = null;
|
||||
for (const item of list) {
|
||||
const src = item && item.$orig ? item.$orig : item;
|
||||
if (!src) continue;
|
||||
if (String(src.files_id) === String(filesId)) return { hit: src };
|
||||
// 兜底:装修链接里的 files_id 与接口不一致时,用标题全等再认一次
|
||||
if (allowTitleMatch && !titleHit && this.filesTitle && src.files_title === this.filesTitle) {
|
||||
titleHit = src;
|
||||
}
|
||||
}
|
||||
if (titleHit) return { hit: titleHit };
|
||||
return { empty: list.length === 0 };
|
||||
},
|
||||
async request(url, data) {
|
||||
try {
|
||||
return await this.$api.sendRequest({
|
||||
url,
|
||||
data,
|
||||
async: false,
|
||||
noRetry: true,
|
||||
// 失败静默返回 null,由调用方兜底,不弹全局报错
|
||||
silentError: true
|
||||
});
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
normalizeUrl(url) {
|
||||
if (!url) return '';
|
||||
const str = String(url);
|
||||
if (/^https?:\/\//i.test(str)) return str;
|
||||
// $util.img 对含逗号的路径会按"多图字段"截断,文件名可能含逗号,这里规避
|
||||
if (str.indexOf(',') !== -1) return str;
|
||||
// 相对路径补全域名(与图片同一套域名规则)
|
||||
return this.$util.img(str);
|
||||
},
|
||||
getFileExt() {
|
||||
let ext = 'pdf';
|
||||
if (this.fileUrl) {
|
||||
const clean = this.fileUrl.split('?')[0];
|
||||
const matched = clean.match(/\.([a-zA-Z0-9]+)$/);
|
||||
if (matched) ext = matched[1].toLowerCase();
|
||||
}
|
||||
return ext;
|
||||
},
|
||||
getFileName() {
|
||||
const title = (this.displayTitle || 'file').toString().replace(/[\\/:*?"<>|\r\n\t]/g, '_');
|
||||
return `${title}.${this.getFileExt()}`;
|
||||
},
|
||||
getFileType() {
|
||||
const ext = this.getFileExt();
|
||||
return ['pdf'].includes(ext) ? 'pdf'
|
||||
: ['doc', 'docx'].includes(ext) ? 'word'
|
||||
: ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm'].includes(ext) ? 'video'
|
||||
: 'pdf';
|
||||
},
|
||||
buildPreviewUrl() {
|
||||
const fileName = encodeURIComponent(this.getFileName());
|
||||
const fileUrl = encodeURIComponent(this.fileUrl);
|
||||
const fileType = encodeURIComponent(this.getFileType());
|
||||
return `/pages_tool/file-preview/file-preview?fileName=${fileName}&fileUrl=${fileUrl}&fileType=${fileType}`;
|
||||
},
|
||||
openFile() {
|
||||
if (!this.fileUrl) {
|
||||
this.failed = true;
|
||||
this.$util.showToast({
|
||||
title: this.$lang('files.list.fileNotExist'),
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.failed = false;
|
||||
const ext = this.getFileExt();
|
||||
const previewPage = this.buildPreviewUrl();
|
||||
|
||||
// ========== H5 端: 用预览页替换当前页(web-view 走 iframe),失败则新标签页打开 ==========
|
||||
// #ifdef H5
|
||||
uni.redirectTo({
|
||||
url: previewPage,
|
||||
fail: () => {
|
||||
try {
|
||||
window.open(this.fileUrl, '_blank');
|
||||
} catch (e) {
|
||||
this.failed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
// #endif
|
||||
|
||||
// ========== 非 H5 端: 下载文件后按平台分支打开 ==========
|
||||
// #ifndef H5
|
||||
uni.showLoading({ title: this.$lang('files.list.loading'), mask: true });
|
||||
uni.downloadFile({
|
||||
url: this.fileUrl,
|
||||
success: res => {
|
||||
if (res.statusCode !== 200) {
|
||||
uni.hideLoading();
|
||||
this.failed = true;
|
||||
uni.showToast({ title: this.$lang('files.list.fileNotExist'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// ----- 微信小程序: saveFile 重命名 + openDocument(标题取短文件名) -----
|
||||
// #ifdef MP-WEIXIN
|
||||
const fs = uni.getFileSystemManager();
|
||||
const title = (this.displayTitle || 'file').toString().replace(/[\\/:*?"<>|\r\n\t]/g, '_');
|
||||
const targetPath = `${wx.env.USER_DATA_PATH}/fs_${Date.now()}_${title}.${ext}`;
|
||||
const openByPath = (filePath, cleanup) => {
|
||||
uni.openDocument({
|
||||
filePath,
|
||||
fileType: ext,
|
||||
showMenu: true,
|
||||
fail: () => {
|
||||
this.failed = true;
|
||||
uni.showToast({ title: this.$lang('files.list.fileNotExist'), icon: 'none' });
|
||||
},
|
||||
complete: () => {
|
||||
if (cleanup) {
|
||||
try { fs.removeSavedFile({ filePath }); } catch (e) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
try {
|
||||
fs.saveFile({
|
||||
tempFilePath: res.tempFilePath,
|
||||
filePath: targetPath,
|
||||
success: r => {
|
||||
uni.hideLoading();
|
||||
openByPath(r.savedFilePath, true);
|
||||
},
|
||||
fail: () => {
|
||||
// saveFile 失败(配额等),直接用临时文件兜底
|
||||
uni.hideLoading();
|
||||
openByPath(res.tempFilePath, false);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
openByPath(res.tempFilePath, false);
|
||||
}
|
||||
// #endif
|
||||
|
||||
// ----- App / 其他小程序: 直接 openDocument;失败兜底跳转预览页 -----
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.hideLoading();
|
||||
uni.openDocument({
|
||||
filePath: res.tempFilePath,
|
||||
fileType: ext,
|
||||
showMenu: true,
|
||||
fail: () => {
|
||||
uni.navigateTo({
|
||||
url: previewPage,
|
||||
fail: () => {
|
||||
this.failed = true;
|
||||
uni.showToast({ title: this.$lang('files.list.fileNotExist'), icon: 'none' });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
this.failed = true;
|
||||
uni.showToast({ title: this.$lang('files.list.fileNotExist'), icon: 'none' });
|
||||
},
|
||||
complete: res => {
|
||||
if (res && res.statusCode === 404) {
|
||||
uni.hideLoading();
|
||||
this.failed = true;
|
||||
uni.showToast({ title: this.$lang('files.list.fileNotExist'), icon: 'none' });
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.files-detail {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.files-detail .file-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.files-detail .file-card .pdf-icon {
|
||||
margin-right: 24rpx;
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
flex-shrink: 0;
|
||||
background-color: #e64340;
|
||||
border-radius: 10rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(230, 67, 64, 0.2);
|
||||
}
|
||||
|
||||
.files-detail .file-card .pdf-icon .pdf-icon-p {
|
||||
position: absolute;
|
||||
top: 2rpx;
|
||||
left: 10rpx;
|
||||
color: #ffffff;
|
||||
font-size: 48rpx;
|
||||
font-weight: 900;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.files-detail .file-card .pdf-icon .pdf-icon-fold {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
background: linear-gradient(135deg, transparent 50%, #ffeceb 50%);
|
||||
border-bottom-left-radius: 4rpx;
|
||||
}
|
||||
|
||||
.files-detail .file-card .pdf-icon .pdf-icon-lines {
|
||||
position: absolute;
|
||||
right: 8rpx;
|
||||
bottom: 8rpx;
|
||||
width: 42rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.files-detail .file-card .pdf-icon .pdf-icon-line {
|
||||
width: 100%;
|
||||
height: 3rpx;
|
||||
background-color: #ffffff;
|
||||
opacity: 0.85;
|
||||
margin-bottom: 4rpx;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
.files-detail .file-card .pdf-icon .pdf-icon-line:nth-child(2) {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.files-detail .file-card .pdf-icon .pdf-icon-line:nth-child(3) {
|
||||
width: 60%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.files-detail .file-card .info-wrap {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.files-detail .file-card .info-wrap .title {
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.4;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.files-detail .file-card .info-wrap .read-wrap {
|
||||
display: flex;
|
||||
color: #999ca7;
|
||||
align-items: center;
|
||||
margin-top: 12rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.files-detail .file-card .info-wrap .read-wrap text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.files-detail .action-area {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.files-detail .action-area .action-btn {
|
||||
height: 84rpx;
|
||||
line-height: 84rpx;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 30rpx;
|
||||
border-radius: 42rpx;
|
||||
background: linear-gradient(90deg, #ff7e47 0%, #f73131 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4rpx 12rpx rgba(247, 49, 49, 0.25);
|
||||
}
|
||||
|
||||
.files-detail .action-area .action-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.files-detail .error-tip {
|
||||
margin-top: 30rpx;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user