feat: 机柜
This commit is contained in:
357
src/views/ops/pages/dc/server/components/DatacenterSelector.vue
Normal file
357
src/views/ops/pages/dc/server/components/DatacenterSelector.vue
Normal file
@@ -0,0 +1,357 @@
|
||||
<template>
|
||||
<a-input-group>
|
||||
<a-select
|
||||
v-model="selectedDatacenterId"
|
||||
:placeholder="placeholders.datacenter"
|
||||
:loading="datacenterListLoading"
|
||||
:disabled="disabled"
|
||||
style="width: 33.33%"
|
||||
allow-search
|
||||
@search="handleDatacenterSearch"
|
||||
@change="handleDatacenterChange"
|
||||
>
|
||||
<a-option
|
||||
v-for="datacenter in datacenterList"
|
||||
:key="datacenter.value"
|
||||
:value="datacenter.value"
|
||||
>
|
||||
{{ datacenter.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-model="selectedFloorId"
|
||||
:placeholder="placeholders.floor"
|
||||
:loading="floorListLoading"
|
||||
:disabled="disabled || !selectedDatacenterId"
|
||||
style="width: 33.33%"
|
||||
allow-search
|
||||
@search="handleFloorSearch"
|
||||
@change="handleFloorChange"
|
||||
>
|
||||
<a-option
|
||||
v-for="floor in floorList"
|
||||
:key="floor.value"
|
||||
:value="floor.value"
|
||||
>
|
||||
{{ floor.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-model="selectedRackId"
|
||||
:placeholder="placeholders.rack"
|
||||
:loading="rackListLoading"
|
||||
:disabled="disabled || !selectedFloorId"
|
||||
style="width: 33.33%"
|
||||
allow-search
|
||||
@search="handleRackSearch"
|
||||
@change="handleRackChange"
|
||||
>
|
||||
<a-option
|
||||
v-for="rack in rackList"
|
||||
:key="rack.id"
|
||||
:value="rack.id"
|
||||
>
|
||||
{{ rack.name }} ({{ rack.code }})
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { fetchDatacenterList, fetchRackListByFloor } from '@/api/ops/rack'
|
||||
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||
|
||||
interface Props {
|
||||
datacenterId?: number
|
||||
floorId?: number
|
||||
rackId?: number
|
||||
disabled?: boolean
|
||||
placeholders?: {
|
||||
datacenter: string
|
||||
floor: string
|
||||
rack: string
|
||||
}
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
datacenterId: undefined,
|
||||
floorId: undefined,
|
||||
rackId: undefined,
|
||||
disabled: false,
|
||||
placeholders: () => ({
|
||||
datacenter: '请选择数据中心',
|
||||
floor: '请选择楼层',
|
||||
rack: '请选择机柜',
|
||||
}),
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:datacenterId': [value: number | undefined]
|
||||
'update:floorId': [value: number | undefined]
|
||||
'update:rackId': [value: number | undefined]
|
||||
'change': [value: { datacenterId?: number; floorId?: number; rackId?: number }]
|
||||
}>()
|
||||
|
||||
// 三级联动相关状态
|
||||
const datacenterListLoading = ref(false)
|
||||
const floorListLoading = ref(false)
|
||||
const rackListLoading = ref(false)
|
||||
const datacenterList = ref<{ label: string; value: number }[]>([])
|
||||
const floorList = ref<{ label: string; value: number }[]>([])
|
||||
const rackList = ref<any[]>([])
|
||||
|
||||
// 内部选中的值
|
||||
const selectedDatacenterId = ref<number | undefined>(props.datacenterId)
|
||||
const selectedFloorId = ref<number | undefined>(props.floorId)
|
||||
const selectedRackId = ref<number | undefined>(props.rackId)
|
||||
|
||||
let datacenterSearchTimer: number | undefined
|
||||
let floorSearchTimer: number | undefined
|
||||
let rackSearchTimer: number | undefined
|
||||
|
||||
// 提取列表数据
|
||||
const extractList = (res: any): any[] => {
|
||||
const candidate =
|
||||
res?.data?.data ??
|
||||
res?.details?.data ??
|
||||
res?.data ??
|
||||
res?.details ??
|
||||
[]
|
||||
return Array.isArray(candidate) ? candidate : []
|
||||
}
|
||||
|
||||
// 获取数据中心列表
|
||||
const fetchDatacenters = async (keyword?: string) => {
|
||||
datacenterListLoading.value = true
|
||||
try {
|
||||
const res: any = await fetchDatacenterList({ keyword })
|
||||
const rows = extractList(res)
|
||||
datacenterList.value = rows.map((d: any) => ({
|
||||
label: d.name || d.code || String(d.id),
|
||||
value: d.id,
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('获取数据中心列表失败:', error)
|
||||
Message.error('获取数据中心列表失败')
|
||||
datacenterList.value = []
|
||||
} finally {
|
||||
datacenterListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取楼层列表
|
||||
const fetchFloors = async (keyword?: string) => {
|
||||
if (!selectedDatacenterId.value) {
|
||||
floorList.value = []
|
||||
return
|
||||
}
|
||||
floorListLoading.value = true
|
||||
try {
|
||||
const res: any = await fetchFloorListByDatacenter(selectedDatacenterId.value, {
|
||||
name: keyword || undefined,
|
||||
})
|
||||
const rows = extractList(res)
|
||||
floorList.value = rows.map((floor: any) => ({
|
||||
label: floor.name || floor.code || String(floor.id),
|
||||
value: floor.id,
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('获取楼层列表失败:', error)
|
||||
Message.error('获取楼层列表失败')
|
||||
floorList.value = []
|
||||
} finally {
|
||||
floorListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取机柜列表
|
||||
const fetchRacks = async (keyword?: string) => {
|
||||
if (!selectedFloorId.value) {
|
||||
rackList.value = []
|
||||
return
|
||||
}
|
||||
rackListLoading.value = true
|
||||
try {
|
||||
const res: any = await fetchRackListByFloor(selectedFloorId.value, { name: keyword })
|
||||
rackList.value = extractList(res)
|
||||
} catch (error) {
|
||||
console.error('获取机柜列表失败:', error)
|
||||
Message.error('获取机柜列表失败')
|
||||
rackList.value = []
|
||||
} finally {
|
||||
rackListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 数据中心搜索
|
||||
const handleDatacenterSearch = (keyword: string) => {
|
||||
if (datacenterSearchTimer) {
|
||||
window.clearTimeout(datacenterSearchTimer)
|
||||
}
|
||||
datacenterSearchTimer = window.setTimeout(() => {
|
||||
fetchDatacenters(keyword?.trim() || undefined)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 楼层搜索
|
||||
const handleFloorSearch = (keyword: string) => {
|
||||
if (!selectedDatacenterId.value) return
|
||||
if (floorSearchTimer) {
|
||||
window.clearTimeout(floorSearchTimer)
|
||||
}
|
||||
floorSearchTimer = window.setTimeout(() => {
|
||||
fetchFloors(keyword?.trim() || undefined)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 机柜搜索
|
||||
const handleRackSearch = (keyword: string) => {
|
||||
if (!selectedFloorId.value) return
|
||||
if (rackSearchTimer) {
|
||||
window.clearTimeout(rackSearchTimer)
|
||||
}
|
||||
rackSearchTimer = window.setTimeout(() => {
|
||||
fetchRacks(keyword?.trim() || undefined)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 数据中心变化
|
||||
const handleDatacenterChange = async (datacenterId?: number | string) => {
|
||||
if (datacenterId !== undefined && datacenterId !== null && datacenterId !== '') {
|
||||
selectedDatacenterId.value = Number(datacenterId)
|
||||
} else {
|
||||
selectedDatacenterId.value = undefined
|
||||
}
|
||||
selectedFloorId.value = undefined
|
||||
selectedRackId.value = undefined
|
||||
rackList.value = []
|
||||
floorList.value = []
|
||||
|
||||
emit('update:datacenterId', selectedDatacenterId.value)
|
||||
emit('update:floorId', undefined)
|
||||
emit('update:rackId', undefined)
|
||||
emit('change', {
|
||||
datacenterId: selectedDatacenterId.value,
|
||||
floorId: undefined,
|
||||
rackId: undefined,
|
||||
})
|
||||
|
||||
await fetchFloors()
|
||||
}
|
||||
|
||||
// 楼层变化
|
||||
const handleFloorChange = async (floorId?: number | string) => {
|
||||
if (floorId !== undefined && floorId !== null && floorId !== '') {
|
||||
selectedFloorId.value = Number(floorId)
|
||||
} else {
|
||||
selectedFloorId.value = undefined
|
||||
}
|
||||
selectedRackId.value = undefined
|
||||
rackList.value = []
|
||||
|
||||
emit('update:floorId', selectedFloorId.value)
|
||||
emit('update:rackId', undefined)
|
||||
emit('change', {
|
||||
datacenterId: selectedDatacenterId.value,
|
||||
floorId: selectedFloorId.value,
|
||||
rackId: undefined,
|
||||
})
|
||||
|
||||
await fetchRacks()
|
||||
}
|
||||
|
||||
// 机柜变化
|
||||
const handleRackChange = (rackId?: number | string) => {
|
||||
if (rackId !== undefined && rackId !== null && rackId !== '') {
|
||||
selectedRackId.value = Number(rackId)
|
||||
} else {
|
||||
selectedRackId.value = undefined
|
||||
}
|
||||
|
||||
emit('update:rackId', selectedRackId.value)
|
||||
emit('change', {
|
||||
datacenterId: selectedDatacenterId.value,
|
||||
floorId: selectedFloorId.value,
|
||||
rackId: selectedRackId.value,
|
||||
})
|
||||
}
|
||||
|
||||
// 重置方法
|
||||
const reset = () => {
|
||||
selectedDatacenterId.value = undefined
|
||||
selectedFloorId.value = undefined
|
||||
selectedRackId.value = undefined
|
||||
floorList.value = []
|
||||
rackList.value = []
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
const initLoad = async () => {
|
||||
await fetchDatacenters()
|
||||
|
||||
// 如果有初始值,加载对应的下级列表
|
||||
if (props.datacenterId) {
|
||||
selectedDatacenterId.value = props.datacenterId
|
||||
await fetchFloors()
|
||||
}
|
||||
if (props.floorId) {
|
||||
selectedFloorId.value = props.floorId
|
||||
await fetchRacks()
|
||||
}
|
||||
if (props.rackId) {
|
||||
selectedRackId.value = props.rackId
|
||||
}
|
||||
}
|
||||
|
||||
// 监听外部传入的值变化
|
||||
watch(
|
||||
() => props.datacenterId,
|
||||
(val) => {
|
||||
if (val !== selectedDatacenterId.value) {
|
||||
selectedDatacenterId.value = val
|
||||
if (val) {
|
||||
fetchFloors()
|
||||
} else {
|
||||
floorList.value = []
|
||||
rackList.value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.floorId,
|
||||
(val) => {
|
||||
if (val !== selectedFloorId.value) {
|
||||
selectedFloorId.value = val
|
||||
if (val) {
|
||||
fetchRacks()
|
||||
} else {
|
||||
rackList.value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.rackId,
|
||||
(val) => {
|
||||
selectedRackId.value = val
|
||||
}
|
||||
)
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
reset,
|
||||
initLoad,
|
||||
fetchDatacenters,
|
||||
fetchFloors,
|
||||
fetchRacks,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
initLoad()
|
||||
})
|
||||
</script>
|
||||
@@ -47,27 +47,13 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item field="location" label="位置/机房信息">
|
||||
<a-input-group>
|
||||
<a-input
|
||||
v-model="formData.location"
|
||||
placeholder="请输入或选择位置信息"
|
||||
<a-form-item field="rack_id" label="数据中心/楼层/机柜">
|
||||
<DatacenterSelector
|
||||
ref="datacenterSelectorRef"
|
||||
v-model:datacenter-id="formData.datacenter_id"
|
||||
v-model:floor-id="formData.floor_id"
|
||||
v-model:rack-id="formData.rack_id"
|
||||
/>
|
||||
<a-select
|
||||
v-model="selectedLocation"
|
||||
placeholder="选择位置"
|
||||
style="width: 200px"
|
||||
@change="handleLocationSelect"
|
||||
>
|
||||
<a-option
|
||||
v-for="location in locationOptions"
|
||||
:key="location.value"
|
||||
:value="location.value"
|
||||
>
|
||||
{{ location.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item field="tags" label="服务器标签">
|
||||
@@ -125,6 +111,7 @@ import { ref, reactive, computed, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import type { FormInstance } from '@arco-design/web-vue'
|
||||
import DatacenterSelector from './DatacenterSelector.vue'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
@@ -139,7 +126,7 @@ const emit = defineEmits(['update:visible', 'success'])
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const confirmLoading = ref(false)
|
||||
const selectedLocation = ref('')
|
||||
const datacenterSelectorRef = ref<InstanceType<typeof DatacenterSelector>>()
|
||||
|
||||
const isEdit = computed(() => !!props.record?.id)
|
||||
|
||||
@@ -148,7 +135,9 @@ const formData = reactive({
|
||||
name: '',
|
||||
server_type: '',
|
||||
os: '',
|
||||
location: '',
|
||||
datacenter_id: undefined as number | undefined,
|
||||
floor_id: undefined as number | undefined,
|
||||
rack_id: undefined as number | undefined,
|
||||
tags: '',
|
||||
ip: '',
|
||||
remote_port: '',
|
||||
@@ -164,27 +153,41 @@ const rules = {
|
||||
os: [{ required: true, message: '请选择操作系统' }],
|
||||
}
|
||||
|
||||
const locationOptions = ref([
|
||||
{ label: 'A数据中心-3层-24机柜-5U位', value: 'A数据中心-3层-24机柜-5U位' },
|
||||
{ label: 'A数据中心-3层-24机柜-6U位', value: 'A数据中心-3层-24机柜-6U位' },
|
||||
{ label: 'B数据中心-1层-12机柜-1U位', value: 'B数据中心-1层-12机柜-1U位' },
|
||||
{ label: 'B数据中心-1层-12机柜-2U位', value: 'B数据中心-1层-12机柜-2U位' },
|
||||
{ label: 'C数据中心-2层-8机柜-3U位', value: 'C数据中心-2层-8机柜-3U位' },
|
||||
])
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
if (isEdit.value && props.record) {
|
||||
Object.assign(formData, props.record)
|
||||
Object.assign(formData, {
|
||||
unique_id: props.record.unique_id || '',
|
||||
name: props.record.name || '',
|
||||
server_type: props.record.server_type || '',
|
||||
os: props.record.os || '',
|
||||
datacenter_id: props.record.datacenter_id,
|
||||
floor_id: props.record.floor_id,
|
||||
rack_id: props.record.rack_id,
|
||||
tags: props.record.tags || '',
|
||||
ip: props.record.ip || '',
|
||||
remote_port: props.record.remote_port || '',
|
||||
agent_url: props.record.agent_url || '',
|
||||
data_collection: props.record.data_collection || false,
|
||||
collection_interval: props.record.collection_interval || 5,
|
||||
remark: props.record.remark || '',
|
||||
})
|
||||
|
||||
// 编辑模式下初始化加载下级列表
|
||||
if (props.record.datacenter_id) {
|
||||
datacenterSelectorRef.value?.initLoad()
|
||||
}
|
||||
} else {
|
||||
Object.assign(formData, {
|
||||
unique_id: '',
|
||||
name: '',
|
||||
server_type: '',
|
||||
os: '',
|
||||
location: '',
|
||||
datacenter_id: undefined,
|
||||
floor_id: undefined,
|
||||
rack_id: undefined,
|
||||
tags: '',
|
||||
ip: '',
|
||||
remote_port: '',
|
||||
@@ -193,15 +196,12 @@ watch(
|
||||
collection_interval: 5,
|
||||
remark: '',
|
||||
})
|
||||
datacenterSelectorRef.value?.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const handleLocationSelect = (value: string) => {
|
||||
formData.location = value
|
||||
}
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
|
||||
Reference in New Issue
Block a user