This commit is contained in:
+39
-38
@@ -189,6 +189,7 @@ export default {
|
|||||||
description: 'Supports various common formats'
|
description: 'Supports various common formats'
|
||||||
},
|
},
|
||||||
submit: 'Secure Send',
|
submit: 'Secure Send',
|
||||||
|
submitting: 'Sending...',
|
||||||
needRetrieveFile: 'Need to retrieve? Click here',
|
needRetrieveFile: 'Need to retrieve? Click here',
|
||||||
sendRecords: 'Send Records',
|
sendRecords: 'Send Records',
|
||||||
secureEncryption: 'Secure Encryption',
|
secureEncryption: 'Secure Encryption',
|
||||||
@@ -217,7 +218,7 @@ export default {
|
|||||||
forever: 'Forever'
|
forever: 'Forever'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
day: 'By Day',
|
day: 'By Day',
|
||||||
hour: 'By Hour',
|
hour: 'By Hour',
|
||||||
minute: 'By Minute',
|
minute: 'By Minute',
|
||||||
@@ -311,7 +312,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Side drawer
|
// Side drawer
|
||||||
drawer: {
|
drawer: {
|
||||||
noRecords: 'No records'
|
noRecords: 'No records'
|
||||||
},
|
},
|
||||||
@@ -466,42 +467,42 @@ export default {
|
|||||||
version: 'Version v2.2.1 Updated: 2025-09-04'
|
version: 'Version v2.2.1 Updated: 2025-09-04'
|
||||||
},
|
},
|
||||||
fileManage: {
|
fileManage: {
|
||||||
title: 'File Management',
|
title: 'File Management',
|
||||||
searchPlaceholder: 'Search file name, description...',
|
searchPlaceholder: 'Search file name, description...',
|
||||||
allFiles: 'All Files',
|
allFiles: 'All Files',
|
||||||
editFileInfo: 'Edit File Info',
|
editFileInfo: 'Edit File Info',
|
||||||
saveChanges: 'Save Changes',
|
saveChanges: 'Save Changes',
|
||||||
headers: {
|
headers: {
|
||||||
code: 'Code',
|
code: 'Code',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
size: 'Size',
|
size: 'Size',
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
expiration: 'Expiration',
|
expiration: 'Expiration',
|
||||||
actions: 'Actions'
|
actions: 'Actions'
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
code: 'Code',
|
code: 'Code',
|
||||||
codePlaceholder: 'Enter code',
|
codePlaceholder: 'Enter code',
|
||||||
filename: 'File Name',
|
filename: 'File Name',
|
||||||
filenamePlaceholder: 'Enter file name',
|
filenamePlaceholder: 'Enter file name',
|
||||||
suffix: 'File Suffix',
|
suffix: 'File Suffix',
|
||||||
suffixPlaceholder: 'Enter file suffix',
|
suffixPlaceholder: 'Enter file suffix',
|
||||||
downloadLimit: 'Download Limit',
|
downloadLimit: 'Download Limit',
|
||||||
downloadLimitPlaceholder: 'Enter download limit'
|
downloadLimitPlaceholder: 'Enter download limit'
|
||||||
},
|
},
|
||||||
updateFailed: 'Update failed',
|
updateFailed: 'Update failed',
|
||||||
deleteFailed: 'Delete failed',
|
deleteFailed: 'Delete failed',
|
||||||
loadFileListFailed: 'Failed to load file list'
|
loadFileListFailed: 'Failed to load file list'
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
title: 'Login',
|
title: 'Login',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
passwordPlaceholder: 'Password',
|
passwordPlaceholder: 'Password',
|
||||||
loginButton: 'Login',
|
loginButton: 'Login',
|
||||||
loggingIn: 'Logging in...',
|
loggingIn: 'Logging in...',
|
||||||
invalidPassword: 'Invalid password',
|
invalidPassword: 'Invalid password',
|
||||||
passwordTooShort: 'Password must be at least 6 characters',
|
passwordTooShort: 'Password must be at least 6 characters',
|
||||||
loginFailed: 'Login failed',
|
loginFailed: 'Login failed',
|
||||||
noValidToken: 'Login failed: No valid token received'
|
noValidToken: 'Login failed: No valid token received'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+26
-25
@@ -190,6 +190,7 @@ export default {
|
|||||||
description: '支持各种常见格式'
|
description: '支持各种常见格式'
|
||||||
},
|
},
|
||||||
submit: '安全寄送',
|
submit: '安全寄送',
|
||||||
|
submitting: '发送中...',
|
||||||
needRetrieveFile: '需要取件?点击这里',
|
needRetrieveFile: '需要取件?点击这里',
|
||||||
sendRecords: '发件记录',
|
sendRecords: '发件记录',
|
||||||
secureEncryption: '安全加密',
|
secureEncryption: '安全加密',
|
||||||
@@ -311,35 +312,35 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 文件记录
|
// 文件记录
|
||||||
fileRecord: {
|
fileRecord: {
|
||||||
filename: '文件名',
|
filename: '文件名',
|
||||||
size: '文件大小',
|
size: '文件大小',
|
||||||
date: '取件日期',
|
date: '取件日期',
|
||||||
code: '取件码',
|
code: '取件码',
|
||||||
actions: '操作',
|
actions: '操作',
|
||||||
download: '下载',
|
download: '下载',
|
||||||
viewDetails: '查看详情',
|
viewDetails: '查看详情',
|
||||||
deleteRecord: '删除记录',
|
deleteRecord: '删除记录',
|
||||||
preview: '预览',
|
preview: '预览',
|
||||||
copyContent: '复制内容',
|
copyContent: '复制内容',
|
||||||
contentCopied: '内容已复制到剪贴板',
|
contentCopied: '内容已复制到剪贴板',
|
||||||
copyFailed: '复制失败,请重试'
|
copyFailed: '复制失败,请重试'
|
||||||
},
|
},
|
||||||
|
|
||||||
// 文件详情模态框
|
// 文件详情模态框
|
||||||
fileDetail: {
|
fileDetail: {
|
||||||
title: '文件详情',
|
title: '文件详情',
|
||||||
content: '文件内容',
|
content: '文件内容',
|
||||||
previewContent: '预览内容',
|
previewContent: '预览内容',
|
||||||
download: '点击下载',
|
download: '点击下载',
|
||||||
qrCode: '取件二维码',
|
qrCode: '取件二维码',
|
||||||
scanQrCode: '扫描二维码快速取件'
|
scanQrCode: '扫描二维码快速取件'
|
||||||
},
|
},
|
||||||
|
|
||||||
// 内容预览
|
// 内容预览
|
||||||
contentPreview: {
|
contentPreview: {
|
||||||
title: '内容预览'
|
title: '内容预览'
|
||||||
},
|
},
|
||||||
|
|
||||||
// 侧边抽屉
|
// 侧边抽屉
|
||||||
drawer: {
|
drawer: {
|
||||||
|
|||||||
+75
-42
@@ -30,10 +30,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else key="text" class="grid grid-cols-1 gap-8">
|
<div v-else key="text" class="grid grid-cols-1 gap-8">
|
||||||
<TextInputArea
|
<TextInputArea v-model="textContent" :placeholder="t('send.uploadArea.textInput')" />
|
||||||
v-model="textContent"
|
|
||||||
:placeholder="t('send.uploadArea.textInput')"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<!-- 过期方式选择 -->
|
<!-- 过期方式选择 -->
|
||||||
@@ -115,7 +112,9 @@
|
|||||||
:class="[
|
:class="[
|
||||||
'absolute right-0 top-0 h-full appearance-none cursor-pointer transition-all duration-300',
|
'absolute right-0 top-0 h-full appearance-none cursor-pointer transition-all duration-300',
|
||||||
'focus:outline-none focus:ring-2 focus:ring-offset-0',
|
'focus:outline-none focus:ring-2 focus:ring-offset-0',
|
||||||
expirationMethod === 'forever' ? 'w-full px-5 rounded-2xl' : 'w-28 pl-4 pr-9 border-l rounded-r-2xl',
|
expirationMethod === 'forever'
|
||||||
|
? 'w-full px-5 rounded-2xl'
|
||||||
|
: 'w-28 pl-4 pr-9 border-l rounded-r-2xl',
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'text-gray-100 border-gray-700/60 focus:ring-indigo-500/80 bg-gray-800/60'
|
? 'text-gray-100 border-gray-700/60 focus:ring-indigo-500/80 bg-gray-800/60'
|
||||||
: 'text-gray-900 border-gray-200 focus:ring-indigo-500/60 bg-white'
|
: 'text-gray-900 border-gray-200 focus:ring-indigo-500/60 bg-white'
|
||||||
@@ -166,14 +165,36 @@
|
|||||||
<!-- 提交按钮 -->
|
<!-- 提交按钮 -->
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="w-full bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 text-white font-bold py-4 px-6 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-opacity-50 transition-all duration-300 transform hover:scale-105 hover:shadow-lg relative overflow-hidden group"
|
:disabled="isSubmitting"
|
||||||
|
class="w-full bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 text-white font-bold py-4 px-6 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-opacity-50 transition-all duration-300 transform hover:scale-105 hover:shadow-lg relative overflow-hidden group disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:hover:scale-100"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="absolute top-0 left-0 w-full h-full bg-white opacity-0 group-hover:opacity-20 transition-opacity duration-300"
|
class="absolute top-0 left-0 w-full h-full bg-white opacity-0 group-hover:opacity-20 transition-opacity duration-300"
|
||||||
></span>
|
></span>
|
||||||
<span class="relative z-10 flex items-center justify-center text-lg">
|
<span class="relative z-10 flex items-center justify-center text-lg">
|
||||||
<SendIcon class="w-6 h-6 mr-2" />
|
<svg
|
||||||
<span>{{ t('send.submit') }}</span>
|
v-if="isSubmitting"
|
||||||
|
class="w-6 h-6 mr-2 animate-spin"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
class="opacity-25"
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="10"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="4"
|
||||||
|
></circle>
|
||||||
|
<path
|
||||||
|
class="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<SendIcon v-else class="w-6 h-6 mr-2" />
|
||||||
|
<span>{{ isSubmitting ? t('send.submitting') : t('send.submit') }}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -532,8 +553,6 @@ interface ShareRecord {
|
|||||||
retrieveCode: string
|
retrieveCode: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const config: Config = JSON.parse(localStorage.getItem('config') || '{}') as Config
|
const config: Config = JSON.parse(localStorage.getItem('config') || '{}') as Config
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -549,6 +568,7 @@ const expirationValue = ref('1')
|
|||||||
const uploadProgress = ref(0)
|
const uploadProgress = ref(0)
|
||||||
const showDrawer = ref(false)
|
const showDrawer = ref(false)
|
||||||
const selectedRecord = ref<ShareRecord | null>(null)
|
const selectedRecord = ref<ShareRecord | null>(null)
|
||||||
|
const isSubmitting = ref(false)
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const alertStore = useAlertStore()
|
const alertStore = useAlertStore()
|
||||||
@@ -564,8 +584,6 @@ const {
|
|||||||
|
|
||||||
const fileHash = ref('')
|
const fileHash = ref('')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleFileSelected = async (file: File) => {
|
const handleFileSelected = async (file: File) => {
|
||||||
selectedFile.value = file
|
selectedFile.value = file
|
||||||
if (!checkOpenUpload()) return
|
if (!checkOpenUpload()) return
|
||||||
@@ -573,8 +591,6 @@ const handleFileSelected = async (file: File) => {
|
|||||||
fileHash.value = await calculateFileHash(file)
|
fileHash.value = await calculateFileHash(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleFileDrop = async (event: DragEvent) => {
|
const handleFileDrop = async (event: DragEvent) => {
|
||||||
if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
|
if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
|
||||||
const file = event.dataTransfer.files[0]
|
const file = event.dataTransfer.files[0]
|
||||||
@@ -604,7 +620,10 @@ const handlePaste = async (event: ClipboardEvent) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
fileHash.value = await calculateFileHash(file)
|
fileHash.value = await calculateFileHash(file)
|
||||||
alertStore.showAlert(t('send.messages.fileAddedFromClipboard', { filename: file.name }), 'success')
|
alertStore.showAlert(
|
||||||
|
t('send.messages.fileAddedFromClipboard', { filename: file.name }),
|
||||||
|
'success'
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alertStore.showAlert(t('send.messages.fileProcessingFailed'), 'error')
|
alertStore.showAlert(t('send.messages.fileProcessingFailed'), 'error')
|
||||||
console.error('File hash calculation failed:', err)
|
console.error('File hash calculation failed:', err)
|
||||||
@@ -860,10 +879,13 @@ const handleChunkUpload = async (file: File) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 完成上传
|
// 3. 完成上传
|
||||||
const completeResponse: ApiResponse<{ code?: string; name?: string }> = await api.post(`chunk/upload/complete/${uploadId}`, {
|
const completeResponse: ApiResponse<{ code?: string; name?: string }> = await api.post(
|
||||||
expire_value: expirationValue.value ? parseInt(expirationValue.value) : 1,
|
`chunk/upload/complete/${uploadId}`,
|
||||||
expire_style: expirationMethod.value
|
{
|
||||||
})
|
expire_value: expirationValue.value ? parseInt(expirationValue.value) : 1,
|
||||||
|
expire_style: expirationMethod.value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (completeResponse.code !== 200) {
|
if (completeResponse.code !== 200) {
|
||||||
throw new Error(t('send.messages.completeUploadFailed'))
|
throw new Error(t('send.messages.completeUploadFailed'))
|
||||||
@@ -898,7 +920,11 @@ const handleDefaultFileUpload = async (file: File) => {
|
|||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
formData.append('expire_value', expirationValue.value)
|
formData.append('expire_value', expirationValue.value)
|
||||||
formData.append('expire_style', expirationMethod.value)
|
formData.append('expire_style', expirationMethod.value)
|
||||||
const response: ApiResponse<{ code?: string; name?: string }> = await api.post('share/file/', formData, config)
|
const response: ApiResponse<{ code?: string; name?: string }> = await api.post(
|
||||||
|
'share/file/',
|
||||||
|
formData,
|
||||||
|
config
|
||||||
|
)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -933,7 +959,10 @@ const checkOpenUpload = () => {
|
|||||||
|
|
||||||
const checkFileSize = (file: File) => {
|
const checkFileSize = (file: File) => {
|
||||||
if (file.size > config.uploadSize) {
|
if (file.size > config.uploadSize) {
|
||||||
alertStore.showAlert(t('send.messages.fileSizeExceeded', { size: getStorageUnit(config.uploadSize) }), 'error')
|
alertStore.showAlert(
|
||||||
|
t('send.messages.fileSizeExceeded', { size: getStorageUnit(config.uploadSize) }),
|
||||||
|
'error'
|
||||||
|
)
|
||||||
selectedFile.value = null
|
selectedFile.value = null
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -971,27 +1000,30 @@ const checkUpload = () => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (sendType.value === 'file' && !selectedFile.value) {
|
if (isSubmitting.value) return
|
||||||
alertStore.showAlert(t('send.messages.selectFile'), 'error')
|
isSubmitting.value = true
|
||||||
return
|
|
||||||
}
|
|
||||||
if (sendType.value === 'text' && !textContent.value.trim()) {
|
|
||||||
alertStore.showAlert(t('send.messages.enterText'), 'error')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (expirationMethod.value !== 'forever' && !expirationValue.value) {
|
|
||||||
alertStore.showAlert(t('send.messages.enterExpirationValue'), 'error')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加过期时间验证
|
|
||||||
if (!checkExpirationTime(expirationMethod.value, expirationValue.value)) {
|
|
||||||
const maxDays = Math.floor(config.max_save_seconds / 86400)
|
|
||||||
alertStore.showAlert(t('send.messages.expirationTooLong', { days: maxDays }), 'error')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (sendType.value === 'file' && !selectedFile.value) {
|
||||||
|
alertStore.showAlert(t('send.messages.selectFile'), 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (sendType.value === 'text' && !textContent.value.trim()) {
|
||||||
|
alertStore.showAlert(t('send.messages.enterText'), 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (expirationMethod.value !== 'forever' && !expirationValue.value) {
|
||||||
|
alertStore.showAlert(t('send.messages.enterExpirationValue'), 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加过期时间验证
|
||||||
|
if (!checkExpirationTime(expirationMethod.value, expirationValue.value)) {
|
||||||
|
const maxDays = Math.floor(config.max_save_seconds / 86400)
|
||||||
|
alertStore.showAlert(t('send.messages.expirationTooLong', { days: maxDays }), 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let response: ApiResponse
|
let response: ApiResponse
|
||||||
|
|
||||||
if (sendType.value === 'file') {
|
if (sendType.value === 'file') {
|
||||||
@@ -1020,8 +1052,8 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response && response.code === 200) {
|
if (response && response.code === 200) {
|
||||||
const retrieveCode = (response.detail as unknown as { code?: string })?.code || ''
|
const retrieveCode = (response.detail as unknown as { code?: string })?.code || ''
|
||||||
const fileName = (response.detail as unknown as { name?: string })?.name || ''
|
const fileName = (response.detail as unknown as { name?: string })?.name || ''
|
||||||
// 添加新的发送记录
|
// 添加新的发送记录
|
||||||
const newRecord: ShareRecord = {
|
const newRecord: ShareRecord = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
@@ -1064,6 +1096,7 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
uploadProgress.value = 0
|
uploadProgress.value = 0
|
||||||
|
isSubmitting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -3,11 +3,10 @@ import { fileURLToPath, URL } from 'node:url'
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
|
||||||
import tailwindcss from 'tailwindcss'
|
import tailwindcss from 'tailwindcss'
|
||||||
import autoprefixer from 'autoprefixer'
|
import autoprefixer from 'autoprefixer'
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue(), vueJsx(), vueDevTools()],
|
plugins: [vue(), vueJsx()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
|||||||
Reference in New Issue
Block a user