feat: improve system settings save workflow
This commit is contained in:
@@ -18,39 +18,78 @@ type ConfigFlagKey = 'enableChunk' | 's3_proxy' | 'openUpload'
|
|||||||
export function useSystemConfig() {
|
export function useSystemConfig() {
|
||||||
const alertStore = useAlertStore()
|
const alertStore = useAlertStore()
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const config = ref<ConfigState>({ ...DEFAULT_CONFIG_STATE })
|
const config = ref<ConfigState>({ ...DEFAULT_CONFIG_STATE })
|
||||||
const isLoading = ref(false)
|
const isRefreshing = ref(false)
|
||||||
|
const isSaving = ref(false)
|
||||||
|
const savedPayloadSnapshot = ref('')
|
||||||
const fileSize = ref(1)
|
const fileSize = ref(1)
|
||||||
const sizeUnit = ref<FileSizeUnit>('MB')
|
const sizeUnit = ref<FileSizeUnit>('MB')
|
||||||
const saveTime = ref(1)
|
const saveTime = ref(1)
|
||||||
const saveTimeUnit = ref<SaveTimeUnit>('天')
|
const saveTimeUnit = ref<SaveTimeUnit>('天')
|
||||||
|
|
||||||
|
const isLoading = computed(() => isRefreshing.value || isSaving.value)
|
||||||
|
|
||||||
|
const buildSubmitPayload = (): Partial<ConfigState> => {
|
||||||
|
const payload: Partial<ConfigState> = buildConfigSubmitPayload(
|
||||||
|
config.value,
|
||||||
|
{ value: fileSize.value, unit: sizeUnit.value },
|
||||||
|
{ value: saveTime.value, unit: saveTimeUnit.value }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!payload.admin_token) {
|
||||||
|
delete payload.admin_token
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshotPayload = (payload: Partial<ConfigState>) => JSON.stringify(payload)
|
||||||
|
|
||||||
|
const normalizeEditableConfig = (nextConfig: Partial<ConfigState>): ConfigState => ({
|
||||||
|
...DEFAULT_CONFIG_STATE,
|
||||||
|
...nextConfig,
|
||||||
|
admin_token: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const markConfigSaved = () => {
|
||||||
|
savedPayloadSnapshot.value = snapshotPayload(buildSubmitPayload())
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDirty = computed(() => {
|
||||||
|
if (!savedPayloadSnapshot.value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshotPayload(buildSubmitPayload()) !== savedPayloadSnapshot.value
|
||||||
|
})
|
||||||
|
|
||||||
// 从本地存储获取配置
|
// 从本地存储获取配置
|
||||||
const getStoredConfig = (): ConfigState | null => {
|
const getStoredConfig = (): ConfigState | null => {
|
||||||
return readStoredConfig<ConfigState>()
|
const storedConfig = readStoredConfig<Partial<ConfigState>>()
|
||||||
|
return storedConfig ? normalizeEditableConfig(storedConfig) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存配置到本地存储
|
// 保存配置到本地存储
|
||||||
const saveConfigToStorage = (configData: ConfigState) => {
|
const saveConfigToStorage = (configData: ConfigState) => {
|
||||||
configStore.updateConfig(configData)
|
configStore.updateConfig(configData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取系统配置
|
// 获取系统配置
|
||||||
const fetchConfig = async (): Promise<ConfigState | null> => {
|
const fetchConfig = async (): Promise<ConfigState | null> => {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isRefreshing.value = true
|
||||||
|
|
||||||
const response = await ConfigService.getConfig()
|
const response = await ConfigService.getConfig()
|
||||||
|
|
||||||
if (response.code === 200 && response.detail) {
|
if (response.code === 200 && response.detail) {
|
||||||
config.value = { ...DEFAULT_CONFIG_STATE, ...response.detail }
|
config.value = normalizeEditableConfig(response.detail)
|
||||||
const notifyMessage = configStore.applyRemoteConfig(config.value)
|
const notifyMessage = configStore.applyRemoteConfig(config.value)
|
||||||
if (notifyMessage) {
|
if (notifyMessage) {
|
||||||
alertStore.showAlert(notifyMessage, 'success')
|
alertStore.showAlert(notifyMessage, 'success')
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.value
|
return config.value
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.message || '获取配置失败')
|
throw new Error(response.message || '获取配置失败')
|
||||||
@@ -62,23 +101,25 @@ export function useSystemConfig() {
|
|||||||
config.value = storedConfig
|
config.value = storedConfig
|
||||||
return config.value
|
return config.value
|
||||||
}
|
}
|
||||||
|
|
||||||
alertStore.showAlert(getErrorMessage(error, '获取配置失败'), 'error')
|
alertStore.showAlert(getErrorMessage(error, '获取配置失败'), 'error')
|
||||||
return null
|
return null
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isRefreshing.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新系统配置
|
// 更新系统配置
|
||||||
const updateConfig = async (newConfig: Partial<ConfigState>): Promise<boolean> => {
|
const updateConfig = async (newConfig: Partial<ConfigState>): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isSaving.value = true
|
||||||
|
|
||||||
const response = await ConfigService.updateConfig(newConfig)
|
const response = await ConfigService.updateConfig(newConfig)
|
||||||
|
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
config.value = { ...config.value, ...newConfig }
|
config.value = normalizeEditableConfig({ ...config.value, ...newConfig })
|
||||||
|
syncConfigForm(config.value)
|
||||||
|
markConfigSaved()
|
||||||
saveConfigToStorage(config.value)
|
saveConfigToStorage(config.value)
|
||||||
alertStore.showAlert('配置更新成功!', 'success')
|
alertStore.showAlert('配置更新成功!', 'success')
|
||||||
return true
|
return true
|
||||||
@@ -89,7 +130,7 @@ export function useSystemConfig() {
|
|||||||
alertStore.showAlert(getErrorMessage(error, '更新配置失败'), 'error')
|
alertStore.showAlert(getErrorMessage(error, '更新配置失败'), 'error')
|
||||||
return false
|
return false
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isSaving.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,52 +152,57 @@ export function useSystemConfig() {
|
|||||||
const latestConfig = await fetchConfig()
|
const latestConfig = await fetchConfig()
|
||||||
if (latestConfig) {
|
if (latestConfig) {
|
||||||
syncConfigForm(latestConfig)
|
syncConfigForm(latestConfig)
|
||||||
|
markConfigSaved()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitConfig = () =>
|
const submitConfig = () => {
|
||||||
updateConfig(
|
if (!isDirty.value || isSaving.value) {
|
||||||
buildConfigSubmitPayload(
|
return Promise.resolve(false)
|
||||||
config.value,
|
}
|
||||||
{ value: fileSize.value, unit: sizeUnit.value },
|
|
||||||
{ value: saveTime.value, unit: saveTimeUnit.value }
|
return updateConfig(buildSubmitPayload())
|
||||||
)
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// 初始化配置
|
// 初始化配置
|
||||||
const initConfig = async () => {
|
const initConfig = async () => {
|
||||||
// 先尝试从本地存储加载
|
// 先尝试从本地存储加载
|
||||||
const storedConfig = getStoredConfig()
|
const storedConfig = getStoredConfig()
|
||||||
if (storedConfig) {
|
if (storedConfig) {
|
||||||
config.value = storedConfig
|
config.value = storedConfig
|
||||||
|
syncConfigForm(storedConfig)
|
||||||
|
markConfigSaved()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 然后从服务器获取最新配置
|
// 然后从服务器获取最新配置
|
||||||
await fetchConfig()
|
await refreshConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const maxFileSizeMB = computed(() => {
|
const maxFileSizeMB = computed(() => {
|
||||||
return Math.round(config.value.uploadSize / 1024 / 1024)
|
return Math.round(config.value.uploadSize / 1024 / 1024)
|
||||||
})
|
})
|
||||||
|
|
||||||
const isConfigLoaded = computed(() => {
|
const isConfigLoaded = computed(() => {
|
||||||
return config.value.name !== DEFAULT_CONFIG_STATE.name || !isLoading.value
|
return config.value.name !== DEFAULT_CONFIG_STATE.name || !isLoading.value
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
config,
|
config,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isRefreshing,
|
||||||
|
isSaving,
|
||||||
|
isDirty,
|
||||||
fileSize,
|
fileSize,
|
||||||
sizeUnit,
|
sizeUnit,
|
||||||
saveTime,
|
saveTime,
|
||||||
saveTimeUnit,
|
saveTimeUnit,
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
maxFileSizeMB,
|
maxFileSizeMB,
|
||||||
isConfigLoaded,
|
isConfigLoaded,
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
fetchConfig,
|
fetchConfig,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
|
|||||||
@@ -446,7 +446,13 @@ export default {
|
|||||||
mb: 'MB',
|
mb: 'MB',
|
||||||
gb: 'GB'
|
gb: 'GB'
|
||||||
},
|
},
|
||||||
saveChanges: 'Save Settings'
|
saveChanges: 'Save Settings',
|
||||||
|
refreshConfig: 'Refresh Config',
|
||||||
|
refreshing: 'Refreshing',
|
||||||
|
saving: 'Saving',
|
||||||
|
unsavedChanges: 'Unsaved configuration changes',
|
||||||
|
allChangesSaved: 'All configuration changes are saved',
|
||||||
|
refreshBlocked: 'Save current changes before refreshing'
|
||||||
},
|
},
|
||||||
systemSettings: {
|
systemSettings: {
|
||||||
title: 'System Settings',
|
title: 'System Settings',
|
||||||
|
|||||||
@@ -477,7 +477,13 @@ export default {
|
|||||||
errorLimits: '访问保护',
|
errorLimits: '访问保护',
|
||||||
errorPerMinute: '检测时间窗口(在此时间内统计错误次数)',
|
errorPerMinute: '检测时间窗口(在此时间内统计错误次数)',
|
||||||
errorCountLimit: '允许错误次数(超过后临时封禁)',
|
errorCountLimit: '允许错误次数(超过后临时封禁)',
|
||||||
saveChanges: '保存设置'
|
saveChanges: '保存设置',
|
||||||
|
refreshConfig: '刷新配置',
|
||||||
|
refreshing: '刷新中',
|
||||||
|
saving: '保存中',
|
||||||
|
unsavedChanges: '有未保存的配置变更',
|
||||||
|
allChangesSaved: '所有配置已保存',
|
||||||
|
refreshBlocked: '请先保存当前变更再刷新'
|
||||||
},
|
},
|
||||||
dashboard: {
|
dashboard: {
|
||||||
title: '仪表盘',
|
title: '仪表盘',
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inject, onMounted } from 'vue'
|
import { inject, onMounted } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { RefreshCwIcon, SaveIcon } from 'lucide-vue-next'
|
||||||
|
import BaseButton from '@/components/common/BaseButton.vue'
|
||||||
import SettingNumberInput from '@/components/common/SettingNumberInput.vue'
|
import SettingNumberInput from '@/components/common/SettingNumberInput.vue'
|
||||||
import SettingSwitch from '@/components/common/SettingSwitch.vue'
|
import SettingSwitch from '@/components/common/SettingSwitch.vue'
|
||||||
import { useSystemConfig } from '@/composables'
|
import { useSystemConfig } from '@/composables'
|
||||||
@@ -9,6 +11,9 @@ const isDarkMode = inject('isDarkMode')
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const {
|
const {
|
||||||
config,
|
config,
|
||||||
|
isRefreshing,
|
||||||
|
isSaving,
|
||||||
|
isDirty,
|
||||||
fileSize,
|
fileSize,
|
||||||
sizeUnit,
|
sizeUnit,
|
||||||
saveTime,
|
saveTime,
|
||||||
@@ -25,11 +30,61 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-6 h-screen overflow-y-auto custom-scrollbar">
|
<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']">
|
<div
|
||||||
{{ t('admin.settings.title') }}
|
class="sticky top-0 z-20 -mx-6 -mt-6 mb-6 border-b px-6 py-4 backdrop-blur"
|
||||||
</h2>
|
:class="[isDarkMode ? 'border-gray-700 bg-gray-900/85' : 'border-gray-200 bg-gray-50/90']"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl font-bold" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||||
|
{{ t('admin.settings.title') }}
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
class="mt-1 text-sm"
|
||||||
|
:class="[isDirty ? 'text-amber-500' : isDarkMode ? 'text-gray-400' : 'text-gray-500']"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
isDirty ? t('manage.settings.unsavedChanges') : t('manage.settings.allChangesSaved')
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="space-y-6 rounded-lg shadow-md p-6" :class="[isDarkMode ? 'bg-gray-800 bg-opacity-70' : 'bg-white']">
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
|
<BaseButton
|
||||||
|
variant="secondary"
|
||||||
|
:loading="isRefreshing"
|
||||||
|
:disabled="isSaving || isDirty"
|
||||||
|
:title="
|
||||||
|
isDirty ? t('manage.settings.refreshBlocked') : t('manage.settings.refreshConfig')
|
||||||
|
"
|
||||||
|
@click="refreshConfig"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<RefreshCwIcon class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{
|
||||||
|
isRefreshing ? t('manage.settings.refreshing') : t('manage.settings.refreshConfig')
|
||||||
|
}}
|
||||||
|
</BaseButton>
|
||||||
|
|
||||||
|
<BaseButton
|
||||||
|
:loading="isSaving"
|
||||||
|
:disabled="!isDirty || isRefreshing"
|
||||||
|
@click="submitConfig"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<SaveIcon class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{ isSaving ? t('manage.settings.saving') : t('manage.settings.saveChanges') }}
|
||||||
|
</BaseButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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">
|
<section class="space-y-4">
|
||||||
<h3 class="text-lg font-medium mb-4" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
<h3 class="text-lg font-medium mb-4" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||||
@@ -39,158 +94,226 @@ onMounted(() => {
|
|||||||
<!-- 网基本信息 -->
|
<!-- 网基本信息 -->
|
||||||
<div class="grid grid-cols-1 gap-6">
|
<div class="grid grid-cols-1 gap-6">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('admin.settings.siteName') }}
|
{{ t('admin.settings.siteName') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" v-model="config.name"
|
<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"
|
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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('admin.settings.websiteDescription') }}
|
{{ t('admin.settings.websiteDescription') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" v-model="config.description"
|
<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"
|
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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('admin.settings.adminPassword') }}
|
{{ t('admin.settings.adminPassword') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input type="password" minlength="6" v-model="config.admin_token" :placeholder="t('admin.settings.passwordPlaceholder')"
|
<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="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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
<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']">
|
<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">{{ t('admin.settings.passwordNote') }}</span>
|
<span class="text-xs">{{ t('admin.settings.passwordNote') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('admin.settings.keywords') }}
|
{{ t('admin.settings.keywords') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" v-model="config.keywords"
|
<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"
|
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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 主题选择 -->
|
<!-- 主题选择 -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.themeSelection') }}
|
{{ t('manage.settings.themeSelection') }}
|
||||||
</label>
|
</label>
|
||||||
<select v-model="config.themesSelect"
|
<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"
|
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"
|
||||||
:class="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
||||||
: 'border-gray-300 hover:border-gray-400'
|
: 'border-gray-300 hover:border-gray-400'
|
||||||
]" style="
|
]"
|
||||||
|
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');
|
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 v-for="item in config.themesChoices" :value="item.key" :key="item.key">
|
<option v-for="item in config.themesChoices" :value="item.key" :key="item.key">
|
||||||
{{ item.name }} (by {{ item.author }} V{{ item.version }})
|
{{ item.name }} (by {{ item.author }} V{{ item.version }})
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.robotsFile') }}
|
{{ t('manage.settings.robotsFile') }}
|
||||||
</label>
|
</label>
|
||||||
<textarea v-model="config.robotsText" rows="3"
|
<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"
|
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"
|
||||||
:class="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]"></textarea>
|
]"
|
||||||
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 通知设置 -->
|
<!-- 通知设置 -->
|
||||||
<div class="grid grid-cols-1 gap-6 mt-8">
|
<div class="grid grid-cols-1 gap-6 mt-8">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.notificationTitle') }}
|
{{ t('manage.settings.notificationTitle') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" v-model="config.notify_title"
|
<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"
|
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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.notificationContent') }}
|
{{ t('manage.settings.notificationContent') }}
|
||||||
</label>
|
</label>
|
||||||
<textarea v-model="config.notify_content" rows="3"
|
<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"
|
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"
|
||||||
:class="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]"></textarea>
|
]"
|
||||||
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 存储设置 -->
|
<!-- 存储设置 -->
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="text-lg font-medium mb-4" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
<h3
|
||||||
|
class="text-lg font-medium mb-4"
|
||||||
|
:class="[isDarkMode ? 'text-white' : 'text-gray-800']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.storageSettings') }}
|
{{ t('manage.settings.storageSettings') }}
|
||||||
</h3>
|
</h3>
|
||||||
<!-- 通知设置 -->
|
<!-- 通知设置 -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.storagePath') }}
|
{{ t('manage.settings.storagePath') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" :placeholder="t('manage.settings.storagePathPlaceholder')" 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="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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.storageMethod') }}
|
{{ t('manage.settings.storageMethod') }}
|
||||||
</label>
|
</label>
|
||||||
<select v-model="config.file_storage"
|
<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"
|
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"
|
||||||
:class="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
||||||
: 'border-gray-300 hover:border-gray-400'
|
: 'border-gray-300 hover:border-gray-400'
|
||||||
]" style="
|
]"
|
||||||
|
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');
|
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">{{ t('manage.settings.localStorage') }}</option>
|
<option value="local">{{ t('manage.settings.localStorage') }}</option>
|
||||||
<option value="s3">{{ t('manage.settings.s3Storage') }}</option>
|
<option value="s3">{{ t('manage.settings.s3Storage') }}</option>
|
||||||
<option value="webdav">{{ t('manage.settings.webdavStorage') }}</option>
|
<option value="webdav">{{ t('manage.settings.webdavStorage') }}</option>
|
||||||
@@ -207,42 +330,63 @@ onMounted(() => {
|
|||||||
<div v-if="config.file_storage === 'webdav'" class="space-y-4">
|
<div v-if="config.file_storage === 'webdav'" class="space-y-4">
|
||||||
<!-- 通知设置 -->
|
<!-- 通知设置 -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
Webdav URL
|
Webdav URL
|
||||||
</label>
|
</label>
|
||||||
<input type="text" :placeholder="t('manage.settings.webdavUrlPlaceholder')" 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="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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
Webdav Username
|
Webdav Username
|
||||||
</label>
|
</label>
|
||||||
<input type="text" :placeholder="t('manage.settings.webdavUsernamePlaceholder')" 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="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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
Webdav Password
|
Webdav Password
|
||||||
</label>
|
</label>
|
||||||
<input type="password" :placeholder="t('manage.settings.webdavPasswordPlaceholder')" 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="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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -250,97 +394,139 @@ onMounted(() => {
|
|||||||
<div v-if="config.file_storage === 's3'" class="space-y-4">
|
<div v-if="config.file_storage === 's3'" class="space-y-4">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.s3AccessKeyId') }}
|
{{ t('manage.settings.s3AccessKeyId') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" v-model="config.s3_access_key_id"
|
<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"
|
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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.s3SecretAccessKey') }}
|
{{ t('manage.settings.s3SecretAccessKey') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="password" v-model="config.s3_secret_access_key"
|
<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"
|
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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.s3BucketName') }}
|
{{ t('manage.settings.s3BucketName') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" v-model="config.s3_bucket_name"
|
<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"
|
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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.s3EndpointUrl') }}
|
{{ t('manage.settings.s3EndpointUrl') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" v-model="config.s3_endpoint_url"
|
<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"
|
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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.s3RegionName') }}
|
{{ t('manage.settings.s3RegionName') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" v-model="config.s3_region_name" :placeholder="t('manage.settings.autoPlaceholder')"
|
<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="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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.s3SignatureVersion') }}
|
{{ t('manage.settings.s3SignatureVersion') }}
|
||||||
</label>
|
</label>
|
||||||
<select v-model="config.s3_signature_version"
|
<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"
|
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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
||||||
: 'border-gray-300 hover:border-gray-400'
|
: 'border-gray-300 hover:border-gray-400'
|
||||||
]">
|
]"
|
||||||
|
>
|
||||||
<option value="s3v2">{{ t('manage.settings.s3v2') }}</option>
|
<option value="s3v2">{{ t('manage.settings.s3v2') }}</option>
|
||||||
<option value="s3v4">{{ t('manage.settings.s3v4') }}</option>
|
<option value="s3v4">{{ t('manage.settings.s3v4') }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.s3Hostname') }}
|
{{ t('manage.settings.s3Hostname') }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" v-model="config.s3_hostname"
|
<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"
|
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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SettingSwitch
|
<SettingSwitch
|
||||||
@@ -365,7 +551,10 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- 上传限制 -->
|
<!-- 上传限制 -->
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h3 class="text-lg font-medium mb-4" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
<h3
|
||||||
|
class="text-lg font-medium mb-4"
|
||||||
|
:class="[isDarkMode ? 'text-white' : 'text-gray-800']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.uploadLimits') }}
|
{{ t('manage.settings.uploadLimits') }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@@ -383,48 +572,70 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.fileSizeLimit') }}
|
{{ t('manage.settings.fileSizeLimit') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<input type="number" v-model="fileSize"
|
<input
|
||||||
|
type="number"
|
||||||
|
v-model="fileSize"
|
||||||
class="w-24 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="w-24 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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
<select v-model="sizeUnit"
|
/>
|
||||||
|
<select
|
||||||
|
v-model="sizeUnit"
|
||||||
class="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="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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
||||||
: 'border-gray-300 hover:border-gray-400'
|
: 'border-gray-300 hover:border-gray-400'
|
||||||
]">
|
]"
|
||||||
|
>
|
||||||
<option value="KB">{{ t('manage.settings.fileSizeUnits.kb') }}</option>
|
<option value="KB">{{ t('manage.settings.fileSizeUnits.kb') }}</option>
|
||||||
<option value="MB">{{ t('manage.settings.fileSizeUnits.mb') }}</option>
|
<option value="MB">{{ t('manage.settings.fileSizeUnits.mb') }}</option>
|
||||||
<option value="GB">{{ t('manage.settings.fileSizeUnits.gb') }}</option>
|
<option value="GB">{{ t('manage.settings.fileSizeUnits.gb') }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium mb-2" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium mb-2"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.expirationType') }}
|
{{ t('manage.settings.expirationType') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex flex-wrap gap-3">
|
<div class="flex flex-wrap gap-3">
|
||||||
<label v-for="style in ['day', 'hour', 'minute', 'forever', 'count']" :key="style"
|
<label
|
||||||
class="relative inline-flex items-center group cursor-pointer">
|
v-for="style in ['day', 'hour', 'minute', 'forever', 'count']"
|
||||||
<input type="checkbox" :value="style" v-model="config.expireStyle" class="peer sr-only" />
|
:key="style"
|
||||||
<div class="px-4 py-2 rounded-full border-2 transition-all duration-200 select-none" :class="[
|
class="relative inline-flex items-center group cursor-pointer"
|
||||||
config.expireStyle.includes(style)
|
>
|
||||||
? isDarkMode
|
<input
|
||||||
? 'bg-indigo-600 border-indigo-600 text-white'
|
type="checkbox"
|
||||||
: 'bg-indigo-600 border-indigo-600 text-white'
|
:value="style"
|
||||||
: isDarkMode
|
v-model="config.expireStyle"
|
||||||
? 'bg-gray-700 border-gray-600 text-gray-300 hover:border-indigo-500'
|
class="peer sr-only"
|
||||||
: 'bg-white border-gray-300 text-gray-700 hover:border-indigo-500'
|
/>
|
||||||
]">
|
<div
|
||||||
|
class="px-4 py-2 rounded-full border-2 transition-all duration-200 select-none"
|
||||||
|
:class="[
|
||||||
|
config.expireStyle.includes(style)
|
||||||
|
? isDarkMode
|
||||||
|
? 'bg-indigo-600 border-indigo-600 text-white'
|
||||||
|
: 'bg-indigo-600 border-indigo-600 text-white'
|
||||||
|
: isDarkMode
|
||||||
|
? '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'
|
||||||
|
]"
|
||||||
|
>
|
||||||
{{ t(`manage.settings.expiration.${style}`) }}
|
{{ t(`manage.settings.expiration.${style}`) }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
@@ -432,24 +643,32 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium" :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">
|
<label
|
||||||
|
class="block text-sm font-medium"
|
||||||
|
:class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.maxSaveTime') }}
|
{{ t('manage.settings.maxSaveTime') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<input type="number" v-model="saveTime"
|
<input
|
||||||
|
type="number"
|
||||||
|
v-model="saveTime"
|
||||||
class="w-24 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="w-24 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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
? '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'
|
: 'border-gray-300 hover:border-gray-400 placeholder-gray-500'
|
||||||
]" />
|
]"
|
||||||
<select v-model="saveTimeUnit"
|
/>
|
||||||
|
<select
|
||||||
|
v-model="saveTimeUnit"
|
||||||
class="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="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="[
|
:class="[
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
? 'bg-gray-700 border-gray-600 text-white hover:border-gray-500'
|
||||||
: 'border-gray-300 hover:border-gray-400'
|
: 'border-gray-300 hover:border-gray-400'
|
||||||
]">
|
]"
|
||||||
|
>
|
||||||
<option value="秒">{{ t('common.second') }}</option>
|
<option value="秒">{{ t('common.second') }}</option>
|
||||||
<option value="分">{{ t('common.minute') }}</option>
|
<option value="分">{{ t('common.minute') }}</option>
|
||||||
<option value="时">{{ t('common.hour') }}</option>
|
<option value="时">{{ t('common.hour') }}</option>
|
||||||
@@ -470,7 +689,10 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- 错误限制 -->
|
<!-- 错误限制 -->
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h3 class="text-lg font-medium mb-4" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
<h3
|
||||||
|
class="text-lg font-medium mb-4"
|
||||||
|
:class="[isDarkMode ? 'text-white' : 'text-gray-800']"
|
||||||
|
>
|
||||||
{{ t('manage.settings.errorLimits') }}
|
{{ t('manage.settings.errorLimits') }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@@ -488,14 +710,6 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 保存按钮 -->
|
|
||||||
<div class="flex justify-end mt-8">
|
|
||||||
<button @click="submitConfig"
|
|
||||||
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>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user