Lan
2025-03-15 23:43:24 +08:00
parent 10193ff17a
commit 77101aa3d1
+75 -15
View File
@@ -1,5 +1,6 @@
<template>
<div class="min-h-screen flex items-center justify-center p-4 overflow-hidden transition-colors duration-300">
<div class="min-h-screen flex items-center justify-center p-4 overflow-hidden transition-colors duration-300"
@paste.prevent="handlePaste">
<div class="rounded-3xl shadow-2xl overflow-hidden border w-full max-w-md transition-colors duration-300" :class="[
isDarkMode
? 'bg-white bg-opacity-10 backdrop-filter backdrop-blur-xl border-gray-700'
@@ -41,7 +42,7 @@
</div>
<transition name="fade" mode="out-in">
<div v-if="sendType === 'file'" key="file" class="grid grid-cols-1 gap-8" @paste.prevent="handlePaste">
<div v-if="sendType === 'file'" key="file" class="grid grid-cols-1 gap-8">
<!-- 文件上传区域 -->
<div
class="rounded-xl p-8 flex flex-col items-center justify-center border-2 border-dashed transition-all duration-300 group cursor-pointer relative"
@@ -214,13 +215,13 @@
<!-- 记录详情弹窗 -->
<transition name="fade">
<div v-if="selectedRecord" class="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-3 sm:p-4 overflow-y-auto">
<div v-if="selectedRecord"
class="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-3 sm:p-4 overflow-y-auto">
<div
class="w-full max-w-2xl rounded-2xl shadow-2xl transform transition-all duration-300 ease-out overflow-hidden"
:class="[isDarkMode ? 'bg-gray-900 bg-opacity-70' : 'bg-white bg-opacity-95']">
<!-- 顶部标题栏 -->
<div class="px-4 sm:px-6 py-3 sm:py-4 border-b"
:class="[isDarkMode ? 'border-gray-800' : 'border-gray-100']">
<div class="px-4 sm:px-6 py-3 sm:py-4 border-b" :class="[isDarkMode ? 'border-gray-800' : 'border-gray-100']">
<div class="flex items-center justify-between">
<h3 class="text-lg sm:text-xl font-semibold" :class="[isDarkMode ? 'text-white' : 'text-gray-900']">
文件详情
@@ -239,7 +240,8 @@
:class="[isDarkMode ? 'bg-gray-800 bg-opacity-50' : 'bg-gray-50 bg-opacity-95']">
<div class="flex items-center mb-3 sm:mb-4">
<div class="p-2 sm:p-3 rounded-lg" :class="[isDarkMode ? 'bg-gray-800' : 'bg-white']">
<FileIcon class="w-5 h-5 sm:w-6 sm:h-6" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
<FileIcon class="w-5 h-5 sm:w-6 sm:h-6"
:class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
</div>
<div class="ml-3 sm:ml-4">
<h4 class="font-medium text-sm sm:text-base" :class="[isDarkMode ? 'text-white' : 'text-gray-900']">
@@ -252,13 +254,15 @@
</div>
<div class="grid grid-cols-2 gap-3 sm:gap-4">
<div class="flex items-center">
<ClockIcon class="w-3.5 h-3.5 sm:w-4 sm:h-4 mr-1.5 sm:mr-2" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']" />
<ClockIcon class="w-3.5 h-3.5 sm:w-4 sm:h-4 mr-1.5 sm:mr-2"
:class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']" />
<span class="text-xs sm:text-sm" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-600']">
{{ selectedRecord.expiration }}
</span>
</div>
<div class="flex items-center">
<ShieldCheckIcon class="w-3.5 h-3.5 sm:w-4 sm:h-4 mr-1.5 sm:mr-2" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']" />
<ShieldCheckIcon class="w-3.5 h-3.5 sm:w-4 sm:h-4 mr-1.5 sm:mr-2"
:class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']" />
<span class="text-xs sm:text-sm" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-600']">
安全加密
</span>
@@ -278,22 +282,26 @@
<ClipboardCopyIcon class="w-4 h-4 sm:w-5 sm:h-5" />
</button>
</div>
<p class="text-2xl sm:text-3xl font-bold tracking-wider text-center">{{ selectedRecord.retrieveCode }}</p>
<p class="text-2xl sm:text-3xl font-bold tracking-wider text-center">{{ selectedRecord.retrieveCode }}
</p>
</div>
<div class="rounded-xl p-3 sm:p-4"
:class="[isDarkMode ? 'bg-gray-800 bg-opacity-50' : 'bg-gray-50 bg-opacity-95']">
<div class="flex items-center justify-between mb-2 sm:mb-3">
<h4 class="font-medium text-sm sm:text-base flex items-center" :class="[isDarkMode ? 'text-white' : 'text-gray-900']">
<h4 class="font-medium text-sm sm:text-base flex items-center"
:class="[isDarkMode ? 'text-white' : 'text-gray-900']">
<TerminalIcon class="w-4 h-4 sm:w-5 sm:h-5 mr-1.5 sm:mr-2 text-indigo-500" />
wget下载
</h4>
<button @click="copyWgetCommand(selectedRecord.retrieveCode)"
class="p-1.5 sm:p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
<ClipboardCopyIcon class="w-4 h-4 sm:w-5 sm:h-5" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']" />
<ClipboardCopyIcon class="w-4 h-4 sm:w-5 sm:h-5"
:class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']" />
</button>
</div>
<p class="text-xs sm:text-sm font-mono break-all" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-600']">
<p class="text-xs sm:text-sm font-mono break-all"
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-600']">
点击复制wget命令
</p>
</div>
@@ -303,7 +311,8 @@
<div class="rounded-xl p-4 sm:p-5 flex flex-col items-center"
:class="[isDarkMode ? 'bg-gray-800 bg-opacity-50' : 'bg-gray-50 bg-opacity-95']">
<div class="bg-white p-3 sm:p-4 rounded-lg shadow-sm mb-3 sm:mb-4">
<QRCode :value="getQRCodeValue(selectedRecord)" :size="140" level="M" class="sm:w-[160px] sm:h-[160px]" />
<QRCode :value="getQRCodeValue(selectedRecord)" :size="140" level="M"
class="sm:w-[160px] sm:h-[160px]" />
</div>
<p class="text-xs sm:text-sm" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']">
扫描二维码快速取件
@@ -397,17 +406,41 @@ const handleFileDrop = async (event: DragEvent) => {
const handlePaste = async (event: ClipboardEvent) => {
const items = event.clipboardData?.items
if (!items) return
for (const item of items) {
if (item.kind === 'file') {
const file = item.getAsFile()
if (file) {
// 检查文件是否为空
if (file.size === 0) {
alertStore.showAlert('无法读取空文件', 'error')
return
}
// 检查文件类型
if (file.type.startsWith('image/')) {
selectedFile.value = file
if (!checkUpload()) return
try {
fileHash.value = await calculateFileHash(file)
alertStore.showAlert('已从剪贴板添加文件' + file.name, 'success')
alertStore.showAlert('已从剪贴板添加图片' + file.name, 'success')
} catch (error) {
alertStore.showAlert('文件处理失败', 'error')
console.error('File hash calculation failed:', error)
}
} else {
alertStore.showAlert('目前仅支持粘贴图片文件', 'warning')
}
break
}
}
else {
sendType.value = 'text'
items[0].getAsString((str: string) => {
textContent.value += str
})
}
}
}
@@ -420,17 +453,30 @@ const calculateFileHash = async (file: File): Promise<string> => {
fileReader.onload = async (e) => {
const chunk = new Uint8Array(e.target!.result as ArrayBuffer)
try {
// 尝试使用 crypto.subtle.digest
if (window.isSecureContext) {
const hashBuffer = await crypto.subtle.digest('SHA-256', chunk)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
currentChunk++
if (currentChunk < chunks) {
loadNext()
} else {
resolve(hashHex)
}
} else {
// 如果不是安全上下文(HTTP),则返回一个基于文件信息的替代哈希
const fallbackHash = generateFallbackHash(file)
resolve(fallbackHash)
}
} catch (error) {
// 如果 crypto.subtle.digest 失败,使用替代方案
const fallbackHash = generateFallbackHash(file)
resolve(fallbackHash)
}
}
const loadNext = () => {
@@ -443,6 +489,20 @@ const calculateFileHash = async (file: File): Promise<string> => {
})
}
// 生成替代哈希的函数
const generateFallbackHash = (file: File): string => {
// 使用文件名、大小和最后修改时间生成一个简单的哈希
const fileInfo = `${file.name}-${file.size}-${file.lastModified}`
let hash = 0
for (let i = 0; i < fileInfo.length; i++) {
const char = fileInfo.charCodeAt(i)
hash = ((hash << 5) - hash) + char
hash = hash & hash // Convert to 32-bit integer
}
// 转换为16进制字符串并填充到64位
return Math.abs(hash).toString(16).padStart(64, '0')
}
const getPlaceholder = (value: string = expirationMethod.value) => {
switch (value) {
case 'day':