Files
nl-blogs/client/src/pages/admin/OSSConfigs.vue

556 lines
19 KiB
Vue
Raw Normal View History

2026-01-19 20:21:09 +08:00
<template>
<div class="w-full animate-reveal">
<div class="mb-6">
<h1 class="text-2xl font-serif italic text-white">OSS配置管理</h1>
</div>
<!-- Tab导航 -->
<div class="mb-6 border-b border-white/10">
<div class="flex gap-4">
<button
v-for="tab in tabs"
:key="tab.value"
@click="switchTab(tab.value)"
:class="[
'px-4 py-2 text-sm font-medium transition-colors border-b-2',
activeTab === tab.value
? 'text-art-accent border-art-accent'
: 'text-art-muted border-transparent hover:text-white'
]"
>
{{ tab.label }}
</button>
</div>
</div>
<!-- 表单 -->
<div class="admin-card p-6">
<form @submit.prevent="saveOSSConfig" class="space-y-6">
<div class="space-y-2">
<label for="ossName" class="block text-xs font-medium text-art-muted uppercase tracking-wider">配置名称</label>
<input
type="text"
id="ossName"
v-model="ossForm.name"
required
class="admin-input"
:placeholder="`${getStorageTypeLabel(activeTab)}配置名称`"
>
</div>
<div class="space-y-2">
<label for="storageType" class="block text-xs font-medium text-art-muted uppercase tracking-wider">存储类型</label>
<input
type="text"
id="storageType"
:value="getStorageTypeLabel(activeTab)"
disabled
class="admin-input bg-white/5 text-art-muted cursor-not-allowed"
>
</div>
<!-- 阿里云OSS字段 -->
<template v-if="activeTab === 'aliyun'">
<div class="space-y-2">
<label for="ossAccessKeyId" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
OSS Access Key ID <span class="text-red-400">*</span>
</label>
<input
type="text"
id="ossAccessKeyId"
v-model="ossForm.ossAccessKeyId"
required
class="admin-input font-mono"
placeholder="OSS Access Key ID"
>
</div>
<div class="space-y-2">
<label for="ossAccessKeySecret" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
OSS Access Key Secret <span class="text-red-400">*</span>
</label>
<input
type="password"
id="ossAccessKeySecret"
v-model="ossForm.ossAccessKeySecret"
required
class="admin-input font-mono"
placeholder="OSS Access Key Secret"
>
</div>
<div class="space-y-2">
<label for="ossEndpoint" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
OSS Endpoint <span class="text-red-400">*</span>
</label>
<input
type="text"
id="ossEndpoint"
v-model="ossForm.ossEndpoint"
required
class="admin-input"
placeholder="例如: https://oss-cn-beijing.aliyuncs.com"
>
</div>
<div class="space-y-2">
<label for="ossBucket" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
OSS Bucket <span class="text-red-400">*</span>
</label>
<input
type="text"
id="ossBucket"
v-model="ossForm.ossBucket"
required
class="admin-input"
placeholder="存储桶名称"
>
</div>
<div class="space-y-2">
<label for="ossDomain" class="block text-xs font-medium text-art-muted uppercase tracking-wider">OSS Domain</label>
<input
type="text"
id="ossDomain"
v-model="ossForm.ossDomain"
class="admin-input"
placeholder="例如: https://cdn.example.com"
>
</div>
</template>
<!-- 腾讯云COS字段 -->
<template v-if="activeTab === 'qcloud'">
<div class="space-y-2">
<label for="qcloudSecretId" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
QCloud Secret ID <span class="text-red-400">*</span>
</label>
<input
type="text"
id="qcloudSecretId"
v-model="ossForm.qcloudSecretId"
required
class="admin-input font-mono"
placeholder="QCloud Secret ID"
>
</div>
<div class="space-y-2">
<label for="qcloudSecretKey" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
QCloud Secret Key <span class="text-red-400">*</span>
</label>
<input
type="password"
id="qcloudSecretKey"
v-model="ossForm.qcloudSecretKey"
required
class="admin-input font-mono"
placeholder="QCloud Secret Key"
>
</div>
<div class="space-y-2">
<label for="qcloudRegion" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
QCloud Region <span class="text-red-400">*</span>
</label>
<input
type="text"
id="qcloudRegion"
v-model="ossForm.qcloudRegion"
required
class="admin-input"
placeholder="例如: ap-beijing"
>
</div>
<div class="space-y-2">
<label for="qcloudBucket" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
QCloud Bucket <span class="text-red-400">*</span>
</label>
<input
type="text"
id="qcloudBucket"
v-model="ossForm.qcloudBucket"
required
class="admin-input"
placeholder="存储桶名称"
>
</div>
<div class="space-y-2">
<label for="qcloudDomain" class="block text-xs font-medium text-art-muted uppercase tracking-wider">QCloud Domain</label>
<input
type="text"
id="qcloudDomain"
v-model="ossForm.qcloudDomain"
class="admin-input"
placeholder="例如: https://cdn.example.com"
>
</div>
</template>
<!-- 七牛云字段 -->
<template v-if="activeTab === 'qiniu'">
<div class="space-y-2">
<label for="qiniuAccessKey" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
Qiniu Access Key <span class="text-red-400">*</span>
</label>
<input
type="text"
id="qiniuAccessKey"
v-model="ossForm.qiniuAccessKey"
required
class="admin-input font-mono"
placeholder="Qiniu Access Key"
>
</div>
<div class="space-y-2">
<label for="qiniuSecretKey" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
Qiniu Secret Key <span class="text-red-400">*</span>
</label>
<input
type="password"
id="qiniuSecretKey"
v-model="ossForm.qiniuSecretKey"
required
class="admin-input font-mono"
placeholder="Qiniu Secret Key"
>
</div>
<div class="space-y-2">
<label for="qiniuBucket" class="block text-xs font-medium text-art-muted uppercase tracking-wider">
Qiniu Bucket <span class="text-red-400">*</span>
</label>
<input
type="text"
id="qiniuBucket"
v-model="ossForm.qiniuBucket"
required
class="admin-input"
placeholder="存储桶名称"
>
</div>
<div class="space-y-2">
<label for="qiniuRegion" class="block text-xs font-medium text-art-muted uppercase tracking-wider">Qiniu Region</label>
<input
type="text"
id="qiniuRegion"
v-model="ossForm.qiniuRegion"
class="admin-input"
placeholder="例如: z0 (华东), z1 (华北), z2 (华南)"
>
</div>
<div class="space-y-2">
<label for="qiniuDomain" class="block text-xs font-medium text-art-muted uppercase tracking-wider">Qiniu Domain</label>
<input
type="text"
id="qiniuDomain"
v-model="ossForm.qiniuDomain"
class="admin-input"
placeholder="例如: https://cdn.example.com"
>
</div>
</template>
<!-- 本地存储不需要额外字段 -->
<div v-if="activeTab === 'local'" class="p-4 bg-white/5 rounded text-sm text-art-muted">
本地存储不需要配置额外的密钥信息
</div>
<div class="space-y-2">
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
v-model="ossForm.isActive"
:true-value="1"
:false-value="0"
class="w-4 h-4 rounded border-white/20 bg-white/5 text-art-accent focus:ring-art-accent"
>
<span class="text-sm text-art-muted">启用此配置</span>
</label>
</div>
<div class="pt-4 flex justify-between items-center">
<button
v-if="currentConfig && !isDefaultConfig(currentConfig)"
type="button"
@click="deleteOSSConfig"
class="admin-btn-danger"
>
删除配置
</button>
<div v-else></div>
<div class="flex gap-3">
<button type="submit" class="admin-btn-primary" :disabled="isSubmitting">
{{ isSubmitting ? '保存中...' : (currentConfig ? '保存' : '创建') }}
</button>
</div>
</div>
</form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue'
import { getOSSConfigs, createOSSConfig, updateOSSConfig, deleteOSSConfig as deleteOSSConfigApi, OSSConfig } from '../../services/api'
import { useToast } from '../../composables/useToast'
const toast = useToast()
// Tab管理
const activeTab = ref<string>('local')
const tabs = [
{ value: 'local', label: '本地存储' },
{ value: 'aliyun', label: '阿里云' },
{ value: 'qcloud', label: '腾讯云' },
{ value: 'qiniu', label: '七牛云' }
]
// OSS配置管理
const ossConfigs = ref<OSSConfig[]>([])
const isSubmitting = ref(false)
const ossForm = ref({
id: 0,
name: '',
storageType: '',
// 阿里云OSS专用字段
ossAccessKeyId: '',
ossAccessKeySecret: '',
ossEndpoint: '',
ossBucket: '',
ossDomain: '',
// 腾讯云COS专用字段
qcloudSecretId: '',
qcloudSecretKey: '',
qcloudRegion: '',
qcloudBucket: '',
qcloudDomain: '',
// 七牛云专用字段
qiniuAccessKey: '',
qiniuSecretKey: '',
qiniuBucket: '',
qiniuRegion: '',
qiniuDomain: '',
isActive: 0 as number
})
// 存储类型选项
const storageTypeOptions = [
{ value: 'local', label: '本地存储' },
{ value: 'qcloud', label: '腾讯云COS' },
{ value: 'aliyun', label: '阿里云OSS' },
{ value: 'qiniu', label: '七牛云' }
]
// 获取存储类型标签
const getStorageTypeLabel = (type: string): string => {
const option = storageTypeOptions.find(opt => opt.value === type)
return option ? option.label : type
}
// 获取当前tab对应的配置
const currentConfig = computed(() => {
// 优先查找已启用的配置
const activeConfig = ossConfigs.value.find(c => c.storageType === activeTab.value && c.isActive === 1)
if (activeConfig) return activeConfig
// 如果没有已启用的,查找任意配置
return ossConfigs.value.find(c => c.storageType === activeTab.value)
})
// 判断是否为默认配置
const isDefaultConfig = (config: OSSConfig | undefined): boolean => {
if (!config) return false
const defaultNames = ['本地存储', '阿里云OSS', '腾讯云COS', '七牛云']
return defaultNames.includes(config.name)
}
// 切换Tab时加载对应配置
const switchTab = (tabValue: string) => {
activeTab.value = tabValue
loadConfigForTab()
}
// 加载当前tab的配置
const loadConfigForTab = () => {
const config = currentConfig.value
if (config) {
// 填充表单
ossForm.value = {
id: config.id,
name: config.name,
storageType: config.storageType,
ossAccessKeyId: (config as any).ossAccessKeyId === '***' ? '' : (config as any).ossAccessKeyId || '',
ossAccessKeySecret: (config as any).ossAccessKeySecret === '***' ? '' : (config as any).ossAccessKeySecret || '',
ossEndpoint: (config as any).ossEndpoint || '',
ossBucket: (config as any).ossBucket || '',
ossDomain: (config as any).ossDomain || '',
qcloudSecretId: (config as any).qcloudSecretId === '***' ? '' : (config as any).qcloudSecretId || '',
qcloudSecretKey: (config as any).qcloudSecretKey === '***' ? '' : (config as any).qcloudSecretKey || '',
qcloudRegion: (config as any).qcloudRegion || '',
qcloudBucket: (config as any).qcloudBucket || '',
qcloudDomain: (config as any).qcloudDomain || '',
qiniuAccessKey: (config as any).qiniuAccessKey === '***' ? '' : (config as any).qiniuAccessKey || '',
qiniuSecretKey: (config as any).qiniuSecretKey === '***' ? '' : (config as any).qiniuSecretKey || '',
qiniuBucket: (config as any).qiniuBucket || '',
qiniuRegion: (config as any).qiniuRegion || '',
qiniuDomain: (config as any).qiniuDomain || '',
isActive: config.isActive
}
} else {
// 清空表单,设置默认值
const defaultNames: Record<string, string> = {
local: '本地存储',
aliyun: '阿里云OSS',
qcloud: '腾讯云COS',
qiniu: '七牛云'
}
ossForm.value = {
id: 0,
name: defaultNames[activeTab.value] || '',
storageType: activeTab.value,
ossAccessKeyId: '',
ossAccessKeySecret: '',
ossEndpoint: '',
ossBucket: '',
ossDomain: '',
qcloudSecretId: '',
qcloudSecretKey: '',
qcloudRegion: '',
qcloudBucket: '',
qcloudDomain: '',
qiniuAccessKey: '',
qiniuSecretKey: '',
qiniuBucket: '',
qiniuRegion: '',
qiniuDomain: '',
isActive: activeTab.value === 'local' ? 1 : 0 // 本地存储默认启用
}
}
}
// OSS配置相关函数
const fetchOSSConfigs = async () => {
try {
ossConfigs.value = await getOSSConfigs()
// 设置默认tab优先选择已启用的配置
const activeConfig = ossConfigs.value.find(c => c.isActive === 1)
if (activeConfig) {
activeTab.value = activeConfig.storageType
} else {
activeTab.value = 'local' // 默认本地存储
}
// 加载当前tab的配置
loadConfigForTab()
} catch (error) {
console.error('Error fetching OSS configs:', error)
toast.showToast('获取OSS配置失败', 'error')
}
}
const saveOSSConfig = async () => {
// 设置存储类型
ossForm.value.storageType = activeTab.value
// 验证必填字段
if (activeTab.value === 'aliyun') {
if (!ossForm.value.ossAccessKeyId || !ossForm.value.ossAccessKeySecret || !ossForm.value.ossEndpoint || !ossForm.value.ossBucket) {
toast.showToast('请填写所有必填字段', 'error')
return
}
} else if (activeTab.value === 'qcloud') {
if (!ossForm.value.qcloudSecretId || !ossForm.value.qcloudSecretKey || !ossForm.value.qcloudRegion || !ossForm.value.qcloudBucket) {
toast.showToast('请填写所有必填字段', 'error')
return
}
} else if (activeTab.value === 'qiniu') {
if (!ossForm.value.qiniuAccessKey || !ossForm.value.qiniuSecretKey || !ossForm.value.qiniuBucket) {
toast.showToast('请填写所有必填字段', 'error')
return
}
}
isSubmitting.value = true
try {
const updateData: any = {
name: ossForm.value.name,
storageType: ossForm.value.storageType,
isActive: ossForm.value.isActive
}
// 根据存储类型设置对应字段
if (activeTab.value === 'aliyun') {
updateData.ossAccessKeyId = ossForm.value.ossAccessKeyId
updateData.ossAccessKeySecret = ossForm.value.ossAccessKeySecret
updateData.ossEndpoint = ossForm.value.ossEndpoint
updateData.ossBucket = ossForm.value.ossBucket
updateData.ossDomain = ossForm.value.ossDomain || ''
} else if (activeTab.value === 'qcloud') {
updateData.qcloudSecretId = ossForm.value.qcloudSecretId
updateData.qcloudSecretKey = ossForm.value.qcloudSecretKey
updateData.qcloudRegion = ossForm.value.qcloudRegion
updateData.qcloudBucket = ossForm.value.qcloudBucket
updateData.qcloudDomain = ossForm.value.qcloudDomain || ''
} else if (activeTab.value === 'qiniu') {
updateData.qiniuAccessKey = ossForm.value.qiniuAccessKey
updateData.qiniuSecretKey = ossForm.value.qiniuSecretKey
updateData.qiniuBucket = ossForm.value.qiniuBucket
updateData.qiniuRegion = ossForm.value.qiniuRegion || ''
updateData.qiniuDomain = ossForm.value.qiniuDomain || ''
}
if (ossForm.value.id > 0) {
// 更新
await updateOSSConfig(ossForm.value.id, updateData)
toast.showToast('OSS配置更新成功', 'success')
} else {
// 创建
await createOSSConfig({
...updateData,
createdAt: '',
updatedAt: ''
})
toast.showToast('OSS配置创建成功', 'success')
}
await fetchOSSConfigs()
} catch (error: any) {
console.error('Error saving OSS config:', error)
toast.showToast(error.message || (ossForm.value.id > 0 ? '更新OSS配置失败' : '创建OSS配置失败'), 'error')
} finally {
isSubmitting.value = false
}
}
const deleteOSSConfig = async () => {
if (!currentConfig.value) return
if (isDefaultConfig(currentConfig.value)) {
toast.showToast('默认配置不能删除', 'error')
return
}
if (confirm(`确定要删除这个OSS配置吗`)) {
try {
await deleteOSSConfigApi(currentConfig.value.id)
toast.showToast('OSS配置删除成功', 'success')
await fetchOSSConfigs()
} catch (error: any) {
console.error('Error deleting OSS config:', error)
toast.showToast(error.message || '删除OSS配置失败', 'error')
}
}
}
onMounted(() => {
fetchOSSConfigs()
})
</script>