Files
front/src/views/ops/pages/datacenter/rack/components/RackFormDialog.vue
2026-03-27 19:26:10 +08:00

807 lines
23 KiB
Vue

<template>
<a-modal
:visible="visible"
:title="isEdit ? '编辑机柜' : '新建机柜'"
width="800px"
@ok="handleOk"
@cancel="handleCancel"
@update:visible="handleVisibleChange"
:confirm-loading="submitting"
>
<a-form :model="form" layout="vertical" ref="formRef">
<!-- 基础信息 -->
<a-divider orientation="left">基础信息</a-divider>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item
label="机柜名称"
field="name"
:rules="[{ required: true, message: '请输入机柜名称' }]"
>
<a-input
v-model="form.name"
placeholder="请输入机柜名称"
:max-length="200"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="机柜编码"
field="code"
:rules="[{ required: true, message: '请输入机柜编码' }]"
>
<a-input
v-model="form.code"
placeholder="请输入机柜编码"
:max-length="100"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item
label="所属中心"
field="datacenter_id"
:rules="[{ required: true, message: '请选择所属中心' }]"
>
<a-select
v-model="form.datacenter_id"
placeholder="请选择所属中心"
:loading="loadingDatacenters"
allow-search
@change="handleDatacenterChange"
>
<a-option
v-for="item in datacenterList"
:key="item.id"
:value="item.id"
>
{{ item.name }}
</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="所属楼层"
field="floor_id"
:rules="[{ required: true, message: '请选择所属楼层' }]"
>
<a-select
v-model="form.floor_id"
placeholder="请选择所属楼层"
:loading="loadingFloors"
allow-search
@search="handleFloorSearch"
>
<a-option
v-for="item in floorList"
:key="item.id"
:value="item.id"
>
{{ item.name }}
</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<!-- 规格参数 -->
<a-divider orientation="left">规格参数</a-divider>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="高度(U)" field="height">
<a-input-number
v-model="form.height"
placeholder="默认42"
:min="1"
:max="100"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="宽度(mm)" field="width">
<a-input-number
v-model="form.width"
placeholder="请输入宽度"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="深度(mm)" field="depth">
<a-input-number
v-model="form.depth"
placeholder="请输入深度"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="重量(kg)" field="weight">
<a-input-number
v-model="form.weight"
placeholder="请输入重量"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="最大承重(kg)" field="max_load">
<a-input-number
v-model="form.max_load"
placeholder="请输入最大承重"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="电力容量(KW)" field="power_capacity">
<a-input-number
v-model="form.power_capacity"
placeholder="请输入电力容量"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="电源相位" field="power_phase">
<a-select v-model="form.power_phase" placeholder="请选择电源相位">
<a-option value="单相">单相</a-option>
<a-option value="三相">三相</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="PDU数量" field="pdu_count">
<a-input-number
v-model="form.pdu_count"
placeholder="请输入PDU数量"
:min="0"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="网络接入方式" field="network_access">
<a-input
v-model="form.network_access"
placeholder="请输入网络接入方式"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="交换机端口数" field="switch_ports">
<a-input-number
v-model="form.switch_ports"
placeholder="请输入交换机端口数"
:min="0"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="制冷方式" field="cooling_type">
<a-input
v-model="form.cooling_type"
placeholder="请输入制冷方式"
/>
</a-form-item>
<!-- 状态信息 -->
<a-divider orientation="left">状态信息</a-divider>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="机柜类型" field="rack_type">
<a-select v-model="form.rack_type" placeholder="请选择机柜类型">
<a-option value="standard">标准机柜</a-option>
<a-option value="blade">刀片机柜</a-option>
<a-option value="network">网络机柜</a-option>
<a-option value="storage">存储机柜</a-option>
<a-option value="custom">定制机柜</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="状态" field="status">
<a-select v-model="form.status" placeholder="请选择状态">
<a-option value="idle">空闲</a-option>
<a-option value="in_use">使用中</a-option>
<a-option value="reserved">已预留</a-option>
<a-option value="maintenance">维护中</a-option>
<a-option value="offline">已下线</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item
label="供应商"
field="supplier_id"
>
<a-select
v-model="form.supplier_id"
placeholder="请选择供应商"
:loading="loadingSuppliers"
allow-search
>
<a-option
v-for="item in supplierList"
:key="item.id"
:value="item.id"
>
{{ item.name }}
</a-option>
</a-select>
</a-form-item>
<!-- 制造商信息 -->
<a-divider orientation="left">制造商信息</a-divider>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="制造商" field="manufacturer">
<a-input
v-model="form.manufacturer"
placeholder="请输入制造商"
:max-length="100"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="型号" field="model">
<a-input
v-model="form.model"
placeholder="请输入型号"
:max-length="100"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="序列号" field="serial_number">
<a-input
v-model="form.serial_number"
placeholder="请输入序列号"
:max-length="100"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="价格(元)" field="price">
<a-input-number
v-model="form.price"
placeholder="请输入价格"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="采购日期" field="purchase_date">
<a-date-picker
v-model="form.purchase_date"
placeholder="请选择采购日期"
style="width: 100%"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</a-form-item>
<!-- 责任人信息 -->
<a-divider orientation="left">责任人信息</a-divider>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="负责人" field="owner">
<a-input
v-model="form.owner"
placeholder="请输入负责人"
:max-length="50"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="联系电话" field="contact_phone">
<a-input
v-model="form.contact_phone"
placeholder="请输入联系电话"
:max-length="20"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="使用部门" field="department">
<a-input
v-model="form.department"
placeholder="请输入使用部门"
:max-length="100"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 位置信息 -->
<a-divider orientation="left">位置信息</a-divider>
<a-row :gutter="16">
<a-col :span="6">
<a-form-item label="行号" field="row">
<a-input-number
v-model="form.row"
placeholder="请输入行号"
:min="0"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="列号" field="column">
<a-input-number
v-model="form.column"
placeholder="请输入列号"
:min="0"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="X坐标" field="position_x">
<a-input-number
v-model="form.position_x"
placeholder="请输入X坐标"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="Y坐标" field="position_y">
<a-input-number
v-model="form.position_y"
placeholder="请输入Y坐标"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 其他 -->
<a-divider orientation="left">其他</a-divider>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="颜色标识" field="color">
<a-input
v-model="form.color"
placeholder="请输入颜色值,如#FF0000"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="启用" field="enabled">
<a-switch v-model="form.enabled" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="描述" field="description">
<a-textarea
v-model="form.description"
placeholder="请输入描述"
:auto-size="{ minRows: 3, maxRows: 6 }"
:max-length="500"
/>
</a-form-item>
<a-form-item label="备注" field="remarks">
<a-textarea
v-model="form.remarks"
placeholder="请输入备注"
:auto-size="{ minRows: 3, maxRows: 6 }"
:max-length="500"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from 'vue'
import { computed } from 'vue'
import { Message } from '@arco-design/web-vue'
import { createRack, updateRack } from '@/api/ops/rack'
import {
fetchDatacenterList,
fetchRackListByDatacenter,
fetchSupplierList,
} from '@/api/ops/rack'
interface Rack {
id?: number
name?: string
code?: string
datacenter_id?: number
floor_id?: number
height?: number
width?: number
depth?: number
weight?: number
max_load?: number
power_capacity?: number
power_phase?: string
pdu_count?: number
network_access?: string
switch_ports?: number
cooling_type?: string
rack_type?: string
status?: string
supplier_id?: number
manufacturer?: string
model?: string
serial_number?: string
purchase_date?: string
price?: number
owner?: string
contact_phone?: string
department?: string
row?: number
column?: number
position_x?: number
position_y?: number
color?: string
enabled?: boolean
description?: string
remarks?: string
}
interface Props {
visible: boolean
rack: Rack | null
}
interface Emits {
(e: 'update:visible', value: boolean): void
(e: 'success'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const formRef = ref()
const loadingDatacenters = ref(false)
const loadingFloors = ref(false)
const loadingSuppliers = ref(false)
const submitting = ref(false)
const datacenterList = ref<any[]>([])
const floorList = ref<any[]>([])
const supplierList = ref<any[]>([])
let floorSearchTimer: number | undefined
// 表单数据
const form = ref({
name: '',
code: '',
datacenter_id: undefined as number | undefined,
floor_id: undefined as number | undefined,
height: 42,
width: undefined as number | undefined,
depth: undefined as number | undefined,
weight: undefined as number | undefined,
max_load: undefined as number | undefined,
power_capacity: undefined as number | undefined,
power_phase: '',
pdu_count: 0,
network_access: '',
switch_ports: 0,
cooling_type: '',
rack_type: 'standard',
status: 'idle',
supplier_id: undefined as number | undefined,
manufacturer: '',
model: '',
serial_number: '',
purchase_date: '',
price: undefined as number | undefined,
owner: '',
contact_phone: '',
department: '',
row: 0,
column: 0,
position_x: undefined as number | undefined,
position_y: undefined as number | undefined,
color: '',
enabled: true,
description: '',
remarks: '',
})
// 是否为编辑模式
const isEdit = computed(() => !!props.rack?.id)
// 加载数据中心列表
const loadDatacenterList = async () => {
loadingDatacenters.value = true
try {
const res: any = await fetchDatacenterList()
if (res.code === 0) {
datacenterList.value = res.details || []
}
} catch (error) {
console.error('获取数据中心列表失败:', error)
} finally {
loadingDatacenters.value = false
}
}
// 加载楼层列表(通过机柜下拉接口提取去重楼层)
const loadFloorList = async (datacenterId?: number, keyword?: string) => {
if (!datacenterId) {
floorList.value = []
return
}
loadingFloors.value = true
try {
const res: any = await fetchRackListByDatacenter(datacenterId, { name: keyword })
if (res.code === 0) {
const list = res.details?.data ?? res.data ?? res.details ?? []
const rows = Array.isArray(list) ? list : []
const floorMap = new Map<number, { id: number; name: string }>()
rows.forEach((rack: any) => {
const floor = rack?.floor
if (!floor?.id || floorMap.has(floor.id)) return
floorMap.set(floor.id, {
id: floor.id,
name: floor.name || String(floor.id),
})
})
floorList.value = Array.from(floorMap.values())
}
} catch (error) {
console.error('获取楼层列表失败:', error)
} finally {
loadingFloors.value = false
}
}
// 加载供应商列表
const loadSupplierList = async () => {
loadingSuppliers.value = true
try {
const res: any = await fetchSupplierList()
if (res.code === 0) {
supplierList.value = res.details || []
}
} catch (error) {
console.error('获取供应商列表失败:', error)
} finally {
loadingSuppliers.value = false
}
}
// 数据中心变化时重新加载楼层列表
const handleDatacenterChange = async (value: number) => {
form.value.floor_id = undefined
await loadFloorList(value)
}
const handleFloorSearch = (keyword: string) => {
if (!form.value.datacenter_id) return
if (floorSearchTimer) {
window.clearTimeout(floorSearchTimer)
}
floorSearchTimer = window.setTimeout(() => {
loadFloorList(form.value.datacenter_id, keyword?.trim() || undefined)
}, 300)
}
// 监听对话框显示状态
watch(
() => props.visible,
(newVal) => {
if (newVal) {
if (props.rack && isEdit.value) {
// 编辑模式:填充表单
form.value = {
name: props.rack.name || '',
code: props.rack.code || '',
datacenter_id: props.rack.datacenter_id,
floor_id: props.rack.floor_id,
height: props.rack.height || 42,
width: props.rack.width,
depth: props.rack.depth,
weight: props.rack.weight,
max_load: props.rack.max_load,
power_capacity: props.rack.power_capacity,
power_phase: props.rack.power_phase || '',
pdu_count: props.rack.pdu_count || 0,
network_access: props.rack.network_access || '',
switch_ports: props.rack.switch_ports || 0,
cooling_type: props.rack.cooling_type || '',
rack_type: props.rack.rack_type || 'standard',
status: props.rack.status || 'idle',
supplier_id: props.rack.supplier_id,
manufacturer: props.rack.manufacturer || '',
model: props.rack.model || '',
serial_number: props.rack.serial_number || '',
purchase_date: props.rack.purchase_date || '',
price: props.rack.price,
owner: props.rack.owner || '',
contact_phone: props.rack.contact_phone || '',
department: props.rack.department || '',
row: props.rack.row || 0,
column: props.rack.column || 0,
position_x: props.rack.position_x,
position_y: props.rack.position_y,
color: props.rack.color || '',
enabled: props.rack.enabled !== undefined ? props.rack.enabled : true,
description: props.rack.description || '',
remarks: props.rack.remarks || '',
}
// 加载对应数据中心的楼层列表
if (props.rack.datacenter_id) {
loadFloorList(props.rack.datacenter_id)
}
} else {
// 新建模式:重置表单
form.value = {
name: '',
code: '',
datacenter_id: undefined,
floor_id: undefined,
height: 42,
width: undefined,
depth: undefined,
weight: undefined,
max_load: undefined,
power_capacity: undefined,
power_phase: '',
pdu_count: 0,
network_access: '',
switch_ports: 0,
cooling_type: '',
rack_type: 'standard',
status: 'idle',
supplier_id: undefined,
manufacturer: '',
model: '',
serial_number: '',
purchase_date: '',
price: undefined,
owner: '',
contact_phone: '',
department: '',
row: 0,
column: 0,
position_x: undefined,
position_y: undefined,
color: '',
enabled: true,
description: '',
remarks: '',
}
}
}
}
)
// 确认提交
const handleOk = async () => {
const valid = await formRef.value?.validate()
if (valid) return
submitting.value = true
try {
const data: any = {
name: form.value.name,
code: form.value.code,
datacenter_id: form.value.datacenter_id,
floor_id: form.value.floor_id,
height: form.value.height,
width: form.value.width,
depth: form.value.depth,
weight: form.value.weight,
max_load: form.value.max_load,
power_capacity: form.value.power_capacity,
power_phase: form.value.power_phase,
pdu_count: form.value.pdu_count,
network_access: form.value.network_access,
switch_ports: form.value.switch_ports,
cooling_type: form.value.cooling_type,
rack_type: form.value.rack_type,
status: form.value.status,
supplier_id: form.value.supplier_id,
manufacturer: form.value.manufacturer,
model: form.value.model,
serial_number: form.value.serial_number,
purchase_date: form.value.purchase_date,
price: form.value.price,
owner: form.value.owner,
contact_phone: form.value.contact_phone,
department: form.value.department,
row: form.value.row,
column: form.value.column,
position_x: form.value.position_x,
position_y: form.value.position_y,
color: form.value.color,
enabled: form.value.enabled,
description: form.value.description,
remarks: form.value.remarks,
}
let res
if (isEdit.value && props.rack?.id) {
// 编辑机柜
data.id = props.rack.id
res = await updateRack(data)
} else {
// 新建机柜
res = await createRack(data)
}
if (res.code === 0) {
Message.success(isEdit.value ? '编辑成功' : '创建成功')
emit('success')
emit('update:visible', false)
} else {
Message.error(res.message || (isEdit.value ? '编辑失败' : '创建失败'))
}
} catch (error) {
Message.error(isEdit.value ? '编辑失败' : '创建失败')
console.error(error)
} finally {
submitting.value = false
}
}
// 取消
const handleCancel = () => {
emit('update:visible', false)
}
// 处理对话框可见性变化
const handleVisibleChange = (visible: boolean) => {
emit('update:visible', visible)
}
// 组件挂载时加载数据
onMounted(() => {
loadDatacenterList()
loadSupplierList()
})
</script>
<script lang="ts">
export default {
name: 'RackFormDialog',
}
</script>
<style scoped lang="less">
</style>