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-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
<a-form-item field="location" label="位置/机房信息">
|
<a-form-item field="rack_id" label="数据中心/楼层/机柜">
|
||||||
<a-input-group>
|
<DatacenterSelector
|
||||||
<a-input
|
ref="datacenterSelectorRef"
|
||||||
v-model="formData.location"
|
v-model:datacenter-id="formData.datacenter_id"
|
||||||
placeholder="请输入或选择位置信息"
|
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>
|
||||||
|
|
||||||
<a-form-item field="tags" label="服务器标签">
|
<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 { Message } from '@arco-design/web-vue'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import type { FormInstance } from '@arco-design/web-vue'
|
import type { FormInstance } from '@arco-design/web-vue'
|
||||||
|
import DatacenterSelector from './DatacenterSelector.vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean
|
visible: boolean
|
||||||
@@ -139,7 +126,7 @@ const emit = defineEmits(['update:visible', 'success'])
|
|||||||
|
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const confirmLoading = ref(false)
|
const confirmLoading = ref(false)
|
||||||
const selectedLocation = ref('')
|
const datacenterSelectorRef = ref<InstanceType<typeof DatacenterSelector>>()
|
||||||
|
|
||||||
const isEdit = computed(() => !!props.record?.id)
|
const isEdit = computed(() => !!props.record?.id)
|
||||||
|
|
||||||
@@ -148,7 +135,9 @@ const formData = reactive({
|
|||||||
name: '',
|
name: '',
|
||||||
server_type: '',
|
server_type: '',
|
||||||
os: '',
|
os: '',
|
||||||
location: '',
|
datacenter_id: undefined as number | undefined,
|
||||||
|
floor_id: undefined as number | undefined,
|
||||||
|
rack_id: undefined as number | undefined,
|
||||||
tags: '',
|
tags: '',
|
||||||
ip: '',
|
ip: '',
|
||||||
remote_port: '',
|
remote_port: '',
|
||||||
@@ -164,27 +153,41 @@ const rules = {
|
|||||||
os: [{ required: true, message: '请选择操作系统' }],
|
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(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
if (isEdit.value && props.record) {
|
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 {
|
} else {
|
||||||
Object.assign(formData, {
|
Object.assign(formData, {
|
||||||
unique_id: '',
|
unique_id: '',
|
||||||
name: '',
|
name: '',
|
||||||
server_type: '',
|
server_type: '',
|
||||||
os: '',
|
os: '',
|
||||||
location: '',
|
datacenter_id: undefined,
|
||||||
|
floor_id: undefined,
|
||||||
|
rack_id: undefined,
|
||||||
tags: '',
|
tags: '',
|
||||||
ip: '',
|
ip: '',
|
||||||
remote_port: '',
|
remote_port: '',
|
||||||
@@ -193,15 +196,12 @@ watch(
|
|||||||
collection_interval: 5,
|
collection_interval: 5,
|
||||||
remark: '',
|
remark: '',
|
||||||
})
|
})
|
||||||
|
datacenterSelectorRef.value?.reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleLocationSelect = (value: string) => {
|
|
||||||
formData.location = value
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOk = async () => {
|
const handleOk = async () => {
|
||||||
try {
|
try {
|
||||||
await formRef.value?.validate()
|
await formRef.value?.validate()
|
||||||
|
|||||||
Reference in New Issue
Block a user