初始化2
This commit is contained in:
568
app/pages/developer/resources/storage.vue
Normal file
568
app/pages/developer/resources/storage.vue
Normal file
@@ -0,0 +1,568 @@
|
||||
<template>
|
||||
<div class="dev-page">
|
||||
<div class="page-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<a-select v-model:value="selectedAppId" placeholder="全部应用" allow-clear style="width: 180px" @change="loadList">
|
||||
<a-select-option v-for="app in appOptions" :key="app.productId" :value="app.productId">{{ app.productName }}</a-select-option>
|
||||
</a-select>
|
||||
<PermissionGuard :app-id="selectedAppId" permission="canEditResource" :show-tip="true">
|
||||
<a-button type="primary" @click="showAdd = true">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加存储桶
|
||||
</a-button>
|
||||
</PermissionGuard>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索存储桶名称"
|
||||
style="width: 240px"
|
||||
@search="loadList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notice-bar">
|
||||
<InfoCircleOutlined class="notice-icon" />
|
||||
<span>在此管理云对象存储(OSS/COS)存储桶,将文件存储资源关联到对应应用。</span>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="list"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
row-key="resourceId"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
<div v-if="record.status === 'failed' && record.remark" style="font-size: 12px; color: #ff4d4f;">
|
||||
{{ record.remark }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'acl'">
|
||||
<a-tag :color="record.acl === 'public-read' ? 'green' : 'default'">
|
||||
{{ record.acl === 'public-read' ? '公开读' : '私有' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'usedBytes'">
|
||||
{{ formatSize(record.usedBytes) }}
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleRefresh(record)">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button v-if="record.isOwner || getAppPermission(record.appId)?.canEditResource" type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-divider v-if="record.isOwner || getAppPermission(record.appId)?.canEditResource" type="vertical" />
|
||||
<a-popconfirm v-if="record.isOwner || getAppPermission(record.appId)?.canEditResource" title="确定要移除此存储桶?" @confirm="handleDelete(record.resourceId)">
|
||||
<a-button type="link" size="small" danger>移除</a-button>
|
||||
</a-popconfirm>
|
||||
<span v-if="!record.isOwner && !getAppPermission(record.appId)?.canEditResource" style="font-size: 12px; color: #faad14;">
|
||||
<LockOutlined /> 只读
|
||||
</span>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<a-modal
|
||||
v-model:open="showAdd"
|
||||
:title="editRecord ? '编辑存储桶' : '添加存储桶'"
|
||||
ok-text="保存"
|
||||
cancel-text="取消"
|
||||
:confirm-loading="saveLoading"
|
||||
@ok="handleSave"
|
||||
@cancel="resetForm"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical" style="margin-top: 8px">
|
||||
<a-form-item label="存储桶名称" name="name">
|
||||
<a-input v-model:value="form.name" placeholder="如:assets-bucket" :disabled="!!editRecord" />
|
||||
</a-form-item>
|
||||
<a-form-item label="服务商" name="provider">
|
||||
<a-select v-model:value="form.provider" placeholder="请选择" :disabled="!!editRecord" @change="handleProviderChange">
|
||||
<a-select-option v-for="opt in providerOptions" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="云账号凭证" name="credentialId">
|
||||
<a-select v-model:value="form.credentialId" placeholder="请先选择服务商" :disabled="!!editRecord" allow-clear>
|
||||
<a-select-option v-for="cred in credentialOptions" :key="cred.id" :value="cred.id">
|
||||
{{ cred.name }} ({{ cred.accessKeyId?.slice(0, 8) }}***)
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="所在地区" name="region">
|
||||
<a-select
|
||||
v-model:value="form.region"
|
||||
placeholder="请选择地区"
|
||||
show-search
|
||||
:filter-option="(input, option) => option.label.toLowerCase().includes(input.toLowerCase())"
|
||||
allow-clear
|
||||
:disabled="!!editRecord"
|
||||
>
|
||||
<a-select-option v-for="opt in getRegionOptions(form.provider)" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="访问权限" name="acl">
|
||||
<a-radio-group v-model:value="form.acl">
|
||||
<a-radio value="public-read">公开读</a-radio>
|
||||
<a-radio value="private">私有</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="关联应用" name="appId">
|
||||
<a-select v-model:value="form.appId" placeholder="可选,关联到应用" allow-clear>
|
||||
<a-select-option v-for="app in appOptions" :key="app.productId" :value="app.productId">
|
||||
{{ app.productName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea v-model:value="form.remark" :rows="2" placeholder="可选备注" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 删除确认弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="showVerifyDelete"
|
||||
title="确认删除"
|
||||
ok-text="确认删除"
|
||||
cancel-text="取消"
|
||||
:confirm-loading="deleteLoading"
|
||||
@ok="confirmDelete"
|
||||
>
|
||||
<div style="text-align: center; padding: 16px 0;">
|
||||
<p style="margin-bottom: 16px; color: #ff4d4f; font-size: 14px;">
|
||||
<ExclamationCircleOutlined /> 此操作不可恢复,请谨慎操作!
|
||||
</p>
|
||||
<p style="margin-bottom: 8px;">请输入手机验证码确认删除</p>
|
||||
<p style="margin-bottom: 16px; font-size: 12px; color: #666;">
|
||||
验证码将发送至测试手机号:13737128880
|
||||
</p>
|
||||
<div style="display: flex; gap: 8px; justify-content: center;">
|
||||
<a-input
|
||||
v-model:value="verifyCode"
|
||||
placeholder="请输入验证码"
|
||||
style="width: 120px;"
|
||||
@pressEnter="confirmDelete"
|
||||
/>
|
||||
<a-button @click="sendVerifyCode" :disabled="verifyCountdown > 0">
|
||||
{{ verifyCountdown > 0 ? `${verifyCountdown}s` : '发送验证码' }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PlusOutlined, InfoCircleOutlined, LockOutlined, ReloadOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { pageAppResource, addAppResource, updateAppResource, removeAppResource, refreshStorage } from '@/api/app/appResource'
|
||||
import { getMyAccessibleApps, getAppProduct } from '@/api/app/appProduct'
|
||||
import { sendSmsCaptcha } from '@/api/passport/login'
|
||||
import { useAppPermission } from '@/composables/useAppPermission'
|
||||
import PermissionGuard from '@/components/developer/PermissionGuard.vue'
|
||||
import { pageCloudCredential } from '@/api/app/cloudCredential'
|
||||
import type { AppResource } from '@/api/app/appResource/model'
|
||||
import type { AppCloudCredential } from '@/api/app/cloudCredential/model'
|
||||
import { enrichResourcesWithPermission } from '@/composables/useResourceAccess'
|
||||
|
||||
definePageMeta({ layout: 'developer' })
|
||||
useHead({ title: '云存储管理 - 开发者中心' })
|
||||
|
||||
const loading = ref(false)
|
||||
const saveLoading = ref(false)
|
||||
const showAdd = ref(false)
|
||||
const searchText = ref('')
|
||||
const editRecord = ref<any>(null)
|
||||
const formRef = ref()
|
||||
const selectedAppId = ref<number | undefined>(undefined)
|
||||
const { getAppPermission } = useAppPermission()
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
provider: undefined as string | undefined,
|
||||
credentialId: undefined as number | undefined,
|
||||
region: '',
|
||||
acl: 'private',
|
||||
appId: undefined as number | undefined,
|
||||
remark: '',
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入存储桶名称' }],
|
||||
provider: [{ required: true, message: '请选择服务商' }],
|
||||
credentialId: [{ required: true, message: '请选择云账号凭证' }],
|
||||
region: [{ required: true, message: '请输入所在地区' }],
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ title: '存储桶名称', dataIndex: 'name', key: 'name', width: 150 },
|
||||
{ title: '服务商', dataIndex: 'provider', key: 'provider', width: 100, customRender: ({ record }) => getProviderLabel(record.provider) },
|
||||
{ title: '地区', dataIndex: 'region', key: 'region', width: 140, customRender: ({ record }) => getRegionLabel(record.provider, record.region) },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 90 },
|
||||
{ title: '访问权限', dataIndex: 'acl', key: 'acl', width: 80 },
|
||||
{ title: '已用空间', dataIndex: 'usedBytes', key: 'usedBytes', width: 90 },
|
||||
{ title: '对象数', dataIndex: 'usedCount', key: 'objectCount', width: 70 },
|
||||
// { title: '关联应用', dataIndex: 'appName', key: 'appName', width: 120 },
|
||||
{ title: '操作', key: 'action', width: 100 },
|
||||
]
|
||||
|
||||
// 获取服务商显示标签
|
||||
function getProviderLabel(provider: string): string {
|
||||
const map: Record<string, string> = {
|
||||
aliyun: '阿里云',
|
||||
tencent: '腾讯云',
|
||||
huawei: '华为云',
|
||||
qiniu: '七牛云',
|
||||
}
|
||||
return map[provider] || provider
|
||||
}
|
||||
|
||||
// 获取地区显示标签
|
||||
function getRegionLabel(provider: string, region: string): string {
|
||||
if (!region) return '-'
|
||||
const opts = getRegionOptions(provider)
|
||||
const found = opts.find(o => o.value === region)
|
||||
return found?.label || region
|
||||
}
|
||||
|
||||
const pagination = reactive({ current: 1, pageSize: 10, total: 0 })
|
||||
const list = ref<AppResource[]>([])
|
||||
const appOptions = ref<any[]>([])
|
||||
const credentialOptions = ref<AppCloudCredential[]>([])
|
||||
|
||||
// 服务商选项
|
||||
const providerOptions = [
|
||||
{ label: '☁️ 阿里云 OSS', value: 'aliyun' },
|
||||
{ label: '🔵 腾讯云 COS', value: 'tencent' },
|
||||
{ label: '🟠 华为云 OBS', value: 'huawei' },
|
||||
{ label: '🟡 七牛云 Kodo', value: 'qiniu' },
|
||||
]
|
||||
|
||||
// 阿里云 OSS 地区选项
|
||||
const aliyunRegions = [
|
||||
{ label: '华东 1(杭州)', value: 'oss-cn-hangzhou' },
|
||||
{ label: '华东 2(上海)', value: 'oss-cn-shanghai' },
|
||||
{ label: '华南 1(深圳)', value: 'oss-cn-shenzhen' },
|
||||
{ label: '华南 2(广州)', value: 'oss-cn-guangzhou' },
|
||||
{ label: '华北 2(北京)', value: 'oss-cn-beijing' },
|
||||
{ label: '华北 3(张家口)', value: 'oss-cn-zhangjiakou' },
|
||||
{ label: '华北 5(呼和浩特)', value: 'oss-cn-huhehaote' },
|
||||
{ label: '西南 1(成都)', value: 'oss-cn-chengdu' },
|
||||
{ label: '香港', value: 'oss-cn-hongkong' },
|
||||
{ label: '新加坡', value: 'oss-ap-southeast-1' },
|
||||
{ label: '日本(东京)', value: 'oss-ap-northeast-1' },
|
||||
{ label: '韩国(首尔)', value: 'oss-ap-northeast-2' },
|
||||
{ label: '美国(弗吉尼亚)', value: 'oss-us-east-1' },
|
||||
{ label: '美国(硅谷)', value: 'oss-us-west-1' },
|
||||
{ label: '德国(法兰克福)', value: 'oss-eu-central-1' },
|
||||
{ label: '英国(伦敦)', value: 'oss-eu-west-1' },
|
||||
]
|
||||
|
||||
// 腾讯云 COS 地区选项
|
||||
const tencentRegions = [
|
||||
{ label: '华北地区(北京)', value: 'ap-beijing' },
|
||||
{ label: '华南地区(广州)', value: 'ap-guangzhou' },
|
||||
{ label: '华南地区(深圳)', value: 'ap-shenzhen' },
|
||||
{ label: '华东地区(上海)', value: 'ap-shanghai' },
|
||||
{ label: '西南地区(成都)', value: 'ap-chengdu' },
|
||||
{ label: '西南地区(重庆)', value: 'ap-chongqing' },
|
||||
{ label: '港澳台地区(香港)', value: 'ap-hongkong' },
|
||||
{ label: '亚太东南(新加坡)', value: 'ap-singapore' },
|
||||
{ label: '亚太东南(曼谷)', value: 'ap-bangkok' },
|
||||
{ label: '亚太南部(孟买)', value: 'ap-mumbai' },
|
||||
{ label: '亚太东北(东京)', value: 'ap-tokyo' },
|
||||
{ label: '美国东部(弗吉尼亚)', value: 'na-ashburn' },
|
||||
{ label: '美国西部(硅谷)', value: 'na-siliconvalley' },
|
||||
{ label: '欧洲地区(法兰克福)', value: 'eu-frankfurt' },
|
||||
{ label: '欧洲地区(伦敦)', value: 'eu-london' },
|
||||
]
|
||||
|
||||
// 华为云 OBS 地区选项
|
||||
const huaweiRegions = [
|
||||
{ label: '华北-北京一', value: 'cn-north-1' },
|
||||
{ label: '华北-北京四', value: 'cn-north-4' },
|
||||
{ label: '华东-上海一', value: 'cn-east-2' },
|
||||
{ label: '华南-广州', value: 'cn-south-1' },
|
||||
{ label: '西南-贵阳一', value: 'cn-southwest-2' },
|
||||
{ label: '香港', value: 'cn-hongkong' },
|
||||
{ label: '亚太-新加坡', value: 'ap-southeast-1' },
|
||||
{ label: '亚太-曼谷', value: 'ap-southeast-2' },
|
||||
{ label: '非洲-约翰内斯堡', value: 'af-south-1' },
|
||||
{ label: '欧洲-巴黎', value: 'eu-west-1' },
|
||||
{ label: '欧洲-法兰克福', value: 'eu-central-1' },
|
||||
{ label: '拉美-圣保罗', value: 'sa-brazil-1' },
|
||||
]
|
||||
|
||||
// 根据服务商获取地区选项
|
||||
function getRegionOptions(provider: string) {
|
||||
if (provider === 'aliyun') return aliyunRegions
|
||||
if (provider === 'tencent') return tencentRegions
|
||||
if (provider === 'huawei') return huaweiRegions
|
||||
return []
|
||||
}
|
||||
|
||||
// 加载云账号凭证列表
|
||||
async function loadCredentials(provider?: string) {
|
||||
try {
|
||||
const result = await pageCloudCredential({ provider, page: 1, limit: 100 })
|
||||
credentialOptions.value = result.list || []
|
||||
} catch {
|
||||
credentialOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function formatSize(bytes: number): string {
|
||||
if (!bytes) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
||||
}
|
||||
|
||||
function getStatusColor(status: string): string {
|
||||
if (status === 'running') return 'success'
|
||||
if (status === 'pending') return 'processing'
|
||||
if (status === 'failed') return 'error'
|
||||
return 'default'
|
||||
}
|
||||
|
||||
function getStatusText(status: string): string {
|
||||
if (status === 'running') return '运行中'
|
||||
if (status === 'pending') return '创建中'
|
||||
if (status === 'failed') return '创建失败'
|
||||
return status || '未知'
|
||||
}
|
||||
|
||||
// 选择服务商后加载对应凭证
|
||||
function handleProviderChange(provider: string) {
|
||||
form.credentialId = undefined
|
||||
if (provider) {
|
||||
loadCredentials(provider)
|
||||
} else {
|
||||
credentialOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 加载应用下拉列表(仅当前用户可访问的应用)
|
||||
async function loadAppOptions() {
|
||||
try {
|
||||
appOptions.value = await getMyAccessibleApps()
|
||||
}
|
||||
catch {
|
||||
appOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await pageAppResource({
|
||||
resourceType: 'storage',
|
||||
keywords: searchText.value || undefined,
|
||||
appId: selectedAppId.value,
|
||||
page: pagination.current,
|
||||
limit: pagination.pageSize,
|
||||
})
|
||||
list.value = enrichResourcesWithPermission(result?.list ?? [])
|
||||
pagination.total = result?.count ?? 0
|
||||
}
|
||||
catch (e: any) {
|
||||
message.error(e.message || '加载失败')
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleTableChange(pag: any) {
|
||||
pagination.current = pag.current
|
||||
loadList()
|
||||
}
|
||||
|
||||
function handleEdit(record: AppResource) {
|
||||
if (!record.isOwner) {
|
||||
message.warning('只有资源创建者才能编辑')
|
||||
return
|
||||
}
|
||||
editRecord.value = record
|
||||
// 先加载对应服务商的凭证
|
||||
if (record.provider) {
|
||||
loadCredentials(record.provider)
|
||||
}
|
||||
Object.assign(form, {
|
||||
name: record.name,
|
||||
provider: record.provider,
|
||||
credentialId: record.credentialId ? Number(record.credentialId) : undefined,
|
||||
region: record.region,
|
||||
acl: record.acl || 'private',
|
||||
appId: record.appId ? Number(record.appId) : undefined,
|
||||
remark: record.remark,
|
||||
resourceId: record.resourceId,
|
||||
})
|
||||
showAdd.value = true
|
||||
}
|
||||
|
||||
// 删除存储桶(需要验证码)
|
||||
const showVerifyDelete = ref(false)
|
||||
const verifyResourceId = ref<number>(0)
|
||||
const verifyCode = ref('')
|
||||
const verifyTargetPhone = ref('')
|
||||
const verifyTargetUser = ref('')
|
||||
|
||||
async function handleDelete(resourceId: number) {
|
||||
const item = list.value.find(r => r.resourceId === resourceId)
|
||||
if (item && !item.isOwner) {
|
||||
message.warning('只有资源创建者才能删除')
|
||||
return
|
||||
}
|
||||
// 确认删除操作
|
||||
showVerifyDelete.value = true
|
||||
verifyResourceId.value = resourceId
|
||||
verifyCode.value = ''
|
||||
verifyTargetPhone.value = ''
|
||||
verifyTargetUser.value = ''
|
||||
|
||||
// 获取应用创建者信息
|
||||
if (item?.appId) {
|
||||
try {
|
||||
const app = await getAppProduct(item.appId)
|
||||
if (app?.developerPhone) {
|
||||
verifyTargetPhone.value = app.developerPhone
|
||||
verifyTargetUser.value = app.developer || '应用创建者'
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// 静默失败,后续会检查手机号
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deleteLoading = ref(false)
|
||||
const verifyCountdown = ref(0)
|
||||
|
||||
async function sendVerifyCode() {
|
||||
if (verifyCountdown.value > 0) return
|
||||
try {
|
||||
// 测试阶段:验证码发送给固定手机号
|
||||
const phone = '13737128880'
|
||||
await sendSmsCaptcha({ phone })
|
||||
message.success('验证码已发送(测试手机号:13737128880)')
|
||||
verifyCountdown.value = 60
|
||||
const timer = setInterval(() => {
|
||||
verifyCountdown.value--
|
||||
if (verifyCountdown.value <= 0) clearInterval(timer)
|
||||
}, 1000)
|
||||
}
|
||||
catch (e: any) {
|
||||
message.error(e.message || '发送失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
if (!verifyCode.value) {
|
||||
message.warning('请输入短信验证码')
|
||||
return
|
||||
}
|
||||
deleteLoading.value = true
|
||||
try {
|
||||
await removeAppResource(verifyResourceId.value, verifyCode.value)
|
||||
message.success('已移除')
|
||||
showVerifyDelete.value = false
|
||||
loadList()
|
||||
}
|
||||
catch (e: any) {
|
||||
message.error(e.message || '删除失败')
|
||||
}
|
||||
finally {
|
||||
deleteLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRefresh(record: AppResource) {
|
||||
try {
|
||||
const result = await refreshStorage(record.resourceId!)
|
||||
// 更新本地数据
|
||||
const index = list.value.findIndex(r => r.resourceId === record.resourceId)
|
||||
if (index !== -1) {
|
||||
list.value[index].usedBytes = result.usedBytes
|
||||
list.value[index].usedCount = result.objectCount
|
||||
}
|
||||
message.success('刷新成功')
|
||||
}
|
||||
catch (e: any) {
|
||||
message.error(e.message || '刷新失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
await formRef.value?.validate()
|
||||
saveLoading.value = true
|
||||
try {
|
||||
const payload: AppResource = {
|
||||
resourceType: 'storage',
|
||||
name: form.name,
|
||||
provider: form.provider,
|
||||
credentialId: form.credentialId ? Number(form.credentialId) : undefined,
|
||||
region: form.region,
|
||||
acl: form.acl,
|
||||
appId: form.appId ? Number(form.appId) : undefined,
|
||||
remark: form.remark,
|
||||
}
|
||||
if (editRecord.value) {
|
||||
payload.resourceId = editRecord.value.resourceId
|
||||
await updateAppResource(payload)
|
||||
message.success('保存成功')
|
||||
}
|
||||
else {
|
||||
await addAppResource(payload)
|
||||
message.success('添加成功,存储桶正在创建中...')
|
||||
}
|
||||
showAdd.value = false
|
||||
resetForm()
|
||||
loadList()
|
||||
}
|
||||
catch (e: any) {
|
||||
message.error(e.message || '操作失败')
|
||||
}
|
||||
finally {
|
||||
saveLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
editRecord.value = null
|
||||
formRef.value?.resetFields()
|
||||
Object.assign(form, { name: '', provider: undefined, credentialId: undefined, region: '', acl: 'private', appId: undefined, remark: '' })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadAppOptions()
|
||||
loadList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dev-page { padding: 24px; max-width: 1100px; }
|
||||
.page-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
||||
.toolbar-left { display: flex; align-items: center; gap: 12px; }
|
||||
.toolbar-right { display: flex; gap: 8px; }
|
||||
.notice-bar {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
background: #e6f4ff; border: 1px solid #91caff;
|
||||
border-radius: 6px; padding: 8px 14px; margin-bottom: 16px;
|
||||
font-size: 13px; color: #1677ff;
|
||||
}
|
||||
.notice-icon { font-size: 15px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user