Files
tiantian-system/app/pages/developer/cloudCredentials.vue
2026-04-08 17:10:58 +08:00

420 lines
12 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="dev-page">
<!-- 页面头部 -->
<div class="page-header">
<div>
<h2 class="page-title"> 云账号凭证</h2>
<p class="page-desc">管理阿里云腾讯云华为云等云服务商的账号凭证用于创建存储桶和云资源</p>
</div>
<a-button type="primary" @click="showCreateModal = true">
+ 添加云账号
</a-button>
</div>
<div class="page-body">
<!-- 使用提示 -->
<a-alert
class="mb-5"
show-icon
type="info"
message="凭证说明"
description="云账号凭证用于调用云服务商 API 创建存储桶等资源。AccessKeySecret 会被加密存储,不会以明文形式返回。请妥善保管,丢失后需重新创建。"
/>
<!-- 凭证列表 -->
<div class="panel">
<div class="panel-header">
<span class="panel-title">我的云账号凭证</span>
<a-tag color="blue">{{ credentialList.length }} </a-tag>
</div>
<a-spin :spinning="loading">
<div v-if="credentialList.length === 0 && !loading" class="empty-state">
<div class="empty-icon"></div>
<div class="empty-title">还没有云账号凭证</div>
<div class="empty-desc">添加云服务商账号开始创建云存储桶</div>
<a-button type="primary" class="mt-4" @click="showCreateModal = true">添加云账号</a-button>
</div>
<a-table
v-else
:columns="columns"
:data-source="credentialList"
:pagination="false"
row-key="id"
class="credential-table"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'provider'">
<span class="provider-cell">
<!-- <span class="provider-icon">{{ getProviderIcon(record.provider) }}</span>-->
{{ getProviderLabel(record.provider) }}
</span>
</template>
<template v-else-if="column.key === 'status'">
<a-tag :color="record.status === 1 ? 'green' : 'default'">
{{ record.status === 1 ? '启用' : '禁用' }}
</a-tag>
</template>
<template v-else-if="column.key === 'testStatus'">
<a-tag v-if="record.testStatus === 1" color="green">
<template #icon><CheckCircleOutlined /></template>
连接正常
</a-tag>
<a-tag v-else-if="record.testStatus === 2" color="error">
<template #icon><CloseCircleOutlined /></template>
连接失败
</a-tag>
<a-tag v-else color="default">
<template #icon><QuestionCircleOutlined /></template>
未测试
</a-tag>
</template>
<template v-else-if="column.key === 'actions'">
<a-space>
<a-tooltip title="测试连接">
<a-button size="small" :loading="record.testing" @click="testConnection(record)">
<template #icon><ApiOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip title="编辑">
<a-button size="small" @click="editCredential(record)">
<template #icon><EditOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip :title="record.status === 1 ? '禁用' : '启用'">
<a-button size="small" @click="toggleStatus(record)">
<template #icon><StopOutlined v-if="record.status === 1" /><CheckCircleOutlined v-else /></template>
</a-button>
</a-tooltip>
<a-popconfirm
title="确认删除该云账号凭证?此操作不可撤销。"
ok-text="删除"
ok-type="danger"
cancel-text="取消"
@confirm="deleteCredential(record.id)"
>
<a-button size="small" danger>
<template #icon><DeleteOutlined /></template>
</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</a-spin>
</div>
</div>
<!-- 创建/编辑云账号凭证弹窗 -->
<a-modal
v-model:open="showCreateModal"
:title="editingId ? '编辑云账号凭证' : '添加云账号凭证'"
:ok-text="editingId ? '保存' : '创建'"
cancel-text="取消"
:confirm-loading="saving"
@ok="handleSave"
>
<a-form layout="vertical" class="mt-2">
<a-form-item label="云服务商" required>
<a-select
v-model:value="form.provider"
placeholder="选择云服务商"
:disabled="!!editingId"
>
<a-select-option v-for="opt in providerOptions" :key="opt.value" :value="opt.value">
<span>{{ opt.icon }} {{ opt.label }}</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="凭证名称" required>
<a-input
v-model:value="form.name"
placeholder="例如:阿里云生产账号、腾讯云测试账号..."
:maxlength="50"
show-count
/>
</a-form-item>
<a-form-item label="AccessKeyId" required>
<a-input
v-model:value="form.accessKeyId"
placeholder="请输入 AccessKeyId"
:maxlength="100"
/>
</a-form-item>
<a-form-item label="AccessKeySecret" required>
<a-input-password
v-model:value="form.accessKeySecret"
placeholder="请输入 AccessKeySecret"
:maxlength="200"
/>
</a-form-item>
<a-form-item label="备注">
<a-textarea
v-model:value="form.remark"
:rows="2"
placeholder="可选,用途说明"
/>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import {
CheckCircleOutlined,
CloseCircleOutlined,
QuestionCircleOutlined,
ApiOutlined,
EditOutlined,
DeleteOutlined,
StopOutlined,
} from '@ant-design/icons-vue'
import {
pageCloudCredential,
createCloudCredential,
updateCloudCredential,
removeCloudCredential,
testCloudCredential,
CLOUD_PROVIDER_OPTIONS,
getProviderLabel,
getProviderIcon,
} from '@/api/app/cloudCredential'
import type { AppCloudCredential, AppCloudCredentialParam } from '@/api/app/cloudCredential/model'
definePageMeta({ layout: 'developer' })
useHead({ title: '云账号凭证 - 开发者中心' })
const columns = [
// { title: '名称', dataIndex: 'name', key: 'name', width: 180 },
{ title: '服务商', key: 'provider', width: 140 },
{ title: 'AK', dataIndex: 'accessKeyId', key: 'accessKeyId', width: 180 },
{ title: '状态', key: 'status', width: 80 },
{ title: '连接测试', key: 'testStatus', width: 100 },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 160 },
{ title: '操作', key: 'actions', width: 200 },
]
const providerOptions = CLOUD_PROVIDER_OPTIONS
const showCreateModal = ref(false)
const editingId = ref<number | null>(null)
const loading = ref(false)
const saving = ref(false)
const form = reactive({
provider: '',
name: '',
accessKeyId: '',
accessKeySecret: '',
remark: '',
})
// 凭证列表
const credentialList = ref<AppCloudCredential[]>([])
// 加载凭证列表
async function loadCredentials() {
loading.value = true
try {
const params: AppCloudCredentialParam = { page: 1, limit: 100 }
console.log('请求参数:', params)
const result = await pageCloudCredential(params)
console.log('返回结果:', result)
console.log('列表数据:', result?.list)
credentialList.value = (result?.list || []).map((item: any) => ({
...item,
testing: false,
}))
console.log('最终数据:', credentialList.value)
} catch (error: any) {
console.error('加载云账号凭证失败:', error)
console.error('错误详情:', error.response?.data, error.config?.url)
message.error(error.message || '加载失败,请稍后重试')
} finally {
loading.value = false
}
}
// 测试连接
async function testConnection(record: AppCloudCredential) {
record.testing = true
try {
const result = await testCloudCredential(record.id!)
if (result?.success) {
message.success(result.message || '连接测试成功')
record.testStatus = 1
record.testMessage = result.message
} else {
message.error(result?.message || '连接测试失败')
record.testStatus = 2
record.testMessage = result?.message
}
} catch (error: any) {
console.error('测试连接失败:', error)
message.error(error.message || '测试连接失败')
record.testStatus = 2
record.testMessage = error.message
} finally {
record.testing = false
}
}
// 编辑凭证
function editCredential(record: AppCloudCredential) {
editingId.value = record.id || null
form.provider = record.provider || ''
form.name = record.name || ''
form.accessKeyId = ''
form.accessKeySecret = ''
form.remark = record.remark || ''
showCreateModal.value = true
}
// 切换状态
async function toggleStatus(record: AppCloudCredential) {
try {
const newStatus = record.status === 1 ? 0 : 1
await updateCloudCredential({
id: record.id,
status: newStatus,
})
record.status = newStatus
message.success(newStatus === 1 ? '已启用' : '已禁用')
} catch (error: any) {
console.error('更新状态失败:', error)
message.error(error.message || '操作失败,请稍后重试')
}
}
// 删除凭证
async function deleteCredential(id: number) {
try {
await removeCloudCredential(id)
await loadCredentials()
message.success('云账号凭证已删除')
} catch (error: any) {
console.error('删除凭证失败:', error)
message.error(error.message || '删除失败,请稍后重试')
}
}
// 保存凭证
async function handleSave() {
if (!form.provider) {
message.error('请选择云服务商')
return
}
if (!form.name.trim()) {
message.error('请输入凭证名称')
return
}
if (!form.accessKeyId.trim()) {
message.error('请输入 AccessKeyId')
return
}
if (!form.accessKeySecret.trim()) {
message.error('请输入 AccessKeySecret')
return
}
saving.value = true
try {
if (editingId.value) {
await updateCloudCredential({
id: editingId.value,
name: form.name,
accessKeyId: form.accessKeyId || undefined,
accessKeySecret: form.accessKeySecret || undefined,
remark: form.remark,
})
message.success('云账号凭证已更新')
} else {
await createCloudCredential({
provider: form.provider,
name: form.name,
accessKeyId: form.accessKeyId,
accessKeySecret: form.accessKeySecret,
remark: form.remark,
status: 1,
})
message.success('云账号凭证已创建')
}
showCreateModal.value = false
resetForm()
await loadCredentials()
} catch (error: any) {
console.error('保存云账号凭证失败:', error)
message.error(error.message || '保存失败,请稍后重试')
} finally {
saving.value = false
}
}
function resetForm() {
editingId.value = null
form.provider = ''
form.name = ''
form.accessKeyId = ''
form.accessKeySecret = ''
form.remark = ''
}
onMounted(() => {
loadCredentials()
})
</script>
<style scoped>
.dev-page {
@apply p-6;
}
.page-header {
@apply flex justify-between items-start mb-6;
}
.page-title {
@apply text-2xl font-bold m-0;
}
.page-desc {
@apply text-gray-500 mt-1;
}
.page-body {
@apply max-w-5xl;
}
.provider-cell {
@apply flex items-center gap-2;
}
.provider-icon {
@apply text-lg;
}
.credential-table {
@apply mt-4;
}
.empty-state {
@apply py-12 text-center;
}
.empty-icon {
@apply text-5xl mb-4;
}
.empty-title {
@apply text-lg font-medium text-gray-700;
}
.empty-desc {
@apply text-gray-500 mt-1 mb-4;
}
</style>