114 lines
3.5 KiB
Vue
114 lines
3.5 KiB
Vue
<script setup lang="ts">
|
|
import { NInput } from 'naive-ui'
|
|
import { computed, reactive, ref, watch } from 'vue'
|
|
|
|
import { createInstances } from '@/api/instances'
|
|
import AppInputNumber from '@/components/AppInputNumber.vue'
|
|
import FormField from '@/components/FormField.vue'
|
|
import FormModal from '@/components/FormModal.vue'
|
|
import InstanceSpecForm from '@/components/instance/InstanceSpecForm.vue'
|
|
import { defaultResourceName } from '@/composables/useFormat'
|
|
import { useToast } from '@/composables/useToast'
|
|
|
|
/** 租户 / 区间 / 区域直接使用父页面当前值,不在表单中出现 */
|
|
const props = defineProps<{
|
|
show: boolean
|
|
cfgId: number | null
|
|
compartmentId?: string
|
|
region?: string
|
|
}>()
|
|
const emit = defineEmits<{ 'update:show': [boolean]; created: [] }>()
|
|
|
|
const message = useToast()
|
|
const submitting = ref(false)
|
|
const specForm = ref<InstanceType<typeof InstanceSpecForm> | null>(null)
|
|
|
|
const form = reactive({ displayName: '', count: 1 })
|
|
|
|
watch(
|
|
() => props.show,
|
|
(v) => {
|
|
if (!v) return
|
|
form.displayName = defaultResourceName('instance')
|
|
form.count = 1
|
|
specForm.value?.reset()
|
|
},
|
|
)
|
|
|
|
const canSubmit = computed(
|
|
() =>
|
|
props.cfgId !== null &&
|
|
Boolean(form.displayName.trim()) &&
|
|
(specForm.value?.valid ?? false),
|
|
)
|
|
|
|
async function submit() {
|
|
if (props.cfgId === null || !specForm.value) return
|
|
const spec = specForm.value.buildSpec()
|
|
if (spec.reservedPublicIpId && form.count > 1) {
|
|
message.warning('保留 IP 仅支持单台创建,请把数量改为 1')
|
|
return
|
|
}
|
|
submitting.value = true
|
|
try {
|
|
const result = await createInstances(props.cfgId, {
|
|
region: props.region || undefined,
|
|
compartmentId: props.compartmentId || undefined,
|
|
displayName: form.displayName.trim(),
|
|
count: form.count,
|
|
...spec,
|
|
})
|
|
// 后端 errors / instances 为空时序列化为 null,判空防炸
|
|
const ok = result.instances?.length ?? 0
|
|
const failed = result.errors?.length ?? 0
|
|
if (failed) message.warning(`${ok} 台创建成功,${failed} 台失败`, result.errors!.join('\n'))
|
|
else message.success(`已创建 ${ok} 台实例`)
|
|
emit('update:show', false)
|
|
emit('created')
|
|
} catch (e) {
|
|
// 逐台错误(可能多行)收进「展开详情」,标题保持简短
|
|
message.error('创建实例失败', e instanceof Error && e.message ? e.message : undefined)
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<FormModal
|
|
:show="show"
|
|
title="创建实例"
|
|
:width="600"
|
|
:submitting="submitting"
|
|
:submit-disabled="!canSubmit"
|
|
submit-text="创建"
|
|
@update:show="emit('update:show', $event)"
|
|
@submit="submit"
|
|
>
|
|
<div class="grid grid-cols-2 gap-3 max-md:grid-cols-1">
|
|
<FormField label="实例名" required>
|
|
<NInput v-model:value="form.displayName" placeholder="instance-20260703-2136" />
|
|
</FormField>
|
|
<FormField label="数量" hint="批量创建,名称自动追加序号">
|
|
<AppInputNumber v-model:value="form.count" :min="1" :max="10" class="w-full" />
|
|
</FormField>
|
|
</div>
|
|
<InstanceSpecForm
|
|
ref="specForm"
|
|
sectioned
|
|
auto-pick-ad
|
|
:cfg-id="cfgId"
|
|
:region="region"
|
|
:compartment-id="compartmentId"
|
|
/>
|
|
<div
|
|
v-if="specForm?.summary"
|
|
class="mt-3 rounded-lg border border-line-soft bg-wash px-3 py-2 text-xs leading-relaxed"
|
|
>
|
|
<span class="font-medium text-ink-2">
|
|
预估规格:{{ form.count }} 台 · {{ specForm.summary }}
|
|
</span>
|
|
</div>
|
|
</FormModal>
|
|
</template>
|