|
|
@@ -1,165 +1,184 @@
|
|
|
<template>
|
|
|
<div class="share-page">
|
|
|
<div class="share-container">
|
|
|
- <div class="share-box" :class="{ 'full-width': verified && fileInfo && fileInfo.fileList }">
|
|
|
+ <transition name="fade" mode="out-in">
|
|
|
<!-- 加载中 -->
|
|
|
- <div v-if="loading" class="loading-box">
|
|
|
- <LoadingOutlined :spin="true" :style="{ fontSize: '48px', color: '#1890ff' }" />
|
|
|
- <p style="margin-top: 20px;">正在加载分享文件...</p>
|
|
|
+ <div v-if="loading" class="loading-box" key="loading">
|
|
|
+ <a-spin size="large" tip="正在加载分享..." />
|
|
|
</div>
|
|
|
|
|
|
<!-- 需要密码 -->
|
|
|
- <div v-else-if="needPassword && !verified" class="password-box">
|
|
|
- <div class="share-header">
|
|
|
- <FolderOpenOutlined :style="{ fontSize: '64px', color: '#1890ff' }" />
|
|
|
- <h1>文件分享</h1>
|
|
|
- <p>请输入提取码访问,如无提取码请直接点击确定</p>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="password-input-box">
|
|
|
- <a-input
|
|
|
- v-model:value="password"
|
|
|
- placeholder="请输入提取码"
|
|
|
- size="large"
|
|
|
- :maxlength="4"
|
|
|
- class="password-input"
|
|
|
- @pressEnter="verifyPassword"
|
|
|
- />
|
|
|
- <a-button type="primary" size="large" @click="verifyPassword" class="submit-btn">
|
|
|
- 访问文件
|
|
|
- </a-button>
|
|
|
+ <div v-else-if="needPassword && !verified" class="password-box" key="password">
|
|
|
+ <div class="password-card">
|
|
|
+ <div class="icon-wrapper">
|
|
|
+ <FolderOpenOutlined />
|
|
|
+ </div>
|
|
|
+ <h1 class="title">文件分享</h1>
|
|
|
+ <p class="subtitle">请输入提取码访问,如无提取码请直接点击确定</p>
|
|
|
+
|
|
|
+ <div class="input-group">
|
|
|
+ <a-input
|
|
|
+ v-model:value="password"
|
|
|
+ placeholder="请输入4位提取码"
|
|
|
+ size="large"
|
|
|
+ :maxlength="4"
|
|
|
+ class="code-input"
|
|
|
+ @pressEnter="verifyPassword"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <LockOutlined style="color: #bfbfbf" />
|
|
|
+ </template>
|
|
|
+ </a-input>
|
|
|
+ <a-button type="primary" size="large" @click="verifyPassword" class="submit-btn" :loading="verifying">
|
|
|
+ 访问文件
|
|
|
+ </a-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 文件信息 -->
|
|
|
- <div v-else-if="fileInfo" class="file-info-box">
|
|
|
- <!-- 单文件分享 -->
|
|
|
- <div v-if="!fileInfo.fileList">
|
|
|
- <div class="file-icon">
|
|
|
- <FileOutlined :style="{ fontSize: '80px', color: '#1890ff' }" />
|
|
|
- </div>
|
|
|
- <h2>{{ fileInfo.fileName }}</h2>
|
|
|
- <div class="file-meta">
|
|
|
- <span><FileOutlined /> {{ formatFileSize(fileInfo.fileSize) }}</span>
|
|
|
- <span style="margin-left: 20px;"><ClockCircleOutlined /> {{ parseTime(fileInfo.createTime) }}</span>
|
|
|
+ <div v-else-if="fileInfo" class="content-box" key="content">
|
|
|
+ <!-- 头部信息 -->
|
|
|
+ <div class="content-header">
|
|
|
+ <div class="header-left">
|
|
|
+ <div class="file-icon">
|
|
|
+ <FolderOpenOutlined v-if="fileInfo.fileList" />
|
|
|
+ <FileOutlined v-else />
|
|
|
+ </div>
|
|
|
+ <div class="file-details">
|
|
|
+ <h2 class="file-name" :title="fileInfo.fileName">{{ fileInfo.fileName }}</h2>
|
|
|
+ <div class="file-meta">
|
|
|
+ <span v-if="shareInfo.expireTime" class="meta-tag expire">
|
|
|
+ <ClockCircleOutlined /> {{ parseTime(shareInfo.expireTime) }} 到期
|
|
|
+ </span>
|
|
|
+ <span class="meta-tag size" v-if="!fileInfo.fileList">
|
|
|
+ {{ formatFileSize(fileInfo.fileSize) }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div class="action-buttons">
|
|
|
- <a-button type="primary" @click="downloadFile">
|
|
|
+ <div class="header-right">
|
|
|
+ <a-button v-if="fileInfo.fileList" type="primary" @click="downloadAll">
|
|
|
+ <template #icon><DownloadOutlined /></template>
|
|
|
+ 下载全部
|
|
|
+ </a-button>
|
|
|
+ <a-button v-else type="primary" size="large" @click="downloadFile">
|
|
|
<template #icon><DownloadOutlined /></template>
|
|
|
下载文件
|
|
|
</a-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 文件夹分享 -->
|
|
|
- <div v-else class="folder-share">
|
|
|
- <div class="folder-header">
|
|
|
- <FolderOpenOutlined :style="{ fontSize: '48px', color: '#1890ff' }" />
|
|
|
- <h2>{{ fileInfo.fileName }}</h2>
|
|
|
- <p>共 {{ folderList.length }} 个文件夹,{{ fileInfo.fileList.length }} 个文件</p>
|
|
|
+
|
|
|
+ <!-- 单文件展示区 -->
|
|
|
+ <div v-if="!fileInfo.fileList" class="single-file-preview">
|
|
|
+ <div class="preview-icon">
|
|
|
+ <FileOutlined />
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 工具栏 -->
|
|
|
- <div class="toolbar">
|
|
|
- <div class="toolbar-left">
|
|
|
- <a-breadcrumb separator="/" v-if="breadcrumb.length > 0">
|
|
|
- <a-breadcrumb-item @click="goToFolder(-1)" style="cursor: pointer;">根目录</a-breadcrumb-item>
|
|
|
- <a-breadcrumb-item
|
|
|
- v-for="(item, index) in breadcrumb"
|
|
|
- :key="index"
|
|
|
- @click="goToFolder(index)"
|
|
|
- style="cursor: pointer;">
|
|
|
+ <p class="preview-tip">该文件暂不支持在线预览,请下载后查看</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 文件夹列表区 -->
|
|
|
+ <div v-else class="folder-content">
|
|
|
+ <!-- 面包屑导航 -->
|
|
|
+ <div class="breadcrumb-bar">
|
|
|
+ <a-breadcrumb separator="/">
|
|
|
+ <a-breadcrumb-item>
|
|
|
+ <a @click="goToRoot" :class="{ 'active': breadcrumb.length === 0 }">
|
|
|
+ <HomeOutlined /> 根目录
|
|
|
+ </a>
|
|
|
+ </a-breadcrumb-item>
|
|
|
+ <a-breadcrumb-item v-for="(item, index) in breadcrumb" :key="index">
|
|
|
+ <a @click="goToFolder(index)" :class="{ 'active': index === breadcrumb.length - 1 }">
|
|
|
{{ item.folderName }}
|
|
|
- </a-breadcrumb-item>
|
|
|
- </a-breadcrumb>
|
|
|
- <span v-else style="color: #999;">根目录</span>
|
|
|
- </div>
|
|
|
- <div class="toolbar-right">
|
|
|
- <a-button type="primary" @click="downloadAll">
|
|
|
- <template #icon><DownloadOutlined /></template>
|
|
|
- 下载全部
|
|
|
- </a-button>
|
|
|
- <a-button
|
|
|
- v-if="selectedItems.length > 0"
|
|
|
- @click="downloadSelected">
|
|
|
- <template #icon><DownloadOutlined /></template>
|
|
|
- 下载选中 ({{ selectedItems.length }})
|
|
|
- </a-button>
|
|
|
- </div>
|
|
|
+ </a>
|
|
|
+ </a-breadcrumb-item>
|
|
|
+ </a-breadcrumb>
|
|
|
+ <span class="file-count">共 {{ allItems.length }} 项</span>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 文件列表 -->
|
|
|
- <a-table
|
|
|
- :dataSource="allItems"
|
|
|
- style="width: 100%; margin-top: 20px;"
|
|
|
- :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
|
|
|
- :pagination="false"
|
|
|
- rowKey="id"
|
|
|
- >
|
|
|
- <a-table-column title="名称" dataIndex="name" key="name" :min-width="300">
|
|
|
- <template #default="{ record }">
|
|
|
- <div v-if="record.isFolder" @click="enterFolder(record)" style="cursor: pointer; display: flex; align-items: center;">
|
|
|
- <FolderOutlined style="color: #1890ff; margin-right: 8px;" />
|
|
|
- <span style="color: #1890ff;">{{ record.folderName }}</span>
|
|
|
- </div>
|
|
|
- <div v-else style="display: flex; align-items: center;">
|
|
|
- <FileOutlined style="margin-right: 8px;" />
|
|
|
- <span>{{ record.fileName }}</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </a-table-column>
|
|
|
- <a-table-column title="大小" width="150">
|
|
|
- <template #default="{ record }">
|
|
|
- <span v-if="!record.isFolder">{{ formatFileSize(record.fileSize) }}</span>
|
|
|
- <span v-else style="color: #999;">-</span>
|
|
|
- </template>
|
|
|
- </a-table-column>
|
|
|
- <a-table-column title="上传时间" width="200">
|
|
|
- <template #default="{ record }">
|
|
|
- {{ parseTime(record.createTime) }}
|
|
|
- </template>
|
|
|
- </a-table-column>
|
|
|
- <a-table-column title="操作" width="120">
|
|
|
- <template #default="{ record }">
|
|
|
- <a-button
|
|
|
- type="link"
|
|
|
- @click="record.isFolder ? downloadFolder(record) : downloadFileById(record.fileId)">
|
|
|
- <template #icon><DownloadOutlined /></template>
|
|
|
- 下载
|
|
|
- </a-button>
|
|
|
- </template>
|
|
|
- </a-table-column>
|
|
|
- </a-table>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="share-info">
|
|
|
- <p>
|
|
|
- <WarningOutlined />
|
|
|
- 分享有效期至: {{ parseTime(shareInfo.expireTime) }}
|
|
|
- </p>
|
|
|
+ <!-- 文件列表表格 -->
|
|
|
+ <div class="table-wrapper">
|
|
|
+ <a-table
|
|
|
+ :dataSource="allItems"
|
|
|
+ :pagination="false"
|
|
|
+ :childrenColumnName="null"
|
|
|
+ rowKey="id"
|
|
|
+ :scroll="{ y: 500 }"
|
|
|
+ size="middle"
|
|
|
+ >
|
|
|
+ <a-table-column title="名称" key="name" :width="400">
|
|
|
+ <template #default="{ record }">
|
|
|
+ <div class="name-cell" @click="handleItemClick(record)">
|
|
|
+ <div class="icon">
|
|
|
+ <FolderFilled v-if="record.isFolder" style="color: #ffc107; font-size: 20px;" />
|
|
|
+ <FileTextOutlined v-else style="color: #8c8c8c; font-size: 18px;" />
|
|
|
+ </div>
|
|
|
+ <span class="text">{{ record.isFolder ? record.folderName : record.fileName }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </a-table-column>
|
|
|
+ <a-table-column title="大小" width="120" align="right">
|
|
|
+ <template #default="{ record }">
|
|
|
+ <span class="size-text">{{ record.isFolder ? '-' : formatFileSize(record.fileSize) }}</span>
|
|
|
+ </template>
|
|
|
+ </a-table-column>
|
|
|
+ <a-table-column title="修改时间" width="180" align="right">
|
|
|
+ <template #default="{ record }">
|
|
|
+ <span class="time-text">{{ parseTime(record.createTime) }}</span>
|
|
|
+ </template>
|
|
|
+ </a-table-column>
|
|
|
+ <a-table-column title="操作" width="100" align="center">
|
|
|
+ <template #default="{ record }">
|
|
|
+ <a-tooltip title="下载">
|
|
|
+ <a-button type="text" shape="circle" @click.stop="downloadItem(record)">
|
|
|
+ <DownloadOutlined />
|
|
|
+ </a-button>
|
|
|
+ </a-tooltip>
|
|
|
+ </template>
|
|
|
+ </a-table-column>
|
|
|
+ </a-table>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 错误提示 -->
|
|
|
- <div v-else class="error-box">
|
|
|
- <WarningOutlined :style="{ fontSize: '64px', color: '#f5222d' }" />
|
|
|
- <h2>{{ errorMessage }}</h2>
|
|
|
- <p>{{ errorDetail }}</p>
|
|
|
+ <div v-else class="error-box" key="error">
|
|
|
+ <div class="error-card">
|
|
|
+ <FrownOutlined class="error-icon" />
|
|
|
+ <h2>{{ errorMessage }}</h2>
|
|
|
+ <p>{{ errorDetail }}</p>
|
|
|
+ <a-button type="primary" @click="refreshPage">刷新页面</a-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </transition>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部版权 -->
|
|
|
+ <div class="footer">
|
|
|
+ <p>© {{ new Date().getFullYear() }} Yushu File Share. All rights reserved.</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup name="FileShare">
|
|
|
-import { LoadingOutlined, FolderOpenOutlined, FileOutlined, ClockCircleOutlined, FolderOutlined, WarningOutlined, DownloadOutlined } from '@ant-design/icons-vue'
|
|
|
-import { verifyFileShare, getShareFolderFiles, listFolder } from '@/api/system/file'
|
|
|
+import { ref, computed, onMounted } from 'vue'
|
|
|
+import { useRoute } from 'vue-router'
|
|
|
+import { getCurrentInstance } from 'vue'
|
|
|
+import { Modal } from 'ant-design-vue'
|
|
|
+import {
|
|
|
+ LoadingOutlined, FolderOpenOutlined, FileOutlined, FileTextOutlined,
|
|
|
+ ClockCircleOutlined, WarningOutlined, DownloadOutlined, LockOutlined,
|
|
|
+ HomeOutlined, FolderFilled, FrownOutlined
|
|
|
+} from '@ant-design/icons-vue'
|
|
|
+import { verifyFileShare, getShareFolderFiles } from '@/api/system/file'
|
|
|
import { parseTime } from '@/utils/yushu'
|
|
|
|
|
|
const { proxy } = getCurrentInstance()
|
|
|
const route = useRoute()
|
|
|
|
|
|
+// 状态管理
|
|
|
const loading = ref(true)
|
|
|
+const verifying = ref(false)
|
|
|
const needPassword = ref(false)
|
|
|
const verified = ref(false)
|
|
|
const password = ref('')
|
|
|
@@ -171,29 +190,53 @@ const errorDetail = ref('')
|
|
|
const currentFolderId = ref(null)
|
|
|
const folderList = ref([])
|
|
|
const breadcrumb = ref([])
|
|
|
-const selectedItems = ref([])
|
|
|
-const selectedRowKeys = ref([])
|
|
|
|
|
|
+// 计算属性:合并文件和文件夹
|
|
|
const allItems = computed(() => {
|
|
|
- const folders = folderList.value.map(folder => ({ ...folder, isFolder: true, id: `folder_${folder.folderId}` }))
|
|
|
- const files = fileInfo.value?.fileList?.map(file => ({ ...file, isFolder: false, id: `file_${file.fileId}` })) || []
|
|
|
+ const folders = (folderList.value || []).map(folder => {
|
|
|
+ const item = {
|
|
|
+ ...folder,
|
|
|
+ isFolder: true,
|
|
|
+ id: `folder_${folder.folderId}`
|
|
|
+ }
|
|
|
+ // 删除 children 属性,防止表格显示展开按钮
|
|
|
+ delete item.children
|
|
|
+ return item
|
|
|
+ })
|
|
|
+
|
|
|
+ const files = (fileInfo.value?.fileList || []).map(file => {
|
|
|
+ const item = {
|
|
|
+ ...file,
|
|
|
+ isFolder: false,
|
|
|
+ id: `file_${file.fileId}`
|
|
|
+ }
|
|
|
+ // 删除 children 属性
|
|
|
+ delete item.children
|
|
|
+ return item
|
|
|
+ })
|
|
|
+
|
|
|
return [...folders, ...files]
|
|
|
})
|
|
|
|
|
|
+// 初始化
|
|
|
onMounted(() => {
|
|
|
shareCode.value = route.params.shareCode
|
|
|
if (shareCode.value) {
|
|
|
const code = route.query.code
|
|
|
if (code) password.value = code
|
|
|
- loading.value = false
|
|
|
- needPassword.value = true
|
|
|
+ // 自动尝试验证
|
|
|
+ verifyPassword()
|
|
|
} else {
|
|
|
- showError('分享链接无效', '请检查链接是否正确')
|
|
|
+ showError('链接无效', '请检查您的分享链接是否正确')
|
|
|
}
|
|
|
})
|
|
|
|
|
|
-async function loadShare() {
|
|
|
+// 验证密码并加载数据
|
|
|
+async function verifyPassword() {
|
|
|
+ if (!shareCode.value) return
|
|
|
+ verifying.value = true
|
|
|
loading.value = true
|
|
|
+
|
|
|
try {
|
|
|
const response = await verifyFileShare(shareCode.value, password.value || '')
|
|
|
|
|
|
@@ -201,122 +244,155 @@ async function loadShare() {
|
|
|
shareInfo.value = response.data
|
|
|
|
|
|
if (response.data.resourceType === '0') {
|
|
|
+ // 单文件
|
|
|
fileInfo.value = response.data.fileInfo
|
|
|
} else {
|
|
|
+ // 文件夹
|
|
|
currentFolderId.value = response.data.folderId
|
|
|
fileInfo.value = {
|
|
|
fileName: '文件夹分享',
|
|
|
fileList: response.data.fileList || []
|
|
|
}
|
|
|
- loadFolderList(response.data.folderId)
|
|
|
+ folderList.value = response.data.folderList || []
|
|
|
}
|
|
|
|
|
|
verified.value = true
|
|
|
needPassword.value = false
|
|
|
if (password.value) proxy.$modal.msgSuccess('验证成功')
|
|
|
} else {
|
|
|
- proxy.$modal.msgError(response.msg || '验证失败')
|
|
|
- needPassword.value = true
|
|
|
- verified.value = false
|
|
|
+ // 需要密码或密码错误
|
|
|
+ if (response.msg.includes('提取码')) {
|
|
|
+ needPassword.value = true
|
|
|
+ verified.value = false
|
|
|
+ if (password.value) proxy.$modal.msgError('提取码错误')
|
|
|
+ } else {
|
|
|
+ showError('验证失败', response.msg)
|
|
|
+ }
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- proxy.$modal.msgError('加载失败,请重试')
|
|
|
- needPassword.value = true
|
|
|
- verified.value = false
|
|
|
+ console.error(error)
|
|
|
+ // 如果是401/403等错误,通常意味着需要密码或出错
|
|
|
+ if (!verified.value) {
|
|
|
+ needPassword.value = true
|
|
|
+ } else {
|
|
|
+ showError('加载失败', '请稍后重试')
|
|
|
+ }
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
+ verifying.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function verifyPassword() {
|
|
|
- await loadShare()
|
|
|
-}
|
|
|
-
|
|
|
-function downloadFile() {
|
|
|
- if (!fileInfo.value) return
|
|
|
- const link = document.createElement('a')
|
|
|
- link.href = `${import.meta.env.VITE_APP_BASE_API}/system/file/share/download/${shareCode.value}`
|
|
|
- link.download = fileInfo.value.fileName
|
|
|
- link.click()
|
|
|
- proxy.$modal.msgSuccess('开始下载')
|
|
|
-}
|
|
|
-
|
|
|
-function downloadFileById(fileId) {
|
|
|
- const link = document.createElement('a')
|
|
|
- link.href = `${import.meta.env.VITE_APP_BASE_API}/system/file/share/download/${shareCode.value}/${fileId}`
|
|
|
- link.click()
|
|
|
- proxy.$modal.msgSuccess('开始下载')
|
|
|
-}
|
|
|
-
|
|
|
-async function loadFolderList(folderId) {
|
|
|
- try {
|
|
|
- const response = await listFolder({ parentId: folderId })
|
|
|
- if (response.code === 200) {
|
|
|
- folderList.value = response.rows || []
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('加载文件夹列表失败:', error)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
+// 文件夹下钻
|
|
|
async function enterFolder(folder) {
|
|
|
+ if (!folder || !folder.folderId) return
|
|
|
+
|
|
|
loading.value = true
|
|
|
+
|
|
|
try {
|
|
|
const response = await getShareFolderFiles(shareCode.value, folder.folderId)
|
|
|
+
|
|
|
if (response.code === 200) {
|
|
|
+ const data = response.data || {}
|
|
|
+
|
|
|
currentFolderId.value = folder.folderId
|
|
|
- fileInfo.value.fileList = response.data || []
|
|
|
- loadFolderList(folder.folderId)
|
|
|
+
|
|
|
breadcrumb.value.push({
|
|
|
folderId: folder.folderId,
|
|
|
folderName: folder.folderName
|
|
|
})
|
|
|
+
|
|
|
+ folderList.value = data.folderList || []
|
|
|
+ fileInfo.value.fileList = data.fileList || []
|
|
|
+ } else {
|
|
|
+ proxy.$modal.msgError(response.msg || '加载失败')
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- proxy.$modal.msgError('加载文件夹失败')
|
|
|
+ proxy.$modal.msgError('网络错误,请重试')
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function goToFolder(index) {
|
|
|
- if (index === -1) {
|
|
|
- breadcrumb.value = []
|
|
|
- await loadShare()
|
|
|
- } else {
|
|
|
- const folder = breadcrumb.value[index]
|
|
|
- breadcrumb.value = breadcrumb.value.slice(0, index + 1)
|
|
|
- await enterFolder(folder)
|
|
|
+// 点击项目(文件夹则进入)
|
|
|
+function handleItemClick(record) {
|
|
|
+ if (record.isFolder) {
|
|
|
+ enterFolder(record)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-function handleSelectionChange(keys, rows) {
|
|
|
- selectedRowKeys.value = keys
|
|
|
- selectedItems.value = rows
|
|
|
+// 导航回根目录
|
|
|
+async function goToRoot() {
|
|
|
+ if (breadcrumb.value.length === 0) return
|
|
|
+ breadcrumb.value = []
|
|
|
+ // 重新加载初始数据
|
|
|
+ verifyPassword()
|
|
|
}
|
|
|
|
|
|
-function downloadAll() {
|
|
|
- proxy.$modal.confirm('确定要下载全部内容吗?').then(() => {
|
|
|
- const files = fileInfo.value.fileList || []
|
|
|
- const folders = folderList.value || []
|
|
|
- if (files.length === 0 && folders.length === 0) {
|
|
|
- proxy.$modal.msgWarning('没有可下载的内容')
|
|
|
- return
|
|
|
- }
|
|
|
- downloadBatch(files.map(f => f.fileId), folders.map(f => f.folderId))
|
|
|
- }).catch(() => {})
|
|
|
+// 导航到指定层级
|
|
|
+async function goToFolder(index) {
|
|
|
+ if (index === breadcrumb.value.length - 1) return
|
|
|
+
|
|
|
+ const folder = breadcrumb.value[index]
|
|
|
+ // 截取面包屑到当前层级之前(因为enterFolder会添加当前层级)
|
|
|
+ breadcrumb.value = breadcrumb.value.slice(0, index)
|
|
|
+ await enterFolder(folder)
|
|
|
}
|
|
|
|
|
|
-function downloadSelected() {
|
|
|
- if (selectedItems.value.length === 0) {
|
|
|
- proxy.$modal.msgWarning('请选择要下载的项目')
|
|
|
- return
|
|
|
+// 下载单文件
|
|
|
+function downloadFile() {
|
|
|
+ if (!fileInfo.value) return
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.href = `${import.meta.env.VITE_APP_BASE_API}/system/file/share/download/${shareCode.value}`
|
|
|
+ link.download = fileInfo.value.fileName
|
|
|
+ link.click()
|
|
|
+}
|
|
|
+
|
|
|
+// 下载列表项
|
|
|
+async function downloadItem(item) {
|
|
|
+ if (item.isFolder) {
|
|
|
+ // 先检查文件夹是否为空
|
|
|
+ Modal.confirm({
|
|
|
+ title: '下载文件夹',
|
|
|
+ content: `确定要下载文件夹 "${item.folderName}" 吗?`,
|
|
|
+ okText: '确定',
|
|
|
+ cancelText: '取消',
|
|
|
+ onOk: () => {
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.href = `${import.meta.env.VITE_APP_BASE_API}/system/file/share/downloadFolder/${shareCode.value}/${item.folderId}`
|
|
|
+ link.click()
|
|
|
+ proxy.$modal.msgSuccess('开始打包下载文件夹,请稍候...')
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.href = `${import.meta.env.VITE_APP_BASE_API}/system/file/share/download/${shareCode.value}/${item.fileId}`
|
|
|
+ link.click()
|
|
|
}
|
|
|
- const files = selectedItems.value.filter(item => !item.isFolder)
|
|
|
- const folders = selectedItems.value.filter(item => item.isFolder)
|
|
|
- downloadBatch(files.map(f => f.fileId), folders.map(f => f.folderId))
|
|
|
}
|
|
|
|
|
|
+// 下载全部
|
|
|
+function downloadAll() {
|
|
|
+ Modal.confirm({
|
|
|
+ title: '确认下载',
|
|
|
+ content: '确定要将当前目录下的所有内容打包下载吗?',
|
|
|
+ okText: '确定',
|
|
|
+ cancelText: '取消',
|
|
|
+ onOk: () => {
|
|
|
+ const files = (fileInfo.value.fileList || []).map(f => f.fileId).filter(id => id)
|
|
|
+ const folders = (folderList.value || []).map(f => f.folderId).filter(id => id)
|
|
|
+
|
|
|
+ if (files.length === 0 && folders.length === 0) {
|
|
|
+ proxy.$modal.msgWarning('当前目录没有可下载的内容')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ downloadBatch(files, folders)
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 批量下载
|
|
|
async function downloadBatch(fileIds, folderIds) {
|
|
|
try {
|
|
|
const response = await fetch(`${import.meta.env.VITE_APP_BASE_API}/system/file/share/downloadBatch/${shareCode.value}`, {
|
|
|
@@ -338,23 +414,23 @@ async function downloadBatch(fileIds, folderIds) {
|
|
|
proxy.$modal.msgError('下载失败')
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- proxy.$modal.msgError('下载失败')
|
|
|
+ proxy.$modal.msgError('下载请求失败')
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-function downloadFolder(folder) {
|
|
|
- const link = document.createElement('a')
|
|
|
- link.href = `${import.meta.env.VITE_APP_BASE_API}/system/file/share/downloadFolder/${shareCode.value}/${folder.folderId}`
|
|
|
- link.click()
|
|
|
- proxy.$modal.msgSuccess(`开始下载文件夹: ${folder.folderName}`)
|
|
|
-}
|
|
|
-
|
|
|
+// 显示错误
|
|
|
function showError(message, detail) {
|
|
|
loading.value = false
|
|
|
errorMessage.value = message
|
|
|
errorDetail.value = detail
|
|
|
}
|
|
|
|
|
|
+// 刷新页面
|
|
|
+function refreshPage() {
|
|
|
+ window.location.reload()
|
|
|
+}
|
|
|
+
|
|
|
+// 格式化文件大小
|
|
|
function formatFileSize(bytes) {
|
|
|
if (!bytes) return '0 B'
|
|
|
const k = 1024
|
|
|
@@ -364,250 +440,313 @@ function formatFileSize(bytes) {
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
-<style lang="scss" scoped>
|
|
|
+<style scoped>
|
|
|
+/* 全局变量模拟 */
|
|
|
.share-page {
|
|
|
+ --primary-color: #1890ff;
|
|
|
+ --bg-color: #f5f7fa;
|
|
|
+ --card-bg: #ffffff;
|
|
|
+ --text-main: #333333;
|
|
|
+ --text-secondary: #666666;
|
|
|
+ --border-color: #f0f0f0;
|
|
|
+
|
|
|
min-height: 100vh;
|
|
|
- background: #f0f2f5 url("data:image/svg+xml,%3Csvg width='64' height='64' viewBox='0 0 64 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 16c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zm33.414 0c5.955 0 10.791-4.836 10.791-10.791 0-5.955-4.836-10.791-10.791-10.791-5.955 0-10.791 4.836-10.791 10.791 0 5.955 4.836 10.791 10.791 10.791zm0-2c4.85 0 8.791-3.941 8.791-8.791 0-4.85-3.941-8.791-8.791-8.791-4.85 0-8.791 3.941-8.791 8.791 0 4.85 3.941 8.791 8.791 8.791zM16 48c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zM8 32c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zm33.414 32c5.955 0 10.791-4.836 10.791-10.791 0-5.955-4.836-10.791-10.791-10.791-5.955 0-10.791 4.836-10.791 10.791 0 5.955 4.836 10.791 10.791 10.791zm0-2c4.85 0 8.791-3.941 8.791-8.791 0-4.85-3.941-8.791-8.791-8.791-4.85 0-8.791 3.941-8.791 8.791 0 4.85 3.941 8.791 8.791 8.791zM63.5 0h-3.2c-1.036 0-1.884.848-1.884 1.884v3.2c0 1.036.848 1.884 1.884 1.884h3.2c1.036 0 1.884-.848 1.884-1.884v-3.2c0-1.036-.848-1.884-1.884-1.884zM61 5h-2V3h2v2zm-28 0h-3.2c-1.036 0-1.884.848-1.884 1.884v3.2c0 1.036.848 1.884 1.884 1.884h3.2c1.036 0 1.884-.848 1.884-1.884v-3.2c0-1.036-.848-1.884-1.884-1.884zM33 5h-2V3h2v2zM6.5 0H3.3c-1.036 0-1.884.848-1.884 1.884v3.2c0 1.036.848 1.884 1.884 1.884h3.2c1.036 0 1.884-.848 1.884-1.884v-3.2c0-1.036-.848-1.884-1.884-1.884zM4 5H2V3h2v2z' fill='%239C92AC' fill-opacity='0.05' fill-rule='evenodd'/%3E%3C/svg%3E");
|
|
|
+ background-color: var(--bg-color);
|
|
|
display: flex;
|
|
|
+ flex-direction: column;
|
|
|
align-items: center;
|
|
|
- justify-content: center;
|
|
|
- padding: 20px;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
|
}
|
|
|
|
|
|
.share-container {
|
|
|
width: 100%;
|
|
|
+ max-width: 1000px;
|
|
|
+ flex: 1;
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
justify-content: center;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 60px 20px;
|
|
|
}
|
|
|
|
|
|
-.share-box {
|
|
|
- background: rgba(255, 255, 255, 0.95);
|
|
|
- backdrop-filter: blur(10px);
|
|
|
- border-radius: 16px;
|
|
|
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
|
|
|
- padding: 48px;
|
|
|
- max-width: 520px;
|
|
|
+/* 卡片通用样式 */
|
|
|
+.password-box, .content-box, .error-box, .loading-box {
|
|
|
width: 100%;
|
|
|
+ background: var(--card-bg);
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
|
|
+ overflow: hidden;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+/* 加载中 */
|
|
|
+.loading-box {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ min-height: 400px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 密码框 */
|
|
|
+.password-box {
|
|
|
+ max-width: 480px;
|
|
|
+ margin: 40px auto;
|
|
|
+ padding: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.password-card {
|
|
|
text-align: center;
|
|
|
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
-
|
|
|
- &.full-width {
|
|
|
- max-width: 1200px;
|
|
|
- padding: 32px;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
-.loading-box,
|
|
|
-.password-box,
|
|
|
-.file-info-box,
|
|
|
-.error-box {
|
|
|
- padding: 20px 0;
|
|
|
- animation: fadeIn 0.5s ease-out;
|
|
|
+.password-card .icon-wrapper {
|
|
|
+ font-size: 48px;
|
|
|
+ color: var(--primary-color);
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.password-card .title {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--text-main);
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.password-card .subtitle {
|
|
|
+ color: var(--text-secondary);
|
|
|
+ margin-bottom: 32px;
|
|
|
}
|
|
|
|
|
|
-@keyframes fadeIn {
|
|
|
- from { opacity: 0; transform: translateY(10px); }
|
|
|
- to { opacity: 1; transform: translateY(0); }
|
|
|
+.password-card .input-group {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
}
|
|
|
|
|
|
-.share-header {
|
|
|
+.password-card .code-input {
|
|
|
text-align: center;
|
|
|
- margin-bottom: 40px;
|
|
|
-
|
|
|
- .anticon {
|
|
|
- margin-bottom: 16px;
|
|
|
- padding: 16px;
|
|
|
- background: #e6f7ff;
|
|
|
- border-radius: 50%;
|
|
|
- color: #1890ff;
|
|
|
- }
|
|
|
-
|
|
|
- h1 {
|
|
|
- font-size: 28px;
|
|
|
- font-weight: 700;
|
|
|
- color: #303133;
|
|
|
- margin: 8px 0;
|
|
|
- letter-spacing: -0.5px;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- color: #999;
|
|
|
- font-size: 15px;
|
|
|
- margin-top: 8px;
|
|
|
- }
|
|
|
+ font-size: 18px;
|
|
|
+ letter-spacing: 2px;
|
|
|
}
|
|
|
|
|
|
-.password-input-box {
|
|
|
- max-width: 320px;
|
|
|
- margin: 0 auto;
|
|
|
-
|
|
|
- .password-input {
|
|
|
- :deep(.ant-input) {
|
|
|
- text-align: center;
|
|
|
- font-size: 24px;
|
|
|
- font-weight: 600;
|
|
|
- letter-spacing: 12px;
|
|
|
- height: 52px;
|
|
|
- color: #303133;
|
|
|
-
|
|
|
- &::placeholder {
|
|
|
- font-size: 16px;
|
|
|
- letter-spacing: 1px;
|
|
|
- font-weight: 400;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .submit-btn {
|
|
|
- width: 100%;
|
|
|
- margin-top: 24px;
|
|
|
- height: 48px;
|
|
|
- border-radius: 12px;
|
|
|
- font-size: 16px;
|
|
|
- font-weight: 600;
|
|
|
- letter-spacing: 1px;
|
|
|
- box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
|
|
- transition: all 0.2s;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- transform: translateY(-1px);
|
|
|
- box-shadow: 0 6px 16px rgba(24, 144, 255, 0.4);
|
|
|
- }
|
|
|
-
|
|
|
- &:active {
|
|
|
- transform: translateY(0);
|
|
|
- }
|
|
|
- }
|
|
|
+.password-card .submit-btn {
|
|
|
+ height: 44px;
|
|
|
+ font-size: 16px;
|
|
|
+ border-radius: 6px;
|
|
|
}
|
|
|
|
|
|
-.file-icon {
|
|
|
- margin-bottom: 24px;
|
|
|
-
|
|
|
- .anticon {
|
|
|
- padding: 24px;
|
|
|
- background: #f0f2f5;
|
|
|
- border-radius: 20px;
|
|
|
- box-shadow: inset 0 0 0 1px rgba(0,0,0,0.05);
|
|
|
- }
|
|
|
+/* 内容区域 */
|
|
|
+.content-box {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-height: 600px;
|
|
|
+}
|
|
|
+
|
|
|
+.content-header {
|
|
|
+ padding: 24px 32px;
|
|
|
+ border-bottom: 1px solid var(--border-color);
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ background-color: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+.content-header .header-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
}
|
|
|
|
|
|
-h2 {
|
|
|
+.content-header .file-icon {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #e6f7ff;
|
|
|
+ border-radius: 8px;
|
|
|
font-size: 24px;
|
|
|
+ color: var(--primary-color);
|
|
|
+}
|
|
|
+
|
|
|
+.content-header .file-details .file-name {
|
|
|
+ font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
- margin-bottom: 12px;
|
|
|
- color: #303133;
|
|
|
+ color: var(--text-main);
|
|
|
+ margin-bottom: 4px;
|
|
|
+ max-width: 400px;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
}
|
|
|
|
|
|
-.file-meta {
|
|
|
- color: #606266;
|
|
|
- margin: 24px 0;
|
|
|
- font-size: 14px;
|
|
|
+.content-header .file-details .file-meta {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: var(--text-secondary);
|
|
|
+}
|
|
|
+
|
|
|
+.content-header .file-details .meta-tag {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.content-header .file-details .meta-tag.expire {
|
|
|
+ color: #ff4d4f;
|
|
|
+ background: #fff1f0;
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 单文件预览 */
|
|
|
+.single-file-preview {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
justify-content: center;
|
|
|
- gap: 24px;
|
|
|
-
|
|
|
- span {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 6px;
|
|
|
- background: #f5f7fa;
|
|
|
- padding: 6px 12px;
|
|
|
- border-radius: 20px;
|
|
|
- }
|
|
|
+ padding: 60px;
|
|
|
}
|
|
|
|
|
|
-.folder-share {
|
|
|
- width: 100%;
|
|
|
- text-align: left;
|
|
|
-
|
|
|
- .folder-header {
|
|
|
- text-align: center;
|
|
|
- padding: 20px 0 40px;
|
|
|
- border-bottom: 1px solid #ebeef5;
|
|
|
- margin-bottom: 20px;
|
|
|
-
|
|
|
- .anticon {
|
|
|
- padding: 16px;
|
|
|
- background: #e6f7ff;
|
|
|
- border-radius: 16px;
|
|
|
- margin-bottom: 16px;
|
|
|
- }
|
|
|
-
|
|
|
- h2 {
|
|
|
- margin: 0;
|
|
|
- font-size: 24px;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- color: #999;
|
|
|
- margin-top: 8px;
|
|
|
- font-size: 14px;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .toolbar {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- padding: 12px 0;
|
|
|
-
|
|
|
- .toolbar-left {
|
|
|
- flex: 1;
|
|
|
- overflow: hidden;
|
|
|
- }
|
|
|
-
|
|
|
- .toolbar-right {
|
|
|
- display: flex;
|
|
|
- gap: 12px;
|
|
|
- flex-shrink: 0;
|
|
|
- margin-left: 20px;
|
|
|
- }
|
|
|
- }
|
|
|
+.single-file-preview .preview-icon {
|
|
|
+ font-size: 80px;
|
|
|
+ color: #d9d9d9;
|
|
|
+ margin-bottom: 24px;
|
|
|
}
|
|
|
|
|
|
-.action-buttons {
|
|
|
- margin: 40px 0;
|
|
|
-
|
|
|
- .ant-btn {
|
|
|
- padding: 12px 32px;
|
|
|
- font-size: 16px;
|
|
|
- border-radius: 8px;
|
|
|
- height: auto;
|
|
|
- }
|
|
|
+.single-file-preview .preview-tip {
|
|
|
+ color: var(--text-secondary);
|
|
|
+ font-size: 16px;
|
|
|
}
|
|
|
|
|
|
-.share-info {
|
|
|
- margin-top: 32px;
|
|
|
- padding-top: 24px;
|
|
|
- border-top: 1px solid #ebeef5;
|
|
|
-
|
|
|
- p {
|
|
|
- color: #999;
|
|
|
- font-size: 13px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- gap: 6px;
|
|
|
- }
|
|
|
+/* 文件夹内容 */
|
|
|
+.folder-content {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.folder-content .breadcrumb-bar {
|
|
|
+ padding: 16px 32px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ border-bottom: 1px solid var(--border-color);
|
|
|
}
|
|
|
|
|
|
+.folder-content .breadcrumb-bar .file-count {
|
|
|
+ color: var(--text-secondary);
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+.folder-content .breadcrumb-bar a {
|
|
|
+ color: var(--text-secondary);
|
|
|
+ transition: color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.folder-content .breadcrumb-bar a:hover,
|
|
|
+.folder-content .breadcrumb-bar a.active {
|
|
|
+ color: var(--primary-color);
|
|
|
+}
|
|
|
+
|
|
|
+.folder-content .table-wrapper {
|
|
|
+ padding: 0 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.folder-content .name-cell {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 8px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.folder-content .name-cell:hover .text {
|
|
|
+ color: var(--primary-color);
|
|
|
+}
|
|
|
+
|
|
|
+.folder-content .name-cell .icon {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.folder-content .name-cell .text {
|
|
|
+ font-weight: 500;
|
|
|
+ color: var(--text-main);
|
|
|
+ transition: color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.folder-content .size-text,
|
|
|
+.folder-content .time-text {
|
|
|
+ color: var(--text-secondary);
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 错误提示 */
|
|
|
.error-box {
|
|
|
- .anticon {
|
|
|
- background: #fff1f0;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 50%;
|
|
|
- margin-bottom: 24px;
|
|
|
+ max-width: 480px;
|
|
|
+ margin: 40px auto;
|
|
|
+ text-align: center;
|
|
|
+ padding: 60px 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.error-box .error-icon {
|
|
|
+ font-size: 64px;
|
|
|
+ color: #ff4d4f;
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.error-box h2 {
|
|
|
+ font-size: 20px;
|
|
|
+ color: var(--text-main);
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.error-box p {
|
|
|
+ color: var(--text-secondary);
|
|
|
+ margin-bottom: 32px;
|
|
|
+}
|
|
|
+
|
|
|
+.footer {
|
|
|
+ text-align: center;
|
|
|
+ padding: 24px;
|
|
|
+ color: #999;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 过渡动画 */
|
|
|
+.fade-enter-active,
|
|
|
+.fade-leave-active {
|
|
|
+ transition: opacity 0.3s ease, transform 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.fade-enter-from,
|
|
|
+.fade-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(10px);
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式调整 */
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .share-container {
|
|
|
+ padding: 20px 10px;
|
|
|
}
|
|
|
|
|
|
- h2 {
|
|
|
- color: #303133;
|
|
|
- margin: 0 0 8px;
|
|
|
+ .content-box .content-header {
|
|
|
+ padding: 16px;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-box .content-header .header-right {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
}
|
|
|
|
|
|
- p {
|
|
|
- color: #999;
|
|
|
+ .folder-content .breadcrumb-bar {
|
|
|
+ padding: 12px 16px;
|
|
|
}
|
|
|
}
|
|
|
</style>
|