|
|
@@ -0,0 +1,1224 @@
|
|
|
+<template>
|
|
|
+ <div class="share-page">
|
|
|
+ <div class="container">
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <div v-if="loading" class="loading-container">
|
|
|
+ <div class="spinner large"></div>
|
|
|
+ <p>加载中...</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 密码验证 -->
|
|
|
+ <div v-else-if="requiresPassword && !passwordVerified" class="password-container card">
|
|
|
+ <h2>🔒 此内容需要密码</h2>
|
|
|
+ <input
|
|
|
+ v-model="password"
|
|
|
+ type="password"
|
|
|
+ class="password-input"
|
|
|
+ placeholder="请输入访问密码"
|
|
|
+ @keyup.enter="verifyPassword"
|
|
|
+ >
|
|
|
+ <button class="btn btn-primary btn-lg" @click="verifyPassword">
|
|
|
+ 验证密码
|
|
|
+ </button>
|
|
|
+ <p v-if="passwordError" class="error-message">{{ passwordError }}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分享内容 -->
|
|
|
+ <div v-else-if="shareData" class="share-content">
|
|
|
+ <!-- 左右分栏布局容器 -->
|
|
|
+ <div v-if="shareData.files && shareData.files.length > 0" class="content-split">
|
|
|
+ <!-- 左侧:文件下载区 -->
|
|
|
+ <div class="left-column">
|
|
|
+ <div class="files-content card">
|
|
|
+ <div class="content-header">
|
|
|
+ <h2>📁 分享的文件</h2>
|
|
|
+ <span class="file-count">{{ shareData.files.length }} 个文件</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="files-list">
|
|
|
+ <div
|
|
|
+ v-for="file in shareData.files"
|
|
|
+ :key="file.fileId"
|
|
|
+ class="file-item"
|
|
|
+ >
|
|
|
+ <div class="file-info">
|
|
|
+ <svg class="file-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
|
|
|
+ <polyline points="13 2 13 9 20 9"></polyline>
|
|
|
+ </svg>
|
|
|
+ <div class="file-details">
|
|
|
+ <p class="file-name">{{ file.fileName }}</p>
|
|
|
+ <p class="file-size">{{ formatFileSize(file.fileSize) }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="file-actions">
|
|
|
+ <!-- 预览按钮(仅图片和PDF) -->
|
|
|
+ <button
|
|
|
+ v-if="isPreviewable(file.mimeType)"
|
|
|
+ class="btn-action btn-preview"
|
|
|
+ @click="previewFile(file.fileId)"
|
|
|
+ title="在新窗口预览"
|
|
|
+ >
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
|
|
+ <circle cx="12" cy="12" r="3"></circle>
|
|
|
+ </svg>
|
|
|
+ <span>预览</span>
|
|
|
+ </button>
|
|
|
+ <!-- 下载按钮 -->
|
|
|
+ <button
|
|
|
+ class="btn-action btn-download"
|
|
|
+ @click="downloadFile(file.fileId, file.fileName)"
|
|
|
+ title="下载文件"
|
|
|
+ >
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
|
+ <polyline points="7 10 12 15 17 10"></polyline>
|
|
|
+ <line x1="12" y1="15" x2="12" y2="3"></line>
|
|
|
+ </svg>
|
|
|
+ <span>下载</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 打包下载按钮 -->
|
|
|
+ <button
|
|
|
+ class="btn-download-all btn btn-primary btn-lg"
|
|
|
+ @click="downloadAll"
|
|
|
+ :disabled="downloadingAll"
|
|
|
+ >
|
|
|
+ {{ downloadingAll ? '⏳ 正在打包...' : '📦 打包下载所有文件' }}
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <div class="content-info">
|
|
|
+ <p>创建时间:{{ formatTime(shareData.createdTime) }}</p>
|
|
|
+ <p>过期时间:{{ formatTime(shareData.expirationTime) }}</p>
|
|
|
+ <p>下载次数:{{ shareData.currentDownloadCount }}/{{ shareData.maxDownloadCount === -1 ? '无限制' : shareData.maxDownloadCount }}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 删除资源按钮 -->
|
|
|
+ <div class="content-actions">
|
|
|
+ <button
|
|
|
+ class="btn-delete"
|
|
|
+ @click="confirmDelete"
|
|
|
+ title="删除此分享资源"
|
|
|
+ >
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="3 6 5 6 21 6"></polyline>
|
|
|
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
|
+ </svg>
|
|
|
+ <span>删除资源</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧:文本内容区 -->
|
|
|
+ <div v-if="shareData.textContent" class="right-column">
|
|
|
+ <div class="text-content card">
|
|
|
+ <div class="content-header">
|
|
|
+ <h2>📝 分享的文本</h2>
|
|
|
+ <div class="header-actions">
|
|
|
+ <span class="content-type">{{ shareData.textType }}</span>
|
|
|
+ <!-- Markdown切换按钮 -->
|
|
|
+ <button
|
|
|
+ v-if="shareData.textType === 'markdown'"
|
|
|
+ class="btn btn-secondary btn-sm"
|
|
|
+ @click="showMarkdownSource = !showMarkdownSource"
|
|
|
+ >
|
|
|
+ {{ showMarkdownSource ? '👁️ 查看预览' : '📝 查看源码' }}
|
|
|
+ </button>
|
|
|
+ <!-- 复制按钮 -->
|
|
|
+ <button
|
|
|
+ class="btn-copy-text"
|
|
|
+ :class="{ copied: copyStatus === 'copied' }"
|
|
|
+ @click="copyTextContent"
|
|
|
+ :title="copyStatus === 'copied' ? '已复制' : '复制内容'"
|
|
|
+ >
|
|
|
+ <svg v-if="copyStatus !== 'copied'" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
|
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
|
+ </svg>
|
|
|
+ <svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="20 6 9 17 4 12"></polyline>
|
|
|
+ </svg>
|
|
|
+ <span>{{ copyStatus === 'copied' ? '已复制' : '复制' }}</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="text-display">
|
|
|
+ <Transition name="fade" mode="out-in">
|
|
|
+ <!-- Markdown渲染 -->
|
|
|
+ <MarkdownRenderer
|
|
|
+ v-if="shareData.textType === 'markdown' && !showMarkdownSource"
|
|
|
+ :content="shareData.textContent"
|
|
|
+ key="preview"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- Markdown源码 -->
|
|
|
+ <pre v-else-if="shareData.textType === 'markdown' && showMarkdownSource" key="source">{{ shareData.textContent }}</pre>
|
|
|
+
|
|
|
+ <!-- 代码高亮 -->
|
|
|
+ <CodeHighlight
|
|
|
+ v-else-if="shareData.textType === 'code'"
|
|
|
+ :code="shareData.textContent"
|
|
|
+ key="code"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- JSON格式化 -->
|
|
|
+ <CodeHighlight
|
|
|
+ v-else-if="shareData.textType === 'json'"
|
|
|
+ :code="formatJson(shareData.textContent)"
|
|
|
+ language="json"
|
|
|
+ key="json"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 纯文本 -->
|
|
|
+ <pre v-else key="plain">{{ shareData.textContent }}</pre>
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="content-info">
|
|
|
+ <p>创建时间:{{ formatTime(shareData.createdTime) }}</p>
|
|
|
+ <p>过期时间:{{ formatTime(shareData.expirationTime) }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 单独显示文本内容(无文件时) -->
|
|
|
+ <div v-else-if="shareData.textContent" class="text-content card full-width">
|
|
|
+ <div class="content-header">
|
|
|
+ <h2>📝 分享的文本</h2>
|
|
|
+ <div class="header-actions">
|
|
|
+ <span class="content-type">{{ shareData.textType }}</span>
|
|
|
+ <!-- Markdown切换按钮 -->
|
|
|
+ <button
|
|
|
+ v-if="shareData.textType === 'markdown'"
|
|
|
+ class="btn btn-secondary btn-sm"
|
|
|
+ @click="showMarkdownSource = !showMarkdownSource"
|
|
|
+ >
|
|
|
+ {{ showMarkdownSource ? '👁️ 查看预览' : '📝 查看源码' }}
|
|
|
+ </button>
|
|
|
+ <!-- 复制按钮 -->
|
|
|
+ <button
|
|
|
+ class="btn-copy-text"
|
|
|
+ :class="{ copied: copyStatus === 'copied' }"
|
|
|
+ @click="copyTextContent"
|
|
|
+ :title="copyStatus === 'copied' ? '已复制' : '复制内容'"
|
|
|
+ >
|
|
|
+ <svg v-if="copyStatus !== 'copied'" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
|
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
|
+ </svg>
|
|
|
+ <svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="20 6 9 17 4 12"></polyline>
|
|
|
+ </svg>
|
|
|
+ <span>{{ copyStatus === 'copied' ? '已复制' : '复制' }}</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="text-display">
|
|
|
+ <Transition name="fade" mode="out-in">
|
|
|
+ <!-- Markdown渲染 -->
|
|
|
+ <MarkdownRenderer
|
|
|
+ v-if="shareData.textType === 'markdown' && !showMarkdownSource"
|
|
|
+ :content="shareData.textContent"
|
|
|
+ key="preview"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- Markdown源码 -->
|
|
|
+ <pre v-else-if="shareData.textType === 'markdown' && showMarkdownSource" key="source">{{ shareData.textContent }}</pre>
|
|
|
+
|
|
|
+ <!-- 代码高亮 -->
|
|
|
+ <CodeHighlight
|
|
|
+ v-else-if="shareData.textType === 'code'"
|
|
|
+ :code="shareData.textContent"
|
|
|
+ key="code"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- JSON格式化 -->
|
|
|
+ <CodeHighlight
|
|
|
+ v-else-if="shareData.textType === 'json'"
|
|
|
+ :code="formatJson(shareData.textContent)"
|
|
|
+ language="json"
|
|
|
+ key="json"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 纯文本 -->
|
|
|
+ <pre v-else key="plain">{{ shareData.textContent }}</pre>
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="content-info">
|
|
|
+ <p>创建时间:{{ formatTime(shareData.createdTime) }}</p>
|
|
|
+ <p>过期时间:{{ formatTime(shareData.expirationTime) }}</p>
|
|
|
+ <p>下载次数:{{ shareData.currentDownloadCount }}/{{ shareData.maxDownloadCount === -1 ? '无限制' : shareData.maxDownloadCount }}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 删除资源按钮 -->
|
|
|
+ <div class="content-actions">
|
|
|
+ <button
|
|
|
+ class="btn-delete"
|
|
|
+ @click="confirmDelete"
|
|
|
+ title="删除此分享资源"
|
|
|
+ >
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
|
+ <polyline points="3 6 5 6 21 6"></polyline>
|
|
|
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
|
+ </svg>
|
|
|
+ <span>删除资源</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 内容不存在或已过期 -->
|
|
|
+ <div v-else class="error-container card">
|
|
|
+ <h2>😔 内容不存在</h2>
|
|
|
+ <p>该分享内容可能已被删除或已过期</p>
|
|
|
+ <router-link to="/" class="btn btn-primary">返回首页</router-link>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 错误状态 -->
|
|
|
+ <div v-else-if="error" class="error-container card">
|
|
|
+ <h2>❌ 加载失败</h2>
|
|
|
+ <p>{{ error }}</p>
|
|
|
+ <router-link to="/" class="btn btn-primary">返回首页</router-link>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 浮动返回首页按钮 -->
|
|
|
+ <router-link to="/" class="floating-back-btn" title="返回首页">
|
|
|
+ 返回首页
|
|
|
+ </router-link>
|
|
|
+
|
|
|
+ <!-- 删除确认对话框 -->
|
|
|
+ <div v-if="showDeleteConfirm" class="modal-overlay" @click="showDeleteConfirm = false">
|
|
|
+ <div class="modal delete-confirm-modal" @click.stop>
|
|
|
+ <div class="modal-header">
|
|
|
+ <h3>⚠️ 确认删除</h3>
|
|
|
+ </div>
|
|
|
+ <div class="modal-body">
|
|
|
+ <p>确定要删除此分享资源吗?</p>
|
|
|
+ <p class="warning-text">
|
|
|
+ 删除后将无法恢复,所有文件和文本内容将被永久删除。
|
|
|
+ </p>
|
|
|
+
|
|
|
+ <!-- 如果有密码保护,提示输入密码 -->
|
|
|
+ <div v-if="shareData && shareData.requiresPassword" class="password-verify">
|
|
|
+ <label>请输入密码以确认删除:</label>
|
|
|
+ <input
|
|
|
+ v-model="deletePassword"
|
|
|
+ type="password"
|
|
|
+ class="password-input"
|
|
|
+ placeholder="输入访问密码"
|
|
|
+ @keyup.enter="executeDelete"
|
|
|
+ >
|
|
|
+ <p v-if="deleteError" class="error-message">{{ deleteError }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="modal-footer">
|
|
|
+ <button class="btn btn-secondary" @click="showDeleteConfirm = false">
|
|
|
+ 取消
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="btn btn-danger"
|
|
|
+ @click="executeDelete"
|
|
|
+ :disabled="deleting"
|
|
|
+ >
|
|
|
+ <span v-if="deleting">删除中...</span>
|
|
|
+ <span v-else>确认删除</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
+import { useRoute, useRouter } from 'vue-router'
|
|
|
+import { getShareContent, verifyAndGetShareContent, downloadFile as downloadFileApi, deleteShare } from '@/api'
|
|
|
+import config from '@/config'
|
|
|
+import CodeHighlight from '@/components/CodeHighlight.vue'
|
|
|
+import MarkdownRenderer from '@/components/MarkdownRenderer.vue'
|
|
|
+import toast from '@/utils/toast'
|
|
|
+
|
|
|
+const route = useRoute()
|
|
|
+const router = useRouter()
|
|
|
+const id = route.params.id
|
|
|
+
|
|
|
+// 状态
|
|
|
+const loading = ref(true)
|
|
|
+const shareData = ref(null)
|
|
|
+const requiresPassword = ref(false)
|
|
|
+const passwordVerified = ref(false)
|
|
|
+const password = ref('')
|
|
|
+const passwordError = ref('')
|
|
|
+const error = ref('')
|
|
|
+const downloadingAll = ref(false)
|
|
|
+const showMarkdownSource = ref(false) // Markdown源码/预览切换状态
|
|
|
+const copyStatus = ref('idle') // 'idle' | 'copied'
|
|
|
+
|
|
|
+// 删除相关状态
|
|
|
+const showDeleteConfirm = ref(false)
|
|
|
+const deletePassword = ref('')
|
|
|
+const deleteError = ref('')
|
|
|
+const deleting = ref(false)
|
|
|
+
|
|
|
+// 生命周期
|
|
|
+onMounted(() => {
|
|
|
+ loadShareContent()
|
|
|
+})
|
|
|
+
|
|
|
+// 方法
|
|
|
+async function loadShareContent() {
|
|
|
+ loading.value = true
|
|
|
+ error.value = ''
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await getShareContent(id)
|
|
|
+
|
|
|
+ if (response.requiresPassword) {
|
|
|
+ requiresPassword.value = true
|
|
|
+ } else {
|
|
|
+ shareData.value = response
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ error.value = err.message || '加载失败'
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function verifyPassword() {
|
|
|
+ passwordError.value = ''
|
|
|
+
|
|
|
+ if (!password.value) {
|
|
|
+ passwordError.value = '请输入密码'
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ loading.value = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await verifyAndGetShareContent(id, password.value)
|
|
|
+ shareData.value = response
|
|
|
+ passwordVerified.value = true
|
|
|
+ } catch (err) {
|
|
|
+ passwordError.value = '密码错误'
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function downloadFile(fileId, fileName) {
|
|
|
+ const url = downloadFileApi(id, fileId)
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.href = url
|
|
|
+ link.download = fileName
|
|
|
+ link.click()
|
|
|
+}
|
|
|
+
|
|
|
+// 判断文件是否可预览(图片或PDF)
|
|
|
+function isPreviewable(mimeType) {
|
|
|
+ if (!mimeType) return false
|
|
|
+ const previewableTypes = [
|
|
|
+ // 图片类型
|
|
|
+ 'image/jpeg',
|
|
|
+ 'image/jpg',
|
|
|
+ 'image/png',
|
|
|
+ 'image/gif',
|
|
|
+ 'image/webp',
|
|
|
+ 'image/svg+xml',
|
|
|
+ 'image/bmp',
|
|
|
+ 'image/x-icon',
|
|
|
+ // PDF类型
|
|
|
+ 'application/pdf'
|
|
|
+ ]
|
|
|
+ return previewableTypes.includes(mimeType)
|
|
|
+}
|
|
|
+
|
|
|
+// 在新窗口预览文件
|
|
|
+function previewFile(fileId) {
|
|
|
+ const url = downloadFileApi(id, fileId)
|
|
|
+ window.open(url, '_blank', 'noopener,noreferrer')
|
|
|
+}
|
|
|
+
|
|
|
+async function downloadAll() {
|
|
|
+ if (downloadingAll.value) return
|
|
|
+
|
|
|
+ downloadingAll.value = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ const url = `${config.baseURL}/api/share/${id}/download-all`
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.href = url
|
|
|
+ link.download = `${id}_files.zip`
|
|
|
+ document.body.appendChild(link)
|
|
|
+ link.click()
|
|
|
+ document.body.removeChild(link)
|
|
|
+ } catch (err) {
|
|
|
+ error.value = '打包下载失败:' + (err.message || '未知错误')
|
|
|
+ } finally {
|
|
|
+ downloadingAll.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function formatFileSize(bytes) {
|
|
|
+ if (bytes === 0) return '0 B'
|
|
|
+ const k = 1024
|
|
|
+ const sizes = ['B', 'KB', 'MB', 'GB']
|
|
|
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
|
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
|
|
|
+}
|
|
|
+
|
|
|
+function formatTime(timeStr) {
|
|
|
+ return new Date(timeStr).toLocaleString('zh-CN')
|
|
|
+}
|
|
|
+
|
|
|
+function formatJson(jsonStr) {
|
|
|
+ try {
|
|
|
+ const obj = JSON.parse(jsonStr)
|
|
|
+ return JSON.stringify(obj, null, 2)
|
|
|
+ } catch (e) {
|
|
|
+ return jsonStr
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 复制文本内容到剪贴板
|
|
|
+async function copyTextContent() {
|
|
|
+ if (!shareData.value?.textContent) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ await navigator.clipboard.writeText(shareData.value.textContent)
|
|
|
+ copyStatus.value = 'copied'
|
|
|
+ toast.success('内容已复制到剪贴板')
|
|
|
+ setTimeout(() => { copyStatus.value = 'idle' }, 2000)
|
|
|
+ } catch (err) {
|
|
|
+ // clipboard API 不可用时回退方案
|
|
|
+ const textarea = document.createElement('textarea')
|
|
|
+ textarea.value = shareData.value.textContent
|
|
|
+ textarea.style.position = 'fixed'
|
|
|
+ textarea.style.opacity = '0'
|
|
|
+ document.body.appendChild(textarea)
|
|
|
+ textarea.select()
|
|
|
+ document.execCommand('copy')
|
|
|
+ document.body.removeChild(textarea)
|
|
|
+ copyStatus.value = 'copied'
|
|
|
+ toast.success('内容已复制到剪贴板')
|
|
|
+ setTimeout(() => { copyStatus.value = 'idle' }, 2000)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 显示删除确认对话框
|
|
|
+function confirmDelete() {
|
|
|
+ showDeleteConfirm.value = true
|
|
|
+ deletePassword.value = ''
|
|
|
+ deleteError.value = ''
|
|
|
+}
|
|
|
+
|
|
|
+// 执行删除
|
|
|
+async function executeDelete() {
|
|
|
+ deleting.value = true
|
|
|
+ deleteError.value = ''
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 如果有密码保护,先验证密码
|
|
|
+ if (shareData.value.requiresPassword) {
|
|
|
+ if (!deletePassword.value) {
|
|
|
+ deleteError.value = '请输入密码'
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证密码
|
|
|
+ try {
|
|
|
+ await verifyAndGetShareContent(id, deletePassword.value)
|
|
|
+ } catch (err) {
|
|
|
+ deleteError.value = '密码错误'
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 调用删除API
|
|
|
+ await deleteShare(id)
|
|
|
+
|
|
|
+ // 显示成功提示
|
|
|
+ toast.success('资源已成功删除')
|
|
|
+
|
|
|
+ // 跳转回首页
|
|
|
+ router.push('/')
|
|
|
+
|
|
|
+ } catch (err) {
|
|
|
+ deleteError.value = err.message || '删除失败,请稍后重试'
|
|
|
+ } finally {
|
|
|
+ deleting.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+@import '@/styles/variables.scss';
|
|
|
+
|
|
|
+.share-page {
|
|
|
+ min-height: 100vh;
|
|
|
+ padding: $spacing-2xl $spacing-lg;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-container {
|
|
|
+ @extend .card !optional;
|
|
|
+ text-align: center;
|
|
|
+ padding: $spacing-2xl;
|
|
|
+
|
|
|
+ .spinner {
|
|
|
+ width: 60px;
|
|
|
+ height: 60px;
|
|
|
+ margin: 0 auto $spacing-lg;
|
|
|
+ border-width: 4px;
|
|
|
+
|
|
|
+ &.large {
|
|
|
+ width: 80px;
|
|
|
+ height: 80px;
|
|
|
+ border-width: 6px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ color: $text-secondary;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.password-container {
|
|
|
+ max-width: 500px;
|
|
|
+ margin: $spacing-2xl auto;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ h2 {
|
|
|
+ margin-bottom: $spacing-xl;
|
|
|
+ color: $text-secondary;
|
|
|
+ }
|
|
|
+
|
|
|
+ .password-input {
|
|
|
+ margin-bottom: $spacing-lg;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.error-message {
|
|
|
+ color: $error-color;
|
|
|
+ margin-top: $spacing-md;
|
|
|
+}
|
|
|
+
|
|
|
+.share-content {
|
|
|
+ max-width: 1400px; // 增加宽度以适应左右分栏
|
|
|
+ margin: 0 auto;
|
|
|
+
|
|
|
+ // 左右分栏时增加宽度
|
|
|
+ &.content-split {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.text-content,
|
|
|
+.files-content {
|
|
|
+ .content-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: $spacing-lg;
|
|
|
+
|
|
|
+ h2 {
|
|
|
+ color: $text-secondary;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-type,
|
|
|
+ .file-count {
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ background: $bg-tertiary;
|
|
|
+ padding: $spacing-xs $spacing-sm;
|
|
|
+ border-radius: $radius-full;
|
|
|
+ color: $text-muted;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.text-display {
|
|
|
+ background: $bg-tertiary;
|
|
|
+ border-radius: $radius-md;
|
|
|
+ padding: $spacing-lg;
|
|
|
+ margin-bottom: $spacing-lg;
|
|
|
+ max-height: 500px;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ pre {
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-wrap: break-word;
|
|
|
+ color: $text-primary;
|
|
|
+ font-family: 'Courier New', monospace;
|
|
|
+ line-height: 1.6;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.files-list {
|
|
|
+ margin-bottom: $spacing-lg;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: $spacing-md;
|
|
|
+ background: $bg-tertiary;
|
|
|
+ border-radius: $radius-md;
|
|
|
+ margin-bottom: $spacing-sm;
|
|
|
+}
|
|
|
+
|
|
|
+.file-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: $spacing-md;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.file-icon {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ color: $accent-color;
|
|
|
+}
|
|
|
+
|
|
|
+.file-details {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0; // 确保flex子项不会溢出
|
|
|
+
|
|
|
+ .file-name {
|
|
|
+ color: $text-primary;
|
|
|
+ margin-bottom: $spacing-xs;
|
|
|
+ word-break: break-word; // 长文件名自然换行
|
|
|
+ overflow-wrap: break-word;
|
|
|
+ white-space: normal; // 允许换行
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-size {
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ color: $text-muted;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 文件操作按钮容器
|
|
|
+.file-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: $spacing-sm;
|
|
|
+ align-items: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+// 现代化按钮样式
|
|
|
+.btn-action {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: $spacing-xs;
|
|
|
+ padding: $spacing-sm $spacing-md;
|
|
|
+ border-radius: $radius-md;
|
|
|
+ border: none;
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ font-weight: 500;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ white-space: nowrap;
|
|
|
+
|
|
|
+ svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ stroke-width: 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.btn-preview {
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ color: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
|
+ background: linear-gradient(135deg, #7c8efc 0%, #8a5db8 100%);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.btn-download {
|
|
|
+ background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
|
|
+ color: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(17, 153, 142, 0.3);
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ box-shadow: 0 4px 12px rgba(17, 153, 142, 0.4);
|
|
|
+ background: linear-gradient(135deg, #14b89f 0%, #4fff8d 100%);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.btn-download-all {
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: $spacing-lg;
|
|
|
+}
|
|
|
+
|
|
|
+.content-info {
|
|
|
+ padding-top: $spacing-lg;
|
|
|
+ border-top: 1px solid $border-color;
|
|
|
+
|
|
|
+ p {
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ color: $text-muted;
|
|
|
+ margin-bottom: $spacing-xs;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 内容操作区
|
|
|
+.content-actions {
|
|
|
+ margin-top: $spacing-md;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+// 删除按钮
|
|
|
+.btn-delete {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: $spacing-xs;
|
|
|
+ padding: $spacing-sm $spacing-md;
|
|
|
+ background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%); // 红色渐变
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ border-radius: $radius-md;
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ font-weight: 500;
|
|
|
+ cursor: pointer;
|
|
|
+ box-shadow: 0 2px 8px rgba(238, 90, 111, 0.3);
|
|
|
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+
|
|
|
+ svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ stroke-width: 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 4px 12px rgba(238, 90, 111, 0.4);
|
|
|
+ background: linear-gradient(135deg, #ff8787 0%, #ff6b6b 100%);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:disabled {
|
|
|
+ opacity: 0.6;
|
|
|
+ cursor: not-allowed;
|
|
|
+ transform: none !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.error-container {
|
|
|
+ max-width: 500px;
|
|
|
+ margin: $spacing-2xl auto;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ h2 {
|
|
|
+ margin-bottom: $spacing-lg;
|
|
|
+ color: $error-color;
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ color: $text-muted;
|
|
|
+ margin-bottom: $spacing-xl;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 新增样式 ====================
|
|
|
+
|
|
|
+// 删除确认对话框
|
|
|
+.delete-confirm-modal {
|
|
|
+ max-width: 450px;
|
|
|
+
|
|
|
+ .warning-text {
|
|
|
+ color: $warning-color;
|
|
|
+ font-weight: 500;
|
|
|
+ margin: $spacing-md 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .password-verify {
|
|
|
+ margin-top: $spacing-lg;
|
|
|
+ padding: $spacing-md;
|
|
|
+ background: $bg-tertiary;
|
|
|
+ border-radius: $radius-md;
|
|
|
+
|
|
|
+ label {
|
|
|
+ display: block;
|
|
|
+ margin-bottom: $spacing-sm;
|
|
|
+ color: $text-secondary;
|
|
|
+ }
|
|
|
+
|
|
|
+ .password-input {
|
|
|
+ width: 100%;
|
|
|
+ padding: $spacing-sm $spacing-md;
|
|
|
+ background: $bg-secondary;
|
|
|
+ border: 1px solid $border-color;
|
|
|
+ border-radius: $radius-md;
|
|
|
+ color: $text-primary;
|
|
|
+
|
|
|
+ &:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: $primary-color;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .error-message {
|
|
|
+ color: $error-color;
|
|
|
+ margin-top: $spacing-sm;
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.btn-danger {
|
|
|
+ background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
|
|
|
+ box-shadow: 0 2px 8px rgba(238, 90, 111, 0.3);
|
|
|
+
|
|
|
+ &:hover:not(:disabled) {
|
|
|
+ background: linear-gradient(135deg, #ff8787 0%, #ff6b6b 100%);
|
|
|
+ box-shadow: 0 4px 12px rgba(238, 90, 111, 0.4);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:disabled {
|
|
|
+ opacity: 0.6;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 左右分栏布局
|
|
|
+.content-split {
|
|
|
+ display: flex;
|
|
|
+ gap: $spacing-xl;
|
|
|
+ align-items: stretch;
|
|
|
+
|
|
|
+ @media (max-width: 968px) {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.left-column,
|
|
|
+.right-column {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0; // 防止flex子项溢出
|
|
|
+}
|
|
|
+
|
|
|
+.full-width {
|
|
|
+ max-width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+// 头部操作区
|
|
|
+.header-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: $spacing-md;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+// 复制文本按钮
|
|
|
+.btn-copy-text {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: $spacing-xs;
|
|
|
+ padding: $spacing-xs $spacing-md;
|
|
|
+ border-radius: $radius-md;
|
|
|
+ border: 1px solid $border-color;
|
|
|
+ background: $bg-tertiary;
|
|
|
+ color: $text-secondary;
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ font-weight: 500;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ white-space: nowrap;
|
|
|
+
|
|
|
+ svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ stroke-width: 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: $primary-color;
|
|
|
+ color: $primary-color;
|
|
|
+ background: rgba($primary-color, 0.08);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ transform: scale(0.97);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.copied {
|
|
|
+ border-color: #11998e;
|
|
|
+ color: #38ef7d;
|
|
|
+ background: rgba(#11998e, 0.1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Markdown过渡动画
|
|
|
+.fade-enter-active,
|
|
|
+.fade-leave-active {
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.fade-enter-from,
|
|
|
+.fade-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+}
|
|
|
+
|
|
|
+// 浮动返回按钮(文字样式 - 蓝色)
|
|
|
+.floating-back-btn {
|
|
|
+ position: fixed;
|
|
|
+ right: $spacing-xl;
|
|
|
+ bottom: $spacing-xl;
|
|
|
+ padding: $spacing-md $spacing-xl;
|
|
|
+ border-radius: $radius-lg;
|
|
|
+ background: #3b82f6; // 蓝色纯色
|
|
|
+ color: white;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: $font-size-base;
|
|
|
+ font-weight: 700; // 黑体
|
|
|
+ text-decoration: none;
|
|
|
+ box-shadow: 0 4px 16px rgba(59, 130, 246, 0.4); // 蓝色阴影
|
|
|
+ z-index: 1030;
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ cursor: pointer;
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 6px 24px rgba(59, 130, 246, 0.5); // 更亮的蓝色阴影
|
|
|
+ background: #2563eb; // hover时变深一点的蓝色
|
|
|
+ border-color: rgba(255, 255, 255, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ transform: translateY(0);
|
|
|
+ box-shadow: 0 2px 12px rgba(59, 130, 246, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 移动端适配
|
|
|
+ @media (max-width: 768px) {
|
|
|
+ padding: $spacing-sm $spacing-lg;
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ right: $spacing-lg;
|
|
|
+ bottom: $spacing-lg;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 移动端优化 ====================
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .share-page {
|
|
|
+ padding: $spacing-lg $spacing-md;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 卡片通用优化
|
|
|
+ .card {
|
|
|
+ padding: $spacing-md;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 内容头部优化
|
|
|
+ .text-content,
|
|
|
+ .files-content {
|
|
|
+ .content-header {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: $spacing-sm;
|
|
|
+ margin-bottom: $spacing-md;
|
|
|
+
|
|
|
+ h2 {
|
|
|
+ font-size: $font-size-lg;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-actions {
|
|
|
+ width: 100%;
|
|
|
+ justify-content: space-between;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 文件列表优化
|
|
|
+ .file-item {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: stretch;
|
|
|
+ gap: $spacing-sm;
|
|
|
+ padding: $spacing-sm;
|
|
|
+
|
|
|
+ .file-info {
|
|
|
+ gap: $spacing-sm;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-icon {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-details {
|
|
|
+ .file-name {
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-size {
|
|
|
+ font-size: $font-size-xs;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-actions {
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: $spacing-xs;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按钮优化
|
|
|
+ .btn-action {
|
|
|
+ padding: $spacing-xs $spacing-sm;
|
|
|
+ font-size: $font-size-xs;
|
|
|
+
|
|
|
+ svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ // 在极小屏幕上隐藏文字,只显示图标
|
|
|
+ @media (max-width: 480px) {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除按钮移动端优化
|
|
|
+ .btn-delete {
|
|
|
+ padding: $spacing-xs $spacing-sm;
|
|
|
+ font-size: $font-size-xs;
|
|
|
+
|
|
|
+ svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ // 在极小屏幕上隐藏文字,只显示图标
|
|
|
+ @media (max-width: 480px) {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打包下载按钮优化
|
|
|
+ .btn-download-all {
|
|
|
+ padding: $spacing-sm $spacing-md;
|
|
|
+ font-size: $font-size-base;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 文本显示区优化
|
|
|
+ .text-display {
|
|
|
+ max-height: 400px;
|
|
|
+ padding: $spacing-md;
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 内容信息优化
|
|
|
+ .content-info {
|
|
|
+ p {
|
|
|
+ font-size: $font-size-xs;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 错误容器优化
|
|
|
+ .error-container {
|
|
|
+ h2 {
|
|
|
+ font-size: $font-size-xl;
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载容器优化
|
|
|
+ .loading-container {
|
|
|
+ padding: $spacing-xl;
|
|
|
+
|
|
|
+ .spinner {
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ border-width: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ font-size: $font-size-sm;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 密码容器优化
|
|
|
+ .password-container {
|
|
|
+ padding: $spacing-lg;
|
|
|
+ max-width: 100%;
|
|
|
+
|
|
|
+ h2 {
|
|
|
+ font-size: $font-size-xl;
|
|
|
+ }
|
|
|
+
|
|
|
+ .password-input {
|
|
|
+ font-size: $font-size-base;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 左右分栏在移动端的额外优化
|
|
|
+ .content-split {
|
|
|
+ gap: $spacing-md;
|
|
|
+ }
|
|
|
+
|
|
|
+ .left-column,
|
|
|
+ .right-column {
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 超小屏幕优化(<480px)
|
|
|
+@media (max-width: 480px) {
|
|
|
+ .share-page {
|
|
|
+ padding: $spacing-md $spacing-sm;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-actions {
|
|
|
+ .btn-action {
|
|
|
+ padding: $spacing-xs;
|
|
|
+ min-width: 36px; // 确保按钮可点击区域足够
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ span {
|
|
|
+ display: none; // 只显示图标
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .text-display {
|
|
|
+ font-size: $font-size-xs;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-header h2 {
|
|
|
+ font-size: $font-size-base;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|