feat: 多语言支持 https://github.com/vastsa/FileCodeBox/issues/310 https://github.com/vastsa/FileCodeBox/issues/299
This commit is contained in:
@@ -22,15 +22,15 @@
|
||||
/>
|
||||
</div>
|
||||
<PageFooter
|
||||
link-text="需要发送文件?点击这里"
|
||||
:link-text="$t('retrieve.needSendFile')"
|
||||
link-to="/send"
|
||||
drawer-text="取件记录"
|
||||
:drawer-text="$t('retrieve.recordsDrawer')"
|
||||
@toggle-drawer="toggleDrawer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SideDrawer :visible="showDrawer" title="取件记录" @close="toggleDrawer">
|
||||
<SideDrawer :visible="showDrawer" :title="$t('retrieve.recordsDrawer')" @close="toggleDrawer">
|
||||
<FileRecordList
|
||||
:records="records"
|
||||
@view-details="viewDetails"
|
||||
@@ -59,6 +59,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, onMounted, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PageHeader from '@/components/common/PageHeader.vue'
|
||||
import RetrieveForm from '@/components/common/RetrieveForm.vue'
|
||||
import PageFooter from '@/components/common/PageFooter.vue'
|
||||
@@ -101,6 +102,7 @@ import DOMPurify from 'dompurify'
|
||||
import { useAlertStore } from '@/stores/alertStore'
|
||||
import { copyToClipboard } from '@/utils/clipboard'
|
||||
|
||||
const { t } = useI18n()
|
||||
const alertStore = useAlertStore()
|
||||
const baseUrl = window.location.origin
|
||||
|
||||
@@ -153,14 +155,14 @@ const getDownloadUrl = (record: FileRecord) => {
|
||||
const copyContent = async () => {
|
||||
if (selectedRecord.value && selectedRecord.value.content) {
|
||||
await copyToClipboard(selectedRecord.value.content, {
|
||||
successMsg: '内容已复制到剪贴板',
|
||||
errorMsg: '复制失败,请重试'
|
||||
successMsg: t('fileRecord.contentCopied'),
|
||||
errorMsg: t('fileRecord.copyFailed')
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleSubmit = async () => {
|
||||
if (code.value.length !== 5) {
|
||||
alertStore.showAlert('请输入5位取件码', 'error')
|
||||
alertStore.showAlert(t('retrieve.messages.invalidCode'), 'error')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -200,18 +202,18 @@ const handleSubmit = async () => {
|
||||
selectedRecord.value = newFileData
|
||||
showPreview.value = true
|
||||
}
|
||||
alertStore.showAlert('文件获取成功', 'success')
|
||||
alertStore.showAlert(t('retrieve.messages.retrieveSuccess'), 'success')
|
||||
} else {
|
||||
alertStore.showAlert('无效的取件码', 'error')
|
||||
alertStore.showAlert(t('retrieve.messages.invalidCodeError'), 'error')
|
||||
}
|
||||
} else {
|
||||
alertStore.showAlert('获取文件失败:' + res.detail, 'error')
|
||||
alertStore.showAlert(t('retrieve.messages.retrieveFailure') + res.detail, 'error')
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
console.error('取件失败:', err)
|
||||
console.error('Retrieve failed:', err)
|
||||
const error = err as { response?: { data?: { detail?: string } }; message?: string }
|
||||
const errorMessage = error?.response?.data?.detail || error?.message || '未知错误'
|
||||
alertStore.showAlert('取件失败,请稍后重试:' + errorMessage, 'error')
|
||||
const errorMessage = error?.response?.data?.detail || error?.message || t('retrieve.messages.unknownError')
|
||||
alertStore.showAlert(t('retrieve.messages.networkError') + errorMessage, 'error')
|
||||
} finally {
|
||||
inputStatus.value.readonly = false
|
||||
inputStatus.value.loading = false
|
||||
@@ -220,9 +222,9 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (bytes === 0) return '0 Bytes'
|
||||
if (bytes === 0) return '0 ' + t('fileSize.bytes')
|
||||
const k = 1024
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
|
||||
const sizes = [t('fileSize.bytes'), t('fileSize.kb'), t('fileSize.mb'), t('fileSize.gb'), t('fileSize.tb')]
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
+42
-40
@@ -32,14 +32,14 @@
|
||||
<div v-else key="text" class="grid grid-cols-1 gap-8">
|
||||
<TextInputArea
|
||||
v-model="textContent"
|
||||
placeholder="在此输入要发送的文本..."
|
||||
:placeholder="t('send.uploadArea.textInput')"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
<!-- 过期方式选择 -->
|
||||
<div class="flex flex-col space-y-3">
|
||||
<label :class="['text-sm font-medium', isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
过期时间
|
||||
{{ t('send.expiration.label') }}
|
||||
</label>
|
||||
<div class="relative flex-grow group">
|
||||
<div
|
||||
@@ -173,13 +173,13 @@
|
||||
></span>
|
||||
<span class="relative z-10 flex items-center justify-center text-lg">
|
||||
<SendIcon class="w-6 h-6 mr-2" />
|
||||
<span>安全寄送</span>
|
||||
<span>{{ t('send.submit') }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
<div class="mt-6 text-center">
|
||||
<router-link to="/" class="text-indigo-400 hover:text-indigo-300 transition duration-300">
|
||||
需要取件?点击这里
|
||||
{{ t('send.needRetrieveFile') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -193,14 +193,14 @@
|
||||
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-800']"
|
||||
>
|
||||
<ShieldCheckIcon class="w-4 h-4 mr-1 text-green-400" />
|
||||
安全加密
|
||||
{{ t('send.secureEncryption') }}
|
||||
</span>
|
||||
<button
|
||||
@click="toggleDrawer"
|
||||
class="text-sm hover:text-indigo-300 transition duration-300 flex items-center"
|
||||
:class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']"
|
||||
>
|
||||
发件记录
|
||||
{{ t('send.sendRecords') }}
|
||||
<ClipboardListIcon class="w-4 h-4 ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -218,7 +218,7 @@
|
||||
:class="[isDarkMode ? 'border-gray-700' : 'border-gray-200']"
|
||||
>
|
||||
<h3 class="text-2xl font-bold" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||
发件记录
|
||||
{{ t('send.sendRecords') }}
|
||||
</h3>
|
||||
<button
|
||||
@click="toggleDrawer"
|
||||
@@ -315,7 +315,7 @@
|
||||
class="text-lg sm:text-xl font-semibold"
|
||||
:class="[isDarkMode ? 'text-white' : 'text-gray-900']"
|
||||
>
|
||||
文件详情
|
||||
{{ t('send.fileDetails') }}
|
||||
</h3>
|
||||
<button
|
||||
@click="selectedRecord = null"
|
||||
@@ -486,6 +486,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, onMounted, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
SendIcon,
|
||||
ClipboardListIcon,
|
||||
@@ -547,6 +548,7 @@ const uploadProgress = ref(0)
|
||||
const showDrawer = ref(false)
|
||||
const selectedRecord = ref<ShareRecord | null>(null)
|
||||
|
||||
const { t } = useI18n()
|
||||
const alertStore = useAlertStore()
|
||||
const sendRecords = computed(() => fileDataStore.shareData)
|
||||
|
||||
@@ -580,7 +582,7 @@ const handlePaste = async (event: ClipboardEvent) => {
|
||||
if (file) {
|
||||
// 检查文件是否为空
|
||||
if (file.size === 0) {
|
||||
alertStore.showAlert('无法读取空文件', 'error')
|
||||
alertStore.showAlert(t('send.messages.emptyFileError'), 'error')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -589,9 +591,9 @@ const handlePaste = async (event: ClipboardEvent) => {
|
||||
|
||||
try {
|
||||
fileHash.value = await calculateFileHash(file)
|
||||
alertStore.showAlert('已从剪贴板添加文件:' + file.name, 'success')
|
||||
alertStore.showAlert(t('send.messages.fileAddedFromClipboard', { filename: file.name }), 'success')
|
||||
} catch (err) {
|
||||
alertStore.showAlert('文件处理失败', 'error')
|
||||
alertStore.showAlert(t('send.messages.fileProcessingFailed'), 'error')
|
||||
console.error('File hash calculation failed:', err)
|
||||
}
|
||||
break
|
||||
@@ -708,32 +710,32 @@ const generateFallbackHash = (file: File): string => {
|
||||
const getPlaceholder = (value: string = expirationMethod.value) => {
|
||||
switch (value) {
|
||||
case 'day':
|
||||
return '输入天数'
|
||||
return t('send.expiration.placeholders.days')
|
||||
case 'hour':
|
||||
return '输入小时数'
|
||||
return t('send.expiration.placeholders.hours')
|
||||
case 'minute':
|
||||
return '输入分钟数'
|
||||
return t('send.expiration.placeholders.minutes')
|
||||
case 'count':
|
||||
return '输入查看次数'
|
||||
return t('send.expiration.placeholders.count')
|
||||
case 'forever':
|
||||
return '永久'
|
||||
return t('send.expiration.placeholders.forever')
|
||||
default:
|
||||
return '输入值'
|
||||
return t('send.expiration.placeholders.default')
|
||||
}
|
||||
}
|
||||
|
||||
const getUnit = (value: string = expirationMethod.value) => {
|
||||
switch (value) {
|
||||
case 'day':
|
||||
return '天'
|
||||
return t('send.expiration.units.days')
|
||||
case 'hour':
|
||||
return '小时'
|
||||
return t('send.expiration.units.hours')
|
||||
case 'minute':
|
||||
return '分钟'
|
||||
return t('send.expiration.units.minutes')
|
||||
case 'count':
|
||||
return '次'
|
||||
return t('send.expiration.units.times')
|
||||
case 'forever':
|
||||
return '永久'
|
||||
return t('send.expiration.units.forever')
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
@@ -741,10 +743,10 @@ const getUnit = (value: string = expirationMethod.value) => {
|
||||
|
||||
const getExpirationTime = (method: string, value: string) => {
|
||||
if (method === 'forever') {
|
||||
return '永久'
|
||||
return t('send.expiration.units.forever')
|
||||
}
|
||||
if (method === 'count') {
|
||||
return `${value}次后过期`
|
||||
return t('send.messages.expiresAfterCount', { count: value })
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
@@ -761,7 +763,7 @@ const getExpirationTime = (method: string, value: string) => {
|
||||
now.setDate(now.getDate() + expireValue)
|
||||
break
|
||||
default:
|
||||
return `${value}${getUnit(method)}后过期`
|
||||
return t('send.messages.expiresAfter', { value, unit: getUnit(method) })
|
||||
}
|
||||
|
||||
const year = now.getFullYear()
|
||||
@@ -770,7 +772,7 @@ const getExpirationTime = (method: string, value: string) => {
|
||||
const hours = now.getHours().toString().padStart(2, '0')
|
||||
const minutes = now.getMinutes().toString().padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}过期`
|
||||
return t('send.messages.expiresAt', { date: `${year}-${month}-${day} ${hours}:${minutes}` })
|
||||
}
|
||||
|
||||
const handleChunkUpload = async (file: File) => {
|
||||
@@ -792,7 +794,7 @@ const handleChunkUpload = async (file: File) => {
|
||||
})
|
||||
|
||||
if (initResponse.code !== 200) {
|
||||
throw new Error('初始化切片上传失败')
|
||||
throw new Error(t('send.messages.initChunkUploadFailed'))
|
||||
}
|
||||
if (initResponse.detail?.existed) {
|
||||
return initResponse
|
||||
@@ -826,7 +828,7 @@ const handleChunkUpload = async (file: File) => {
|
||||
)
|
||||
|
||||
if (chunkResponse.code !== 200) {
|
||||
throw new Error(`切片 ${i} 上传失败`)
|
||||
throw new Error(t('send.messages.chunkUploadFailed', { index: i }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,7 +839,7 @@ const handleChunkUpload = async (file: File) => {
|
||||
})
|
||||
|
||||
if (completeResponse.code !== 200) {
|
||||
throw new Error('完成上传失败')
|
||||
throw new Error(t('send.messages.completeUploadFailed'))
|
||||
}
|
||||
|
||||
return completeResponse
|
||||
@@ -849,7 +851,7 @@ const handleChunkUpload = async (file: File) => {
|
||||
alertStore.showAlert(axiosError.response.data.detail, 'error')
|
||||
}
|
||||
} else {
|
||||
alertStore.showAlert('上传失败,请稍后重试', 'error')
|
||||
alertStore.showAlert(t('send.messages.uploadFailed'), 'error')
|
||||
}
|
||||
throw error
|
||||
}
|
||||
@@ -874,7 +876,7 @@ const handleDefaultFileUpload = async (file: File) => {
|
||||
}
|
||||
const checkOpenUpload = () => {
|
||||
if (config.openUpload === 0 && localStorage.getItem('token') === null) {
|
||||
alertStore.showAlert('游客上传功能已关闭', 'error')
|
||||
alertStore.showAlert(t('send.messages.guestUploadDisabled'), 'error')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -882,7 +884,7 @@ const checkOpenUpload = () => {
|
||||
|
||||
const checkFileSize = (file: File) => {
|
||||
if (file.size > config.uploadSize) {
|
||||
alertStore.showAlert(`文件大小超过限制 (${getStorageUnit(config.uploadSize)})`, 'error')
|
||||
alertStore.showAlert(t('send.messages.fileSizeExceeded', { size: getStorageUnit(config.uploadSize) }), 'error')
|
||||
selectedFile.value = null
|
||||
return false
|
||||
}
|
||||
@@ -921,22 +923,22 @@ const checkUpload = () => {
|
||||
}
|
||||
const handleSubmit = async () => {
|
||||
if (sendType.value === 'file' && !selectedFile.value) {
|
||||
alertStore.showAlert('请选择要上传的文件', 'error')
|
||||
alertStore.showAlert(t('send.messages.selectFile'), 'error')
|
||||
return
|
||||
}
|
||||
if (sendType.value === 'text' && !textContent.value.trim()) {
|
||||
alertStore.showAlert('请输入要发送的文本', 'error')
|
||||
alertStore.showAlert(t('send.messages.enterText'), 'error')
|
||||
return
|
||||
}
|
||||
if (expirationMethod.value !== 'forever' && !expirationValue.value) {
|
||||
alertStore.showAlert('请输入过期值', 'error')
|
||||
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(`过期时间不能超过${maxDays}天`, 'error')
|
||||
alertStore.showAlert(t('send.messages.expirationTooLong', { days: maxDays }), 'error')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -977,14 +979,14 @@ const handleSubmit = async () => {
|
||||
: `${(selectedFile.value!.size / (1024 * 1024)).toFixed(1)} MB`,
|
||||
expiration:
|
||||
expirationMethod.value === 'forever'
|
||||
? '永久'
|
||||
? t('send.expiration.forever')
|
||||
: getExpirationTime(expirationMethod.value, expirationValue.value),
|
||||
retrieveCode: retrieveCode
|
||||
}
|
||||
fileDataStore.addShareDataRecord(newRecord)
|
||||
|
||||
// 显示发送成功消息
|
||||
alertStore.showAlert(`文件发送成功!取件码:${retrieveCode}`, 'success')
|
||||
alertStore.showAlert(t('send.messages.sendSuccess', { code: retrieveCode }), 'success')
|
||||
// 重置表单 - 只重置文件和文本内容,保留过期信息
|
||||
selectedFile.value = null
|
||||
textContent.value = ''
|
||||
@@ -994,7 +996,7 @@ const handleSubmit = async () => {
|
||||
// 自动复制取件码链接
|
||||
await copyRetrieveLink(retrieveCode)
|
||||
} else {
|
||||
throw new Error('服务器响应异常')
|
||||
throw new Error(t('send.messages.serverError'))
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error && typeof error === 'object' && 'response' in error) {
|
||||
@@ -1003,7 +1005,7 @@ const handleSubmit = async () => {
|
||||
alertStore.showAlert(axiosError.response.data.detail, 'error')
|
||||
}
|
||||
} else {
|
||||
alertStore.showAlert('发送失败,请稍后重试', 'error')
|
||||
alertStore.showAlert(t('send.messages.sendFailed'), 'error')
|
||||
}
|
||||
} finally {
|
||||
uploadProgress.value = 0
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
<template>
|
||||
<div class="p-6 overflow-y-auto custom-scrollbar">
|
||||
<h2 class="text-2xl font-bold mb-6" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||
仪表盘
|
||||
{{ t('admin.dashboard.title') }}
|
||||
</h2>
|
||||
<!-- 统计卡片区域 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<StatCard
|
||||
title="总文件数"
|
||||
:title="t('admin.dashboard.totalFiles')"
|
||||
:value="dashboardData.totalFiles"
|
||||
:icon="FileIcon"
|
||||
icon-color="indigo"
|
||||
description-type="success">
|
||||
<template #description>
|
||||
<span>昨天:{{ dashboardData.yesterdayCount }}</span>
|
||||
<span class="ml-2">今天:{{ dashboardData.todayCount }}</span>
|
||||
<span>{{ t('admin.dashboard.yesterday') }}:{{ dashboardData.yesterdayCount }}</span>
|
||||
<span class="ml-2">{{ t('admin.dashboard.today') }}:{{ dashboardData.todayCount }}</span>
|
||||
</template>
|
||||
</StatCard>
|
||||
|
||||
<StatCard
|
||||
title="存储空间"
|
||||
:title="t('admin.dashboard.storageSpace')"
|
||||
:value="dashboardData.storageUsed"
|
||||
:icon="HardDriveIcon"
|
||||
icon-color="purple"
|
||||
description-type="success">
|
||||
<template #description>
|
||||
<span>昨天:{{ dashboardData.yesterdaySize }}</span>
|
||||
<span class="ml-2">今天:{{ dashboardData.todaySize }}</span>
|
||||
<span>{{ t('admin.dashboard.yesterday') }}:{{ dashboardData.yesterdaySize }}</span>
|
||||
<span class="ml-2">{{ t('admin.dashboard.today') }}:{{ dashboardData.todaySize }}</span>
|
||||
</template>
|
||||
</StatCard>
|
||||
|
||||
<StatCard
|
||||
title="活跃用户"
|
||||
:title="t('admin.dashboard.activeUsers')"
|
||||
value="25"
|
||||
:icon="UsersIcon"
|
||||
icon-color="green"
|
||||
description-type="error">
|
||||
<template #description>
|
||||
<span>↓ 5% 较上周</span>
|
||||
<span>{{ t('admin.dashboard.weeklyChange') }}</span>
|
||||
</template>
|
||||
</StatCard>
|
||||
|
||||
<StatCard
|
||||
title="系统状态"
|
||||
value="正常"
|
||||
:title="t('admin.dashboard.systemStatus')"
|
||||
:value="t('admin.dashboard.normal')"
|
||||
:icon="ActivityIcon"
|
||||
icon-color="blue"
|
||||
description-type="neutral">
|
||||
<template #description>
|
||||
服务器运行时间: {{ dashboardData.sysUptime }}
|
||||
{{ t('admin.dashboard.serverUptime') }}: {{ dashboardData.sysUptime }}
|
||||
</template>
|
||||
</StatCard>
|
||||
</div>
|
||||
@@ -75,7 +75,9 @@ import {
|
||||
import { StatsService } from '@/services'
|
||||
import type { DashboardData } from '@/types'
|
||||
import StatCard from '@/components/common/StatCard.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const isDarkMode = inject('isDarkMode')
|
||||
const { t } = useI18n()
|
||||
|
||||
const dashboardData = reactive<DashboardData>({
|
||||
totalFiles: 0,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- 页面标题和统计信息 -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-2xl font-bold mb-4" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||
文件管理
|
||||
{{ t('fileManage.title') }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -18,19 +18,19 @@
|
||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400'
|
||||
: 'bg-white border-gray-300 text-gray-900 placeholder-gray-400',
|
||||
'w-full pl-10 pr-4 py-2.5 rounded-lg border focus:ring-2 focus:ring-indigo-500 focus:border-transparent'
|
||||
]" placeholder="搜索文件名称、描述..." />
|
||||
]" :placeholder="t('fileManage.searchPlaceholder')" />
|
||||
<SearchIcon class="absolute left-3 top-3 w-5 h-5" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']" />
|
||||
</div>
|
||||
<button @click="handleSearch"
|
||||
class="px-4 py-2.5 rounded-lg inline-flex items-center transition-all duration-200 bg-indigo-600 hover:bg-indigo-700 text-white shadow-sm">
|
||||
<SearchIcon class="w-5 h-5 mr-2" />
|
||||
搜索
|
||||
{{ t('common.search') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<DataTable title="所有文件" :headers="fileTableHeaders">
|
||||
<DataTable :title="t('fileManage.allFiles')" :headers="fileTableHeaders">
|
||||
<template #body>
|
||||
<tr v-for="file in tableData" :key="file.id" class="hover:bg-opacity-50 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-50']">
|
||||
@@ -84,7 +84,7 @@
|
||||
? (isDarkMode ? 'bg-yellow-900/30 text-yellow-400' : 'bg-yellow-100 text-yellow-800')
|
||||
: (isDarkMode ? 'bg-green-900/30 text-green-400' : 'bg-green-100 text-green-800')
|
||||
]">
|
||||
{{ file.expired_at ? formatTimestamp(file.expired_at) : '永久' }}
|
||||
{{ file.expired_at ? formatTimestamp(file.expired_at) : t('send.expiration.units.forever') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
@@ -96,7 +96,7 @@
|
||||
: 'bg-blue-50 text-blue-600 hover:bg-blue-100'
|
||||
]">
|
||||
<PencilIcon class="w-4 h-4 mr-1.5" />
|
||||
编辑
|
||||
{{ t('common.edit') }}
|
||||
</button>
|
||||
<button @click="deleteFile(file.id)"
|
||||
class="inline-flex items-center px-3 py-1.5 rounded-md transition-colors duration-200" :class="[
|
||||
@@ -105,7 +105,7 @@
|
||||
: 'bg-red-50 text-red-600 hover:bg-red-100'
|
||||
]">
|
||||
<TrashIcon class="w-4 h-4 mr-1.5" />
|
||||
删除
|
||||
{{ t('common.delete') }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -144,7 +144,7 @@
|
||||
<PencilIcon class="w-5 h-5" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold leading-6" :class="[isDarkMode ? 'text-white' : 'text-gray-900']">
|
||||
编辑文件信息
|
||||
{{ t('fileManage.editFileInfo') }}
|
||||
</h3>
|
||||
</div>
|
||||
<!-- 优化的关闭按钮 -->
|
||||
@@ -164,7 +164,7 @@
|
||||
<div class="space-y-2 group">
|
||||
<label class="text-sm font-medium flex items-center space-x-2 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'text-gray-300 group-focus-within:text-indigo-400' : 'text-gray-700 group-focus-within:text-indigo-600']">
|
||||
<span>取件码</span>
|
||||
<span>{{ t('fileManage.form.code') }}</span>
|
||||
<div class="h-px flex-1 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'bg-gray-700 group-focus-within:bg-indigo-500/50' : 'bg-gray-200 group-focus-within:bg-indigo-500/30']">
|
||||
</div>
|
||||
@@ -176,7 +176,7 @@
|
||||
isDarkMode
|
||||
? 'bg-gray-700/50 text-white placeholder-gray-400 focus:ring-indigo-500/50'
|
||||
: 'bg-gray-50 text-gray-900 placeholder-gray-400 focus:ring-indigo-500'
|
||||
]" placeholder="输入取件码">
|
||||
]" :placeholder="t('fileManage.form.codePlaceholder')">
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none transition-opacity duration-200 opacity-0 group-focus-within:opacity-100">
|
||||
<CheckIcon class="w-5 h-5" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
|
||||
@@ -188,7 +188,7 @@
|
||||
<div class="space-y-2 group">
|
||||
<label class="text-sm font-medium flex items-center space-x-2 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'text-gray-300 group-focus-within:text-indigo-400' : 'text-gray-700 group-focus-within:text-indigo-600']">
|
||||
<span>文件名称</span>
|
||||
<span>{{ t('fileManage.form.filename') }}</span>
|
||||
<div class="h-px flex-1 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'bg-gray-700 group-focus-within:bg-indigo-500/50' : 'bg-gray-200 group-focus-within:bg-indigo-500/30']">
|
||||
</div>
|
||||
@@ -200,7 +200,7 @@
|
||||
isDarkMode
|
||||
? 'bg-gray-700/50 text-white placeholder-gray-400 focus:ring-indigo-500/50'
|
||||
: 'bg-gray-50 text-gray-900 placeholder-gray-400 focus:ring-indigo-500'
|
||||
]" placeholder="输入文件名称">
|
||||
]" :placeholder="t('fileManage.form.filenamePlaceholder')">
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none transition-opacity duration-200 opacity-0 group-focus-within:opacity-100">
|
||||
<CheckIcon class="w-5 h-5" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
|
||||
@@ -212,7 +212,7 @@
|
||||
<div class="space-y-2 group">
|
||||
<label class="text-sm font-medium flex items-center space-x-2 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'text-gray-300 group-focus-within:text-indigo-400' : 'text-gray-700 group-focus-within:text-indigo-600']">
|
||||
<span>文件后缀</span>
|
||||
<span>{{ t('fileManage.form.suffix') }}</span>
|
||||
<div class="h-px flex-1 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'bg-gray-700 group-focus-within:bg-indigo-500/50' : 'bg-gray-200 group-focus-within:bg-indigo-500/30']">
|
||||
</div>
|
||||
@@ -224,7 +224,7 @@
|
||||
isDarkMode
|
||||
? 'bg-gray-700/50 text-white placeholder-gray-400 focus:ring-indigo-500/50'
|
||||
: 'bg-gray-50 text-gray-900 placeholder-gray-400 focus:ring-indigo-500'
|
||||
]" placeholder="输入文件后缀">
|
||||
]" :placeholder="t('fileManage.form.suffixPlaceholder')">
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none transition-opacity duration-200 opacity-0 group-focus-within:opacity-100">
|
||||
<CheckIcon class="w-5 h-5" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
|
||||
@@ -236,7 +236,7 @@
|
||||
<div class="space-y-2 group">
|
||||
<label class="text-sm font-medium flex items-center space-x-2 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'text-gray-300 group-focus-within:text-indigo-400' : 'text-gray-700 group-focus-within:text-indigo-600']">
|
||||
<span>过期时间</span>
|
||||
<span>{{ t('send.expiration.label') }}</span>
|
||||
<div class="h-px flex-1 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'bg-gray-700 group-focus-within:bg-indigo-500/50' : 'bg-gray-200 group-focus-within:bg-indigo-500/30']">
|
||||
</div>
|
||||
@@ -260,7 +260,7 @@
|
||||
<div class="space-y-2 group">
|
||||
<label class="text-sm font-medium flex items-center space-x-2 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'text-gray-300 group-focus-within:text-indigo-400' : 'text-gray-700 group-focus-within:text-indigo-600']">
|
||||
<span>下载次数限制</span>
|
||||
<span>{{ t('fileManage.form.downloadLimit') }}</span>
|
||||
<div class="h-px flex-1 transition-colors duration-200"
|
||||
:class="[isDarkMode ? 'bg-gray-700 group-focus-within:bg-indigo-500/50' : 'bg-gray-200 group-focus-within:bg-indigo-500/30']">
|
||||
</div>
|
||||
@@ -272,7 +272,7 @@
|
||||
isDarkMode
|
||||
? 'bg-gray-700/50 text-white placeholder-gray-400 focus:ring-indigo-500/50'
|
||||
: 'bg-gray-50 text-gray-900 placeholder-gray-400 focus:ring-indigo-500'
|
||||
]" placeholder="输入下载次数限制">
|
||||
]" :placeholder="t('fileManage.form.downloadLimitPlaceholder')">
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none transition-opacity duration-200 opacity-0 group-focus-within:opacity-100">
|
||||
<CheckIcon class="w-5 h-5" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']" />
|
||||
@@ -292,12 +292,12 @@
|
||||
? 'bg-gray-700/50 text-gray-300 hover:bg-gray-600/50 hover:text-white'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||
]">
|
||||
取消
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
<button @click="handleUpdate"
|
||||
class="inline-flex items-center justify-center rounded-lg px-4 py-2.5 text-sm font-medium transition-all duration-200 bg-gradient-to-r from-indigo-600 to-indigo-500 hover:from-indigo-500 hover:to-indigo-600 text-white shadow-lg shadow-indigo-500/25 hover:shadow-indigo-500/35">
|
||||
<CheckIcon class="w-4 h-4 mr-2" />
|
||||
保存更改
|
||||
{{ t('fileManage.saveChanges') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -309,6 +309,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject, ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { FileService } from '@/services'
|
||||
import type { FileListItem, FileEditForm } from '@/types'
|
||||
import {
|
||||
@@ -333,11 +334,19 @@ function formatTimestamp(timestamp: string): string {
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const isDarkMode = inject('isDarkMode')
|
||||
const tableData = ref<FileListItem[]>([])
|
||||
const alertStore = useAlertStore()
|
||||
// 修改文件表头
|
||||
const fileTableHeaders = ['取件码', '名称', '大小', '描述', '过期时间', '操作']
|
||||
const fileTableHeaders = computed(() => [
|
||||
t('fileManage.headers.code'),
|
||||
t('fileManage.headers.name'),
|
||||
t('fileManage.headers.size'),
|
||||
t('fileManage.headers.description'),
|
||||
t('fileManage.headers.expiration'),
|
||||
t('fileManage.headers.actions')
|
||||
])
|
||||
|
||||
// 分页参数
|
||||
const params = ref({
|
||||
@@ -392,7 +401,7 @@ const handleUpdate = async () => {
|
||||
closeEditModal()
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string } } }
|
||||
alertStore.showAlert(err.response?.data?.detail || '更新失败', 'error')
|
||||
alertStore.showAlert(err.response?.data?.detail || t('manage.fileManage.updateFailed'), 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +434,7 @@ const deleteFile = async (id: number) => {
|
||||
await FileService.deleteAdminFile(id)
|
||||
await loadFiles()
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error)
|
||||
console.error(t('manage.fileManage.deleteFailed'), error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,7 +469,7 @@ const loadFiles = async () => {
|
||||
params.value.total = res.detail.total
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载文件列表失败:', error)
|
||||
console.error(t('manage.fileManage.loadFileListFailed'), error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ import { inject, ref } from 'vue'
|
||||
import { ConfigService } from '@/services'
|
||||
import type { ConfigState } from '@/types'
|
||||
import { useAlertStore } from '@/stores/alertStore'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const isDarkMode = inject('isDarkMode')
|
||||
const { t } = useI18n()
|
||||
|
||||
const config = ref<ConfigState>({
|
||||
name: '',
|
||||
@@ -150,21 +152,21 @@ refreshData()
|
||||
<template>
|
||||
<div class="p-6 h-screen overflow-y-auto custom-scrollbar">
|
||||
<h2 class="text-2xl font-bold mb-6" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||
系统设置
|
||||
{{ t('admin.settings.title') }}
|
||||
</h2>
|
||||
|
||||
<div class="space-y-6 rounded-lg shadow-md p-6" :class="[isDarkMode ? 'bg-gray-800 bg-opacity-70' : 'bg-white']">
|
||||
<!-- 基本设置 -->
|
||||
<section class="space-y-4">
|
||||
<h3 class="text-lg font-medium mb-4" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||
基本设置
|
||||
{{ t('admin.settings.basicSettings') }}
|
||||
</h3>
|
||||
|
||||
<!-- 网基本信息 -->
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
网站名称
|
||||
{{ t('admin.settings.siteName') }}
|
||||
</label>
|
||||
<input type="text" v-model="config.name"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -177,7 +179,7 @@ refreshData()
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
网站描述
|
||||
{{ t('admin.settings.websiteDescription') }}
|
||||
</label>
|
||||
<input type="text" v-model="config.description"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -190,10 +192,10 @@ refreshData()
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
管理员密码
|
||||
{{ t('admin.settings.adminPassword') }}
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input type="password" minlength="6" v-model="config.admin_token" placeholder="留空则不修改密码"
|
||||
<input type="password" minlength="6" v-model="config.admin_token" :placeholder="t('admin.settings.passwordPlaceholder')"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
:class="[
|
||||
isDarkMode
|
||||
@@ -202,14 +204,14 @@ refreshData()
|
||||
]" />
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-3 text-sm text-gray-400"
|
||||
:class="[isDarkMode ? 'text-gray-500' : 'text-gray-400']">
|
||||
<span class="text-xs">留空则不修改</span>
|
||||
<span class="text-xs">{{ t('admin.settings.passwordNote') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
关键词
|
||||
{{ t('admin.settings.keywords') }}
|
||||
</label>
|
||||
<input type="text" v-model="config.keywords"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -223,7 +225,7 @@ refreshData()
|
||||
<!-- 主题选择 -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
主题选择
|
||||
{{ t('manage.settings.themeSelection') }}
|
||||
</label>
|
||||
<select v-model="config.themesSelect"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border appearance-none bg-no-repeat bg-right focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none cursor-pointer"
|
||||
@@ -241,7 +243,7 @@ refreshData()
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
Robots.txt
|
||||
{{ t('manage.settings.robotsFile') }}
|
||||
</label>
|
||||
<textarea v-model="config.robotsText" rows="3"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border resize-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -257,7 +259,7 @@ refreshData()
|
||||
<div class="grid grid-cols-1 gap-6 mt-8">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
通知标题
|
||||
{{ t('manage.settings.notificationTitle') }}
|
||||
</label>
|
||||
<input type="text" v-model="config.notify_title"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -270,7 +272,7 @@ refreshData()
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
通知内容
|
||||
{{ t('manage.settings.notificationContent') }}
|
||||
</label>
|
||||
<textarea v-model="config.notify_content" rows="3"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border resize-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -285,14 +287,14 @@ refreshData()
|
||||
<!-- 存储设置 -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-medium mb-4" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||
存储设置
|
||||
{{ t('manage.settings.storageSettings') }}
|
||||
</h3>
|
||||
<!-- 通知设置 -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
存储路径
|
||||
{{ t('manage.settings.storagePath') }}
|
||||
</label>
|
||||
<input type="text" placeholder="留空则使用默认路径,可不填写" v-model="config.storage_path"
|
||||
<input type="text" :placeholder="t('manage.settings.storagePathPlaceholder')" v-model="config.storage_path"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
:class="[
|
||||
isDarkMode
|
||||
@@ -304,7 +306,7 @@ refreshData()
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
存储方式
|
||||
{{ t('manage.settings.storageMethod') }}
|
||||
</label>
|
||||
<select v-model="config.file_storage"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border appearance-none bg-no-repeat bg-right focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none cursor-pointer"
|
||||
@@ -315,14 +317,14 @@ refreshData()
|
||||
]" style="
|
||||
background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M7%208l3%203%203-3%22%20stroke%3D%22%236B7280%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E');
|
||||
">
|
||||
<option value="local">本地存储</option>
|
||||
<option value="s3">S3 存储</option>
|
||||
<option value="webdav">Webdav 存储</option>
|
||||
<option value="local">{{ t('manage.settings.localStorage') }}</option>
|
||||
<option value="s3">{{ t('manage.settings.s3Storage') }}</option>
|
||||
<option value="webdav">{{ t('manage.settings.webdavStorage') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-2" v-if="config.file_storage === 'local'">
|
||||
<label class="block text-sm font-medium mb-2" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
开启切片上传(实验性功能,还在开发中,目前仅本地存储可用,可能会出现未知问题)
|
||||
{{ t('manage.settings.chunkUploadNote') }}
|
||||
</label>
|
||||
<div class="flex items-center">
|
||||
<button type="button" @click="config.enableChunk = config.enableChunk === 1 ? 0 : 1"
|
||||
@@ -337,7 +339,7 @@ refreshData()
|
||||
]" />
|
||||
</button>
|
||||
<span class="ml-3 text-sm" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
{{ config.enableChunk === 1 ? '已开启' : '已关闭' }}
|
||||
{{ config.enableChunk === 1 ? t('common.enabled') : t('common.disabled') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -347,7 +349,7 @@ refreshData()
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
Webdav URL
|
||||
</label>
|
||||
<input type="text" placeholder="请输入 Webdav URL" v-model="config.webdav_url"
|
||||
<input type="text" :placeholder="t('manage.settings.webdavUrlPlaceholder')" v-model="config.webdav_url"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
:class="[
|
||||
isDarkMode
|
||||
@@ -360,7 +362,7 @@ refreshData()
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
Webdav Username
|
||||
</label>
|
||||
<input type="text" placeholder="请输入 Webdav Username" v-model="config.webdav_username"
|
||||
<input type="text" :placeholder="t('manage.settings.webdavUsernamePlaceholder')" v-model="config.webdav_username"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
:class="[
|
||||
isDarkMode
|
||||
@@ -373,7 +375,7 @@ refreshData()
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
Webdav Password
|
||||
</label>
|
||||
<input type="password" placeholder="请输入 Webdav Password" v-model="config.webdav_password"
|
||||
<input type="password" :placeholder="t('manage.settings.webdavPasswordPlaceholder')" v-model="config.webdav_password"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
:class="[
|
||||
isDarkMode
|
||||
@@ -388,7 +390,7 @@ refreshData()
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
S3 AccessKeyId
|
||||
{{ t('manage.settings.s3AccessKeyId') }}
|
||||
</label>
|
||||
<input type="text" v-model="config.s3_access_key_id"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -401,7 +403,7 @@ refreshData()
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
S3 SecretAccessKey
|
||||
{{ t('manage.settings.s3SecretAccessKey') }}
|
||||
</label>
|
||||
<input type="password" v-model="config.s3_secret_access_key"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -414,7 +416,7 @@ refreshData()
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
S3 BucketName
|
||||
{{ t('manage.settings.s3BucketName') }}
|
||||
</label>
|
||||
<input type="text" v-model="config.s3_bucket_name"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -427,7 +429,7 @@ refreshData()
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
S3 EndpointUrl
|
||||
{{ t('manage.settings.s3EndpointUrl') }}
|
||||
</label>
|
||||
<input type="text" v-model="config.s3_endpoint_url"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -440,9 +442,9 @@ refreshData()
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
S3 Region Name
|
||||
{{ t('manage.settings.s3RegionName') }}
|
||||
</label>
|
||||
<input type="text" v-model="config.s3_region_name" placeholder="auto"
|
||||
<input type="text" v-model="config.s3_region_name" :placeholder="t('manage.settings.autoPlaceholder')"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
:class="[
|
||||
isDarkMode
|
||||
@@ -453,7 +455,7 @@ refreshData()
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
S3 Signature Version
|
||||
{{ t('manage.settings.s3SignatureVersion') }}
|
||||
</label>
|
||||
<select v-model="config.s3_signature_version"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -462,14 +464,14 @@ refreshData()
|
||||
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
||||
: 'border-gray-300 hover:border-gray-400'
|
||||
]">
|
||||
<option value="s3v2">S3v2</option>
|
||||
<option value="s3v4">S3v4</option>
|
||||
<option value="s3v2">{{ t('manage.settings.s3v2') }}</option>
|
||||
<option value="s3v4">{{ t('manage.settings.s3v4') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
S3 Hostname
|
||||
{{ t('manage.settings.s3Hostname') }}
|
||||
</label>
|
||||
<input type="text" v-model="config.s3_hostname"
|
||||
class="w-full rounded-md shadow-sm px-4 py-2.5 transition-all duration-200 ease-in-out border focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
|
||||
@@ -483,7 +485,7 @@ refreshData()
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium mb-2"
|
||||
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
启用代理
|
||||
{{ t('manage.settings.enableProxy') }}
|
||||
</label>
|
||||
<div class="flex items-center">
|
||||
<button type="button" @click="config.s3_proxy = config.s3_proxy === 1 ? 0 : 1"
|
||||
@@ -498,7 +500,7 @@ refreshData()
|
||||
]" />
|
||||
</button>
|
||||
<span class="ml-3 text-sm" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
{{ config.s3_proxy === 1 ? '已开启' : '已关闭' }}
|
||||
{{ config.s3_proxy === 1 ? t('common.enabled') : t('common.disabled') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -510,13 +512,13 @@ refreshData()
|
||||
<!-- 上传限制 -->
|
||||
<div class="mt-8">
|
||||
<h3 class="text-lg font-medium mb-4" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||
上传限制
|
||||
{{ t('manage.settings.uploadLimits') }}
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
每分钟上传限制
|
||||
{{ t('manage.settings.uploadPerMinute') }}
|
||||
</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="number" v-model="config.uploadMinute"
|
||||
@@ -526,13 +528,13 @@ refreshData()
|
||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
||||
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||
]" />
|
||||
<span :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">分钟</span>
|
||||
<span :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">{{ t('common.minute') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
上传数量限制
|
||||
{{ t('manage.settings.uploadCountLimit') }}
|
||||
</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="number" v-model="config.uploadCount"
|
||||
@@ -542,13 +544,13 @@ refreshData()
|
||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
||||
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||
]" />
|
||||
<span :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">个文件</span>
|
||||
<span :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">{{ t('common.files') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
文件大小限制
|
||||
{{ t('manage.settings.fileSizeLimit') }}
|
||||
</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="number" v-model="fileSize"
|
||||
@@ -565,16 +567,16 @@ refreshData()
|
||||
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
||||
: 'border-gray-300 hover:border-gray-400'
|
||||
]">
|
||||
<option value="KB">KB</option>
|
||||
<option value="MB">MB</option>
|
||||
<option value="GB">GB</option>
|
||||
<option value="KB">{{ t('manage.settings.fileSizeUnits.kb') }}</option>
|
||||
<option value="MB">{{ t('manage.settings.fileSizeUnits.mb') }}</option>
|
||||
<option value="GB">{{ t('manage.settings.fileSizeUnits.gb') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium mb-2" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
过期方式
|
||||
{{ t('manage.settings.expirationType') }}
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<label v-for="style in ['day', 'hour', 'minute', 'forever', 'count']" :key="style"
|
||||
@@ -589,15 +591,7 @@ refreshData()
|
||||
? 'bg-gray-700 border-gray-600 text-gray-300 hover:border-indigo-500'
|
||||
: 'bg-white border-gray-300 text-gray-700 hover:border-indigo-500'
|
||||
]">
|
||||
{{
|
||||
{
|
||||
day: '按天',
|
||||
hour: '按小时',
|
||||
minute: '按分钟',
|
||||
forever: '永久',
|
||||
count: '按次数'
|
||||
}[style]
|
||||
}}
|
||||
{{ t(`manage.settings.expiration.${style}`) }}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -605,7 +599,7 @@ refreshData()
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
最长保存时间
|
||||
{{ t('manage.settings.maxSaveTime') }}
|
||||
</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="number" v-model="saveTime"
|
||||
@@ -622,17 +616,17 @@ refreshData()
|
||||
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
||||
: 'border-gray-300 hover:border-gray-400'
|
||||
]">
|
||||
<option value="秒">秒</option>
|
||||
<option value="分">分</option>
|
||||
<option value="时">时</option>
|
||||
<option value="天">天</option>
|
||||
<option value="秒">{{ t('common.second') }}</option>
|
||||
<option value="分">{{ t('common.minute') }}</option>
|
||||
<option value="时">{{ t('common.hour') }}</option>
|
||||
<option value="天">{{ t('common.day') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium mb-2" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
游客上传
|
||||
{{ t('manage.settings.guestUpload') }}
|
||||
</label>
|
||||
<div class="flex items-center">
|
||||
<button type="button" @click="config.openUpload = config.openUpload === 1 ? 0 : 1"
|
||||
@@ -647,7 +641,7 @@ refreshData()
|
||||
]" />
|
||||
</button>
|
||||
<span class="ml-3 text-sm" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
{{ config.openUpload === 1 ? '已开启' : '已关闭' }}
|
||||
{{ config.openUpload === 1 ? t('common.enabled') : t('common.disabled') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -657,13 +651,13 @@ refreshData()
|
||||
<!-- 错误限制 -->
|
||||
<div class="mt-8">
|
||||
<h3 class="text-lg font-medium mb-4" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||
错误限制
|
||||
{{ t('manage.settings.errorLimits') }}
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
每分钟错误限制
|
||||
{{ t('manage.settings.errorPerMinute') }}
|
||||
</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="number" v-model="config.errorMinute"
|
||||
@@ -673,13 +667,13 @@ refreshData()
|
||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
||||
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||
]" />
|
||||
<span :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">分钟</span>
|
||||
<span :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">{{ t('common.minute') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
||||
错误次数限制
|
||||
{{ t('manage.settings.errorCountLimit') }}
|
||||
</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="number" v-model="config.errorCount"
|
||||
@@ -689,7 +683,7 @@ refreshData()
|
||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
||||
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||
]" />
|
||||
<span :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">次</span>
|
||||
<span :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">{{ t('common.times') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -699,7 +693,7 @@ refreshData()
|
||||
<div class="flex justify-end mt-8">
|
||||
<button @click="submitSave"
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200">
|
||||
保存设置
|
||||
{{ t('manage.settings.saveChanges') }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user