fix
This commit is contained in:
@@ -17,6 +17,7 @@ export interface ServerItem {
|
|||||||
server_type: string
|
server_type: string
|
||||||
tags: string
|
tags: string
|
||||||
location: string
|
location: string
|
||||||
|
asset_id?: number
|
||||||
remote_access: string
|
remote_access: string
|
||||||
remote_port: number
|
remote_port: number
|
||||||
agent_config: string
|
agent_config: string
|
||||||
@@ -60,6 +61,7 @@ export interface ServerFormData {
|
|||||||
server_type?: string
|
server_type?: string
|
||||||
tags?: string
|
tags?: string
|
||||||
location?: string
|
location?: string
|
||||||
|
asset_id?: number
|
||||||
remote_access?: string
|
remote_access?: string
|
||||||
remote_port?: number
|
remote_port?: number
|
||||||
agent_config?: string
|
agent_config?: string
|
||||||
|
|||||||
@@ -766,6 +766,22 @@ export const localMenuFlatItems: MenuItem[] = [
|
|||||||
sort_key: 39,
|
sort_key: 39,
|
||||||
created_at: '2025-12-26T13:23:52.220159+08:00',
|
created_at: '2025-12-26T13:23:52.220159+08:00',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 53,
|
||||||
|
identity: '019d0000-0000-7000-8000-000000000053',
|
||||||
|
title: '机房管理',
|
||||||
|
title_en: 'Room Management',
|
||||||
|
code: 'ops:数据中心管理:机房管理',
|
||||||
|
description: '数据中心管理 - 机房管理',
|
||||||
|
app_id: 2,
|
||||||
|
parent_id: 49,
|
||||||
|
menu_path: '/datacenter/room',
|
||||||
|
menu_icon: 'appstore',
|
||||||
|
component: 'ops/pages/datacenter/room',
|
||||||
|
type: 1,
|
||||||
|
sort_key: 39.5,
|
||||||
|
created_at: '2026-04-14T10:00:00+08:00',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 54,
|
id: 54,
|
||||||
identity: '019b591d-0343-7ce7-91bd-d82497ea0a11',
|
identity: '019b591d-0343-7ce7-91bd-d82497ea0a11',
|
||||||
|
|||||||
@@ -824,6 +824,23 @@ export const localMenuItems: MenuItem[] = [
|
|||||||
created_at: '2025-12-26T13:23:52.220159+08:00',
|
created_at: '2025-12-26T13:23:52.220159+08:00',
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 53,
|
||||||
|
identity: '019d0000-0000-7000-8000-000000000053',
|
||||||
|
title: '机房管理',
|
||||||
|
title_en: 'Room Management',
|
||||||
|
code: 'ops:数据中心管理:机房管理',
|
||||||
|
description: '数据中心管理 - 机房管理',
|
||||||
|
app_id: 2,
|
||||||
|
parent_id: 49,
|
||||||
|
menu_path: '/datacenter/room',
|
||||||
|
menu_icon: 'appstore',
|
||||||
|
component: 'ops/pages/datacenter/room',
|
||||||
|
type: 1,
|
||||||
|
sort_key: 9,
|
||||||
|
created_at: '2026-04-14T10:00:00+08:00',
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -208,7 +208,7 @@
|
|||||||
<!-- 位置信息 -->
|
<!-- 位置信息 -->
|
||||||
<a-card class="info-card" title="位置信息">
|
<a-card class="info-card" title="位置信息">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="6">
|
<a-col :span="5">
|
||||||
<a-form-item label="所属数据中心" field="datacenter_id">
|
<a-form-item label="所属数据中心" field="datacenter_id">
|
||||||
<a-select
|
<a-select
|
||||||
v-model="form.datacenter_id"
|
v-model="form.datacenter_id"
|
||||||
@@ -229,7 +229,7 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="5">
|
||||||
<a-form-item label="所属楼层" field="floor_id">
|
<a-form-item label="所属楼层" field="floor_id">
|
||||||
<a-select
|
<a-select
|
||||||
v-model="form.floor_id"
|
v-model="form.floor_id"
|
||||||
@@ -251,7 +251,30 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="5">
|
||||||
|
<a-form-item label="所属机房" field="room_id">
|
||||||
|
<a-select
|
||||||
|
v-model="form.room_id"
|
||||||
|
placeholder="请选择机房"
|
||||||
|
allow-clear
|
||||||
|
allow-search
|
||||||
|
:loading="roomLoading"
|
||||||
|
:disabled="!form.floor_id"
|
||||||
|
:filter-option="false"
|
||||||
|
@search="handleRoomSearch"
|
||||||
|
@change="handleRoomChange"
|
||||||
|
>
|
||||||
|
<a-option
|
||||||
|
v-for="item in roomOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
{{ item.name }} ({{ item.code }})
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="5">
|
||||||
<a-form-item label="所属机柜" field="rack_id">
|
<a-form-item label="所属机柜" field="rack_id">
|
||||||
<a-select
|
<a-select
|
||||||
v-model="form.rack_id"
|
v-model="form.rack_id"
|
||||||
@@ -259,7 +282,7 @@
|
|||||||
allow-clear
|
allow-clear
|
||||||
allow-search
|
allow-search
|
||||||
:loading="rackLoading"
|
:loading="rackLoading"
|
||||||
:disabled="!form.floor_id"
|
:disabled="!form.room_id"
|
||||||
:filter-option="false"
|
:filter-option="false"
|
||||||
@search="handleRackSearch"
|
@search="handleRackSearch"
|
||||||
>
|
>
|
||||||
@@ -273,7 +296,7 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="3">
|
<a-col :span="2">
|
||||||
<a-form-item label="起始U位" field="unit_start">
|
<a-form-item label="起始U位" field="unit_start">
|
||||||
<a-input-number
|
<a-input-number
|
||||||
v-model="form.unit_start"
|
v-model="form.unit_start"
|
||||||
@@ -284,7 +307,7 @@
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="3">
|
<a-col :span="2">
|
||||||
<a-form-item label="结束U位" field="unit_end">
|
<a-form-item label="结束U位" field="unit_end">
|
||||||
<a-input-number
|
<a-input-number
|
||||||
v-model="form.unit_end"
|
v-model="form.unit_end"
|
||||||
@@ -387,8 +410,9 @@ import {
|
|||||||
AssetForm,
|
AssetForm,
|
||||||
} from '@/api/ops/asset'
|
} from '@/api/ops/asset'
|
||||||
import { fetchAllSuppliers } from '@/api/ops/supplier'
|
import { fetchAllSuppliers } from '@/api/ops/supplier'
|
||||||
import { fetchDatacenterList, fetchRackListByFloor } from '@/api/ops/rack'
|
import { fetchDatacenterList, fetchRackListByRoom } from '@/api/ops/rack'
|
||||||
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||||
|
import { fetchRoomListByFloor } from '@/api/ops/room'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -406,6 +430,7 @@ const categoryOptions = ref<any[]>([])
|
|||||||
const supplierOptions = ref<any[]>([])
|
const supplierOptions = ref<any[]>([])
|
||||||
const datacenterOptions = ref<{ label: string; value: number }[]>([])
|
const datacenterOptions = ref<{ label: string; value: number }[]>([])
|
||||||
const floorOptions = ref<{ label: string; value: number }[]>([])
|
const floorOptions = ref<{ label: string; value: number }[]>([])
|
||||||
|
const roomOptions = ref<any[]>([])
|
||||||
const rackOptions = ref<any[]>([])
|
const rackOptions = ref<any[]>([])
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
@@ -413,11 +438,13 @@ const categoryLoading = ref(false)
|
|||||||
const supplierLoading = ref(false)
|
const supplierLoading = ref(false)
|
||||||
const datacenterLoading = ref(false)
|
const datacenterLoading = ref(false)
|
||||||
const floorLoading = ref(false)
|
const floorLoading = ref(false)
|
||||||
|
const roomLoading = ref(false)
|
||||||
const rackLoading = ref(false)
|
const rackLoading = ref(false)
|
||||||
|
|
||||||
// 搜索防抖定时器
|
// 搜索防抖定时器
|
||||||
let datacenterSearchTimer: number | undefined
|
let datacenterSearchTimer: number | undefined
|
||||||
let floorSearchTimer: number | undefined
|
let floorSearchTimer: number | undefined
|
||||||
|
let roomSearchTimer: number | undefined
|
||||||
let rackSearchTimer: number | undefined
|
let rackSearchTimer: number | undefined
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
@@ -574,13 +601,13 @@ const handleFloorSearch = (keyword: string) => {
|
|||||||
|
|
||||||
// 加载机柜列表
|
// 加载机柜列表
|
||||||
const fetchRacks = async (keyword?: string) => {
|
const fetchRacks = async (keyword?: string) => {
|
||||||
if (!form.value.floor_id) {
|
if (!form.value.room_id) {
|
||||||
rackOptions.value = []
|
rackOptions.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rackLoading.value = true
|
rackLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res: any = await fetchRackListByFloor(form.value.floor_id, { name: keyword })
|
const res: any = await fetchRackListByRoom(form.value.room_id, { name: keyword })
|
||||||
rackOptions.value = extractList(res)
|
rackOptions.value = extractList(res)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取机柜列表失败:', error)
|
console.error('获取机柜列表失败:', error)
|
||||||
@@ -592,7 +619,7 @@ const fetchRacks = async (keyword?: string) => {
|
|||||||
|
|
||||||
// 机柜搜索(带防抖)
|
// 机柜搜索(带防抖)
|
||||||
const handleRackSearch = (keyword: string) => {
|
const handleRackSearch = (keyword: string) => {
|
||||||
if (!form.value.floor_id) return
|
if (!form.value.room_id) return
|
||||||
if (rackSearchTimer) {
|
if (rackSearchTimer) {
|
||||||
window.clearTimeout(rackSearchTimer)
|
window.clearTimeout(rackSearchTimer)
|
||||||
}
|
}
|
||||||
@@ -601,11 +628,44 @@ const handleRackSearch = (keyword: string) => {
|
|||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载机房列表
|
||||||
|
const fetchRooms = async (keyword?: string) => {
|
||||||
|
if (!form.value.floor_id) {
|
||||||
|
roomOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roomLoading.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await fetchRoomListByFloor(form.value.floor_id, {
|
||||||
|
name: keyword || undefined,
|
||||||
|
})
|
||||||
|
roomOptions.value = extractList(res)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取机房列表失败:', error)
|
||||||
|
roomOptions.value = []
|
||||||
|
} finally {
|
||||||
|
roomLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 机房搜索(带防抖)
|
||||||
|
const handleRoomSearch = (keyword: string) => {
|
||||||
|
if (!form.value.floor_id) return
|
||||||
|
if (roomSearchTimer) {
|
||||||
|
window.clearTimeout(roomSearchTimer)
|
||||||
|
}
|
||||||
|
roomSearchTimer = window.setTimeout(() => {
|
||||||
|
fetchRooms(keyword?.trim() || undefined)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
// 数据中心变化时加载楼层
|
// 数据中心变化时加载楼层
|
||||||
const handleDatacenterChange = async (value: number | undefined) => {
|
const handleDatacenterChange = async (value: number | undefined) => {
|
||||||
form.value.floor_id = undefined
|
form.value.floor_id = undefined
|
||||||
|
form.value.room_id = undefined
|
||||||
form.value.rack_id = undefined
|
form.value.rack_id = undefined
|
||||||
floorOptions.value = []
|
floorOptions.value = []
|
||||||
|
roomOptions.value = []
|
||||||
rackOptions.value = []
|
rackOptions.value = []
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -615,6 +675,18 @@ const handleDatacenterChange = async (value: number | undefined) => {
|
|||||||
|
|
||||||
// 楼层变化时加载机柜
|
// 楼层变化时加载机柜
|
||||||
const handleFloorChange = async (value: number | undefined) => {
|
const handleFloorChange = async (value: number | undefined) => {
|
||||||
|
form.value.room_id = undefined
|
||||||
|
form.value.rack_id = undefined
|
||||||
|
roomOptions.value = []
|
||||||
|
rackOptions.value = []
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
await fetchRooms()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 机房变化时加载机柜
|
||||||
|
const handleRoomChange = async (value: number | undefined) => {
|
||||||
form.value.rack_id = undefined
|
form.value.rack_id = undefined
|
||||||
rackOptions.value = []
|
rackOptions.value = []
|
||||||
|
|
||||||
@@ -675,7 +747,17 @@ const loadDeviceDetail = async () => {
|
|||||||
// 如果有楼层,加载机柜
|
// 如果有楼层,加载机柜
|
||||||
if (res.details.floor_id) {
|
if (res.details.floor_id) {
|
||||||
try {
|
try {
|
||||||
const rackRes: any = await fetchRackListByFloor(res.details.floor_id)
|
const roomRes: any = await fetchRoomListByFloor(res.details.floor_id)
|
||||||
|
roomOptions.value = extractList(roomRes)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载机房失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有机房,加载机柜
|
||||||
|
if (res.details.room_id) {
|
||||||
|
try {
|
||||||
|
const rackRes: any = await fetchRackListByRoom(res.details.room_id)
|
||||||
rackOptions.value = extractList(rackRes)
|
rackOptions.value = extractList(rackRes)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载机柜失败:', error)
|
console.error('加载机柜失败:', error)
|
||||||
|
|||||||
@@ -56,6 +56,16 @@
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="状态" field="status">
|
||||||
|
<a-select v-model="form.status" placeholder="请选择状态">
|
||||||
|
<a-option value="planning">规划中</a-option>
|
||||||
|
<a-option value="construction">建设中</a-option>
|
||||||
|
<a-option value="operating">运营中</a-option>
|
||||||
|
<a-option value="maintenance">维护中</a-option>
|
||||||
|
<a-option value="offline">已下线</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="面积(平方米)"
|
label="面积(平方米)"
|
||||||
field="area"
|
field="area"
|
||||||
@@ -152,6 +162,7 @@ interface Floor {
|
|||||||
name?: string
|
name?: string
|
||||||
datacenter_id?: number
|
datacenter_id?: number
|
||||||
floor_number?: number
|
floor_number?: number
|
||||||
|
status?: string
|
||||||
area?: number
|
area?: number
|
||||||
height?: number
|
height?: number
|
||||||
load_bearing?: number
|
load_bearing?: number
|
||||||
@@ -184,6 +195,7 @@ const form = ref({
|
|||||||
name: '',
|
name: '',
|
||||||
datacenter_id: undefined as number | undefined,
|
datacenter_id: undefined as number | undefined,
|
||||||
floor_number: 1,
|
floor_number: 1,
|
||||||
|
status: 'planning',
|
||||||
area: undefined as number | undefined,
|
area: undefined as number | undefined,
|
||||||
height: undefined as number | undefined,
|
height: undefined as number | undefined,
|
||||||
load_bearing: undefined as number | undefined,
|
load_bearing: undefined as number | undefined,
|
||||||
@@ -252,6 +264,7 @@ watch(
|
|||||||
name: props.floor.name || '',
|
name: props.floor.name || '',
|
||||||
datacenter_id: props.floor.datacenter_id,
|
datacenter_id: props.floor.datacenter_id,
|
||||||
floor_number: props.floor.floor_number || 1,
|
floor_number: props.floor.floor_number || 1,
|
||||||
|
status: props.floor.status || 'planning',
|
||||||
area: props.floor.area,
|
area: props.floor.area,
|
||||||
height: props.floor.height,
|
height: props.floor.height,
|
||||||
load_bearing: props.floor.load_bearing,
|
load_bearing: props.floor.load_bearing,
|
||||||
@@ -277,6 +290,7 @@ watch(
|
|||||||
name: '',
|
name: '',
|
||||||
datacenter_id: undefined,
|
datacenter_id: undefined,
|
||||||
floor_number: 1,
|
floor_number: 1,
|
||||||
|
status: 'planning',
|
||||||
area: undefined,
|
area: undefined,
|
||||||
height: undefined,
|
height: undefined,
|
||||||
load_bearing: undefined,
|
load_bearing: undefined,
|
||||||
@@ -301,6 +315,7 @@ const handleOk = async () => {
|
|||||||
name: form.value.name,
|
name: form.value.name,
|
||||||
datacenter_id: form.value.datacenter_id,
|
datacenter_id: form.value.datacenter_id,
|
||||||
floor_number: form.value.floor_number,
|
floor_number: form.value.floor_number,
|
||||||
|
status: form.value.status,
|
||||||
area: form.value.area,
|
area: form.value.area,
|
||||||
height: form.value.height,
|
height: form.value.height,
|
||||||
load_bearing: form.value.load_bearing,
|
load_bearing: form.value.load_bearing,
|
||||||
|
|||||||
@@ -74,7 +74,9 @@
|
|||||||
v-model="form.floor_id"
|
v-model="form.floor_id"
|
||||||
placeholder="请选择所属楼层"
|
placeholder="请选择所属楼层"
|
||||||
:loading="loadingFloors"
|
:loading="loadingFloors"
|
||||||
|
:disabled="!form.datacenter_id"
|
||||||
allow-search
|
allow-search
|
||||||
|
@change="handleFloorChange"
|
||||||
@search="handleFloorSearch"
|
@search="handleFloorSearch"
|
||||||
>
|
>
|
||||||
<a-option
|
<a-option
|
||||||
@@ -88,6 +90,32 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item
|
||||||
|
label="所属机房"
|
||||||
|
field="room_id"
|
||||||
|
:rules="[{ required: true, message: '请选择所属机房' }]"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model="form.room_id"
|
||||||
|
placeholder="请选择所属机房"
|
||||||
|
:loading="loadingRooms"
|
||||||
|
:disabled="!form.floor_id"
|
||||||
|
allow-search
|
||||||
|
@search="handleRoomSearch"
|
||||||
|
>
|
||||||
|
<a-option
|
||||||
|
v-for="item in roomList"
|
||||||
|
: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-divider orientation="left">规格参数</a-divider>
|
||||||
@@ -438,9 +466,10 @@ import { Message } from '@arco-design/web-vue'
|
|||||||
import { createRack, updateRack } from '@/api/ops/rack'
|
import { createRack, updateRack } from '@/api/ops/rack'
|
||||||
import {
|
import {
|
||||||
fetchDatacenterList,
|
fetchDatacenterList,
|
||||||
fetchRackListByDatacenter,
|
|
||||||
fetchSupplierList,
|
fetchSupplierList,
|
||||||
} from '@/api/ops/rack'
|
} from '@/api/ops/rack'
|
||||||
|
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||||
|
import { fetchRoomListByFloor } from '@/api/ops/room'
|
||||||
|
|
||||||
interface Rack {
|
interface Rack {
|
||||||
id?: number
|
id?: number
|
||||||
@@ -448,6 +477,7 @@ interface Rack {
|
|||||||
code?: string
|
code?: string
|
||||||
datacenter_id?: number
|
datacenter_id?: number
|
||||||
floor_id?: number
|
floor_id?: number
|
||||||
|
room_id?: number
|
||||||
height?: number
|
height?: number
|
||||||
width?: number
|
width?: number
|
||||||
depth?: number
|
depth?: number
|
||||||
@@ -496,12 +526,15 @@ const emit = defineEmits<Emits>()
|
|||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const loadingDatacenters = ref(false)
|
const loadingDatacenters = ref(false)
|
||||||
const loadingFloors = ref(false)
|
const loadingFloors = ref(false)
|
||||||
|
const loadingRooms = ref(false)
|
||||||
const loadingSuppliers = ref(false)
|
const loadingSuppliers = ref(false)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const datacenterList = ref<any[]>([])
|
const datacenterList = ref<any[]>([])
|
||||||
const floorList = ref<any[]>([])
|
const floorList = ref<any[]>([])
|
||||||
|
const roomList = ref<any[]>([])
|
||||||
const supplierList = ref<any[]>([])
|
const supplierList = ref<any[]>([])
|
||||||
let floorSearchTimer: number | undefined
|
let floorSearchTimer: number | undefined
|
||||||
|
let roomSearchTimer: number | undefined
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const form = ref({
|
const form = ref({
|
||||||
@@ -509,6 +542,7 @@ const form = ref({
|
|||||||
code: '',
|
code: '',
|
||||||
datacenter_id: undefined as number | undefined,
|
datacenter_id: undefined as number | undefined,
|
||||||
floor_id: undefined as number | undefined,
|
floor_id: undefined as number | undefined,
|
||||||
|
room_id: undefined as number | undefined,
|
||||||
height: 42,
|
height: 42,
|
||||||
width: undefined as number | undefined,
|
width: undefined as number | undefined,
|
||||||
depth: undefined as number | undefined,
|
depth: undefined as number | undefined,
|
||||||
@@ -559,7 +593,7 @@ const loadDatacenterList = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载楼层列表(通过机柜下拉接口提取去重楼层)
|
// 加载楼层列表
|
||||||
const loadFloorList = async (datacenterId?: number, keyword?: string) => {
|
const loadFloorList = async (datacenterId?: number, keyword?: string) => {
|
||||||
if (!datacenterId) {
|
if (!datacenterId) {
|
||||||
floorList.value = []
|
floorList.value = []
|
||||||
@@ -567,20 +601,10 @@ const loadFloorList = async (datacenterId?: number, keyword?: string) => {
|
|||||||
}
|
}
|
||||||
loadingFloors.value = true
|
loadingFloors.value = true
|
||||||
try {
|
try {
|
||||||
const res: any = await fetchRackListByDatacenter(datacenterId, { name: keyword })
|
const res: any = await fetchFloorListByDatacenter(datacenterId, { name: keyword })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const list = res.details?.data ?? res.data ?? res.details ?? []
|
const list = res.details?.data ?? res.data ?? res.details ?? []
|
||||||
const rows = Array.isArray(list) ? list : []
|
floorList.value = 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) {
|
} catch (error) {
|
||||||
console.error('获取楼层列表失败:', error)
|
console.error('获取楼层列表失败:', error)
|
||||||
@@ -607,9 +631,36 @@ const loadSupplierList = async () => {
|
|||||||
// 数据中心变化时重新加载楼层列表
|
// 数据中心变化时重新加载楼层列表
|
||||||
const handleDatacenterChange = async (value: number) => {
|
const handleDatacenterChange = async (value: number) => {
|
||||||
form.value.floor_id = undefined
|
form.value.floor_id = undefined
|
||||||
|
form.value.room_id = undefined
|
||||||
|
roomList.value = []
|
||||||
await loadFloorList(value)
|
await loadFloorList(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadRoomList = async (floorId?: number, keyword?: string) => {
|
||||||
|
if (!floorId) {
|
||||||
|
roomList.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loadingRooms.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await fetchRoomListByFloor(floorId, { name: keyword })
|
||||||
|
if (res.code === 0) {
|
||||||
|
const list = res.details?.data ?? res.data ?? res.details ?? []
|
||||||
|
roomList.value = Array.isArray(list) ? list : []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取机房列表失败:', error)
|
||||||
|
roomList.value = []
|
||||||
|
} finally {
|
||||||
|
loadingRooms.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFloorChange = async (value: number) => {
|
||||||
|
form.value.room_id = undefined
|
||||||
|
await loadRoomList(value)
|
||||||
|
}
|
||||||
|
|
||||||
const handleFloorSearch = (keyword: string) => {
|
const handleFloorSearch = (keyword: string) => {
|
||||||
if (!form.value.datacenter_id) return
|
if (!form.value.datacenter_id) return
|
||||||
if (floorSearchTimer) {
|
if (floorSearchTimer) {
|
||||||
@@ -620,6 +671,16 @@ const handleFloorSearch = (keyword: string) => {
|
|||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleRoomSearch = (keyword: string) => {
|
||||||
|
if (!form.value.floor_id) return
|
||||||
|
if (roomSearchTimer) {
|
||||||
|
window.clearTimeout(roomSearchTimer)
|
||||||
|
}
|
||||||
|
roomSearchTimer = window.setTimeout(() => {
|
||||||
|
loadRoomList(form.value.floor_id, keyword?.trim() || undefined)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
// 监听对话框显示状态
|
// 监听对话框显示状态
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
@@ -632,6 +693,7 @@ watch(
|
|||||||
code: props.rack.code || '',
|
code: props.rack.code || '',
|
||||||
datacenter_id: props.rack.datacenter_id,
|
datacenter_id: props.rack.datacenter_id,
|
||||||
floor_id: props.rack.floor_id,
|
floor_id: props.rack.floor_id,
|
||||||
|
room_id: props.rack.room_id,
|
||||||
height: props.rack.height || 42,
|
height: props.rack.height || 42,
|
||||||
width: props.rack.width,
|
width: props.rack.width,
|
||||||
depth: props.rack.depth,
|
depth: props.rack.depth,
|
||||||
@@ -667,6 +729,9 @@ watch(
|
|||||||
if (props.rack.datacenter_id) {
|
if (props.rack.datacenter_id) {
|
||||||
loadFloorList(props.rack.datacenter_id)
|
loadFloorList(props.rack.datacenter_id)
|
||||||
}
|
}
|
||||||
|
if (props.rack.floor_id) {
|
||||||
|
loadRoomList(props.rack.floor_id)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新建模式:重置表单
|
// 新建模式:重置表单
|
||||||
form.value = {
|
form.value = {
|
||||||
@@ -674,6 +739,7 @@ watch(
|
|||||||
code: '',
|
code: '',
|
||||||
datacenter_id: undefined,
|
datacenter_id: undefined,
|
||||||
floor_id: undefined,
|
floor_id: undefined,
|
||||||
|
room_id: undefined,
|
||||||
height: 42,
|
height: 42,
|
||||||
width: undefined,
|
width: undefined,
|
||||||
depth: undefined,
|
depth: undefined,
|
||||||
@@ -705,6 +771,8 @@ watch(
|
|||||||
description: '',
|
description: '',
|
||||||
remarks: '',
|
remarks: '',
|
||||||
}
|
}
|
||||||
|
floorList.value = []
|
||||||
|
roomList.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -722,6 +790,7 @@ const handleOk = async () => {
|
|||||||
code: form.value.code,
|
code: form.value.code,
|
||||||
datacenter_id: form.value.datacenter_id,
|
datacenter_id: form.value.datacenter_id,
|
||||||
floor_id: form.value.floor_id,
|
floor_id: form.value.floor_id,
|
||||||
|
room_id: form.value.room_id,
|
||||||
height: form.value.height,
|
height: form.value.height,
|
||||||
width: form.value.width,
|
width: form.value.width,
|
||||||
depth: form.value.depth,
|
depth: form.value.depth,
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ export const searchFormConfig: FormItem[] = [
|
|||||||
options: [], // 需要动态加载
|
options: [], // 需要动态加载
|
||||||
span: 6,
|
span: 6,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'room_id',
|
||||||
|
label: '机房',
|
||||||
|
type: 'select',
|
||||||
|
placeholder: '请选择机房',
|
||||||
|
options: [], // 需要动态加载
|
||||||
|
span: 6,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'rack_type',
|
field: 'rack_type',
|
||||||
label: '机柜类型',
|
label: '机柜类型',
|
||||||
|
|||||||
@@ -113,9 +113,10 @@ import { columns as columnsConfig } from './config/columns'
|
|||||||
import {
|
import {
|
||||||
fetchRackList,
|
fetchRackList,
|
||||||
fetchDatacenterList,
|
fetchDatacenterList,
|
||||||
fetchRackListByDatacenter,
|
|
||||||
deleteRack,
|
deleteRack,
|
||||||
} from '@/api/ops/rack'
|
} from '@/api/ops/rack'
|
||||||
|
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||||
|
import { fetchRoomListByFloor } from '@/api/ops/room'
|
||||||
import RackDetailDialog from './components/RackDetailDialog.vue'
|
import RackDetailDialog from './components/RackDetailDialog.vue'
|
||||||
import RackFormDialog from './components/RackFormDialog.vue'
|
import RackFormDialog from './components/RackFormDialog.vue'
|
||||||
|
|
||||||
@@ -128,6 +129,7 @@ const formModel = ref({
|
|||||||
keyword: '',
|
keyword: '',
|
||||||
datacenter_id: undefined,
|
datacenter_id: undefined,
|
||||||
floor_id: undefined,
|
floor_id: undefined,
|
||||||
|
room_id: undefined,
|
||||||
rack_type: undefined,
|
rack_type: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
})
|
})
|
||||||
@@ -141,8 +143,10 @@ const pagination = reactive({
|
|||||||
// 表单项配置
|
// 表单项配置
|
||||||
const datacenterSelectOptions = ref<{ label: string; value: number }[]>([])
|
const datacenterSelectOptions = ref<{ label: string; value: number }[]>([])
|
||||||
const floorSelectOptions = ref<{ label: string; value: number }[]>([])
|
const floorSelectOptions = ref<{ label: string; value: number }[]>([])
|
||||||
|
const roomSelectOptions = ref<{ label: string; value: number }[]>([])
|
||||||
let datacenterSearchTimer: number | undefined
|
let datacenterSearchTimer: number | undefined
|
||||||
let floorSearchTimer: number | undefined
|
let floorSearchTimer: number | undefined
|
||||||
|
let roomSearchTimer: number | undefined
|
||||||
|
|
||||||
const formItems = computed<FormItem[]>(() =>
|
const formItems = computed<FormItem[]>(() =>
|
||||||
searchFormConfig.map((item) => {
|
searchFormConfig.map((item) => {
|
||||||
@@ -163,6 +167,15 @@ const formItems = computed<FormItem[]>(() =>
|
|||||||
disabled: !formModel.value.datacenter_id,
|
disabled: !formModel.value.datacenter_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (item.field === 'room_id') {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
options: roomSelectOptions.value,
|
||||||
|
allowSearch: true,
|
||||||
|
onSearch: handleRoomSearch,
|
||||||
|
disabled: !formModel.value.floor_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
return item
|
return item
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -199,25 +212,18 @@ const loadDatacenterOptions = async (keyword?: string) => {
|
|||||||
const loadFloorOptions = async (datacenterId?: number, keyword?: string) => {
|
const loadFloorOptions = async (datacenterId?: number, keyword?: string) => {
|
||||||
if (!datacenterId) {
|
if (!datacenterId) {
|
||||||
floorSelectOptions.value = []
|
floorSelectOptions.value = []
|
||||||
|
roomSelectOptions.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res: any = await fetchRackListByDatacenter(datacenterId, {
|
const res: any = await fetchFloorListByDatacenter(datacenterId, { name: keyword })
|
||||||
name: keyword,
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const list = res.details?.data ?? res.data ?? res.details ?? []
|
const list = res.details?.data ?? res.data ?? res.details ?? []
|
||||||
const rows = Array.isArray(list) ? list : []
|
const rows = Array.isArray(list) ? list : []
|
||||||
const floorMap = new Map<number, { label: string; value: number }>()
|
floorSelectOptions.value = rows.map((floor: any) => ({
|
||||||
rows.forEach((rack: any) => {
|
label: floor.name || String(floor.id),
|
||||||
const floor = rack?.floor
|
value: floor.id,
|
||||||
if (!floor?.id || floorMap.has(floor.id)) return
|
}))
|
||||||
floorMap.set(floor.id, {
|
|
||||||
label: floor.name || String(floor.id),
|
|
||||||
value: floor.id,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
floorSelectOptions.value = Array.from(floorMap.values())
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载楼层列表失败:', error)
|
console.error('加载楼层列表失败:', error)
|
||||||
@@ -226,6 +232,28 @@ const loadFloorOptions = async (datacenterId?: number, keyword?: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadRoomOptions = async (floorId?: number, keyword?: string) => {
|
||||||
|
if (!floorId) {
|
||||||
|
roomSelectOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res: any = await fetchRoomListByFloor(floorId, { name: keyword })
|
||||||
|
if (res.code === 0) {
|
||||||
|
const list = res.details?.data ?? res.data ?? res.details ?? []
|
||||||
|
const rows = Array.isArray(list) ? list : []
|
||||||
|
roomSelectOptions.value = rows.map((room: any) => ({
|
||||||
|
label: room.name || String(room.id),
|
||||||
|
value: room.id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载机房列表失败:', error)
|
||||||
|
Message.error('加载机房列表失败')
|
||||||
|
roomSelectOptions.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleDatacenterSearch = (keyword: string) => {
|
const handleDatacenterSearch = (keyword: string) => {
|
||||||
if (datacenterSearchTimer) {
|
if (datacenterSearchTimer) {
|
||||||
window.clearTimeout(datacenterSearchTimer)
|
window.clearTimeout(datacenterSearchTimer)
|
||||||
@@ -245,16 +273,37 @@ const handleFloorSearch = (keyword: string) => {
|
|||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleRoomSearch = (keyword: string) => {
|
||||||
|
if (!formModel.value.floor_id) return
|
||||||
|
if (roomSearchTimer) {
|
||||||
|
window.clearTimeout(roomSearchTimer)
|
||||||
|
}
|
||||||
|
roomSearchTimer = window.setTimeout(() => {
|
||||||
|
loadRoomOptions(formModel.value.floor_id, keyword?.trim() || undefined)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => formModel.value.datacenter_id,
|
() => formModel.value.datacenter_id,
|
||||||
(newId, oldId) => {
|
(newId, oldId) => {
|
||||||
if (newId !== oldId) {
|
if (newId !== oldId) {
|
||||||
formModel.value.floor_id = undefined
|
formModel.value.floor_id = undefined
|
||||||
|
formModel.value.room_id = undefined
|
||||||
}
|
}
|
||||||
loadFloorOptions(newId)
|
loadFloorOptions(newId)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => formModel.value.floor_id,
|
||||||
|
(newId, oldId) => {
|
||||||
|
if (newId !== oldId) {
|
||||||
|
formModel.value.room_id = undefined
|
||||||
|
}
|
||||||
|
loadRoomOptions(newId)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// 获取机柜类型颜色
|
// 获取机柜类型颜色
|
||||||
const getRackTypeColor = (type?: string) => {
|
const getRackTypeColor = (type?: string) => {
|
||||||
const colorMap: Record<string, string> = {
|
const colorMap: Record<string, string> = {
|
||||||
@@ -314,6 +363,7 @@ const fetchRacks = async () => {
|
|||||||
keyword: formModel.value.keyword || undefined,
|
keyword: formModel.value.keyword || undefined,
|
||||||
datacenter_id: formModel.value.datacenter_id ?? undefined,
|
datacenter_id: formModel.value.datacenter_id ?? undefined,
|
||||||
floor_id: formModel.value.floor_id ?? undefined,
|
floor_id: formModel.value.floor_id ?? undefined,
|
||||||
|
room_id: formModel.value.room_id ?? undefined,
|
||||||
rack_type: formModel.value.rack_type || undefined,
|
rack_type: formModel.value.rack_type || undefined,
|
||||||
status: formModel.value.status || undefined,
|
status: formModel.value.status || undefined,
|
||||||
}
|
}
|
||||||
@@ -350,6 +400,7 @@ const handleReset = () => {
|
|||||||
keyword: '',
|
keyword: '',
|
||||||
datacenter_id: undefined,
|
datacenter_id: undefined,
|
||||||
floor_id: undefined,
|
floor_id: undefined,
|
||||||
|
room_id: undefined,
|
||||||
rack_type: undefined,
|
rack_type: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
}
|
}
|
||||||
@@ -411,7 +462,7 @@ const handleDelete = async (record: any) => {
|
|||||||
// U位管理
|
// U位管理
|
||||||
const handleUnitManagement = (record: any) => {
|
const handleUnitManagement = (record: any) => {
|
||||||
router.push({
|
router.push({
|
||||||
path: '/ops/datacenter/u-position',
|
path: '/datacenter/u-position',
|
||||||
query: { rack_id: record.id, rack_name: record.name },
|
query: { rack_id: record.id, rack_name: record.name },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
title="机房详情"
|
||||||
|
width="700px"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@update:visible="handleVisibleChange"
|
||||||
|
:footer="false"
|
||||||
|
>
|
||||||
|
<a-spin :loading="loading" style="width: 100%">
|
||||||
|
<a-descriptions :column="2" bordered v-if="roomDetail">
|
||||||
|
<a-descriptions-item label="机房名称" :span="2">
|
||||||
|
{{ roomDetail.name || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="机房编码">
|
||||||
|
{{ roomDetail.code || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="状态">
|
||||||
|
<a-tag :color="statusMap[roomDetail.status]?.color || 'gray'">
|
||||||
|
{{ statusMap[roomDetail.status]?.text || roomDetail.status || '-' }}
|
||||||
|
</a-tag>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="所属中心">
|
||||||
|
{{ roomDetail.datacenter?.name || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="所属楼层">
|
||||||
|
{{ roomDetail.floor?.name || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="描述" :span="2">
|
||||||
|
{{ roomDetail.description || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="创建时间">
|
||||||
|
{{ formatDate(roomDetail.created_at) }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="更新时间">
|
||||||
|
{{ formatDate(roomDetail.updated_at) }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-spin>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import { fetchRoomDetail } from '@/api/ops/room'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean
|
||||||
|
roomId?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:visible', value: boolean): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const roomDetail = ref<any>(null)
|
||||||
|
|
||||||
|
const statusMap: Record<string, { text: string; color: string }> = {
|
||||||
|
planning: { text: '规划中', color: 'blue' },
|
||||||
|
construction: { text: '建设中', color: 'orange' },
|
||||||
|
operating: { text: '运营中', color: 'green' },
|
||||||
|
maintenance: { text: '维护中', color: 'gold' },
|
||||||
|
offline: { text: '已下线', color: 'red' },
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (dateStr?: string) => {
|
||||||
|
if (!dateStr) return '-'
|
||||||
|
const date = new Date(dateStr)
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadRoomDetail = async () => {
|
||||||
|
if (!props.roomId) return
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await fetchRoomDetail(props.roomId)
|
||||||
|
if (res.code === 0) {
|
||||||
|
roomDetail.value = res.details
|
||||||
|
} else {
|
||||||
|
Message.error(res.message || '获取机房详情失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取机房详情失败:', error)
|
||||||
|
Message.error('获取机房详情失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal && props.roomId) {
|
||||||
|
loadRoomDetail()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVisibleChange = (visible: boolean) => {
|
||||||
|
emit('update:visible', visible)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'RoomDetailDialog',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
:title="isEdit ? '编辑机房' : '新建机房'"
|
||||||
|
width="700px"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@update:visible="handleVisibleChange"
|
||||||
|
:confirm-loading="submitting"
|
||||||
|
>
|
||||||
|
<a-form :model="form" layout="vertical" ref="formRef">
|
||||||
|
<a-form-item
|
||||||
|
label="机房名称"
|
||||||
|
field="name"
|
||||||
|
:rules="[{ required: true, message: '请输入机房名称' }]"
|
||||||
|
>
|
||||||
|
<a-input v-model="form.name" placeholder="请输入机房名称" :max-length="200" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
label="机房编码"
|
||||||
|
field="code"
|
||||||
|
:rules="[{ required: true, message: '请输入机房编码' }]"
|
||||||
|
>
|
||||||
|
<a-input v-model="form.code" placeholder="请输入机房编码" :max-length="100" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<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-form-item
|
||||||
|
label="所属楼层"
|
||||||
|
field="floor_id"
|
||||||
|
:rules="[{ required: true, message: '请选择所属楼层' }]"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model="form.floor_id"
|
||||||
|
placeholder="请选择所属楼层"
|
||||||
|
:loading="loadingFloors"
|
||||||
|
:disabled="!form.datacenter_id"
|
||||||
|
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-form-item label="状态" field="status">
|
||||||
|
<a-select v-model="form.status" placeholder="请选择状态">
|
||||||
|
<a-option value="planning">规划中</a-option>
|
||||||
|
<a-option value="construction">建设中</a-option>
|
||||||
|
<a-option value="operating">运营中</a-option>
|
||||||
|
<a-option value="maintenance">维护中</a-option>
|
||||||
|
<a-option value="offline">已下线</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="描述" field="description">
|
||||||
|
<a-textarea
|
||||||
|
v-model="form.description"
|
||||||
|
placeholder="请输入描述"
|
||||||
|
:auto-size="{ minRows: 4, maxRows: 8 }"
|
||||||
|
:max-length="500"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import { createRoom, updateRoom } from '@/api/ops/room'
|
||||||
|
import { fetchDatacenterList } from '@/api/ops/floor'
|
||||||
|
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||||
|
|
||||||
|
interface Room {
|
||||||
|
id?: number
|
||||||
|
name?: string
|
||||||
|
code?: string
|
||||||
|
datacenter_id?: number
|
||||||
|
floor_id?: number
|
||||||
|
status?: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean
|
||||||
|
room: Room | 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 submitting = ref(false)
|
||||||
|
const datacenterList = ref<any[]>([])
|
||||||
|
const floorList = ref<any[]>([])
|
||||||
|
let floorSearchTimer: number | undefined
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
datacenter_id: undefined as number | undefined,
|
||||||
|
floor_id: undefined as number | undefined,
|
||||||
|
status: 'planning',
|
||||||
|
description: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const isEdit = computed(() => !!props.room?.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 fetchFloorListByDatacenter(datacenterId, { name: keyword })
|
||||||
|
if (res.code === 0) {
|
||||||
|
const list = res.details?.data ?? res.data ?? res.details ?? []
|
||||||
|
floorList.value = Array.isArray(list) ? list : []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取楼层列表失败:', error)
|
||||||
|
floorList.value = []
|
||||||
|
} finally {
|
||||||
|
loadingFloors.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,
|
||||||
|
async (newVal) => {
|
||||||
|
if (!newVal) return
|
||||||
|
|
||||||
|
if (props.room && isEdit.value) {
|
||||||
|
form.value = {
|
||||||
|
name: props.room.name || '',
|
||||||
|
code: props.room.code || '',
|
||||||
|
datacenter_id: props.room.datacenter_id,
|
||||||
|
floor_id: props.room.floor_id,
|
||||||
|
status: props.room.status || 'planning',
|
||||||
|
description: props.room.description || '',
|
||||||
|
}
|
||||||
|
if (props.room.datacenter_id) {
|
||||||
|
await loadFloorList(props.room.datacenter_id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
form.value = {
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
datacenter_id: undefined,
|
||||||
|
floor_id: undefined,
|
||||||
|
status: 'planning',
|
||||||
|
description: '',
|
||||||
|
}
|
||||||
|
floorList.value = []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
status: form.value.status,
|
||||||
|
description: form.value.description,
|
||||||
|
}
|
||||||
|
|
||||||
|
let res
|
||||||
|
if (isEdit.value && props.room?.id) {
|
||||||
|
data.id = props.room.id
|
||||||
|
res = await updateRoom(data)
|
||||||
|
} else {
|
||||||
|
res = await createRoom(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDatacenterList()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'RoomFormDialog',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
52
src/views/ops/pages/datacenter/room/config/columns.ts
Normal file
52
src/views/ops/pages/datacenter/room/config/columns.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'
|
||||||
|
|
||||||
|
export const columns: TableColumnData[] = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
dataIndex: 'index',
|
||||||
|
slotName: 'index',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '机房名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '机房编码',
|
||||||
|
dataIndex: 'code',
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属中心',
|
||||||
|
dataIndex: 'datacenter',
|
||||||
|
slotName: 'datacenter',
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属楼层',
|
||||||
|
dataIndex: 'floor',
|
||||||
|
slotName: 'floor',
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
slotName: 'status',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '描述',
|
||||||
|
dataIndex: 'description',
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
width: 220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'actions',
|
||||||
|
slotName: 'actions',
|
||||||
|
width: 240,
|
||||||
|
fixed: 'right' as const,
|
||||||
|
},
|
||||||
|
]
|
||||||
37
src/views/ops/pages/datacenter/room/config/search-form.ts
Normal file
37
src/views/ops/pages/datacenter/room/config/search-form.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type { FormItem } from '@/components/search-form/types'
|
||||||
|
|
||||||
|
export const searchFormConfig: FormItem[] = [
|
||||||
|
{
|
||||||
|
field: 'keyword',
|
||||||
|
label: '关键词',
|
||||||
|
type: 'input',
|
||||||
|
placeholder: '请输入机房名称或编码',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'datacenter_id',
|
||||||
|
label: '数据中心',
|
||||||
|
type: 'select',
|
||||||
|
placeholder: '请选择数据中心',
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'floor_id',
|
||||||
|
label: '所属楼层',
|
||||||
|
type: 'select',
|
||||||
|
placeholder: '请选择所属楼层',
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
label: '状态',
|
||||||
|
type: 'select',
|
||||||
|
placeholder: '请选择状态',
|
||||||
|
options: [
|
||||||
|
{ label: '规划中', value: 'planning' },
|
||||||
|
{ label: '建设中', value: 'construction' },
|
||||||
|
{ label: '运营中', value: 'operating' },
|
||||||
|
{ label: '维护中', value: 'maintenance' },
|
||||||
|
{ label: '已下线', value: 'offline' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
326
src/views/ops/pages/datacenter/room/index.vue
Normal file
326
src/views/ops/pages/datacenter/room/index.vue
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<search-table
|
||||||
|
:form-model="formModel"
|
||||||
|
:form-items="formItems"
|
||||||
|
:data="tableData"
|
||||||
|
:columns="columns"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
title="机房管理"
|
||||||
|
search-button-text="查询"
|
||||||
|
reset-button-text="重置"
|
||||||
|
@update:form-model="handleFormModelUpdate"
|
||||||
|
@search="handleSearch"
|
||||||
|
@reset="handleReset"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
@refresh="handleRefresh"
|
||||||
|
>
|
||||||
|
<template #toolbar-left>
|
||||||
|
<a-button type="primary" @click="handleCreate">
|
||||||
|
<template #icon>
|
||||||
|
<icon-plus />
|
||||||
|
</template>
|
||||||
|
新建机房
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #index="{ rowIndex }">
|
||||||
|
{{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #datacenter="{ record }">
|
||||||
|
{{ record.datacenter?.name || '-' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #floor="{ record }">
|
||||||
|
{{ record.floor?.name || '-' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #status="{ record }">
|
||||||
|
<a-tag :color="statusMap[record.status]?.color || 'gray'">
|
||||||
|
{{ statusMap[record.status]?.text || record.status }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions="{ record }">
|
||||||
|
<a-button type="text" size="small" @click="handleDetail(record)">
|
||||||
|
详情
|
||||||
|
</a-button>
|
||||||
|
<a-button type="text" size="small" @click="handleEdit(record)">
|
||||||
|
编辑
|
||||||
|
</a-button>
|
||||||
|
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">
|
||||||
|
删除
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</search-table>
|
||||||
|
|
||||||
|
<room-form-dialog
|
||||||
|
v-model:visible="formVisible"
|
||||||
|
:room="editingRoom"
|
||||||
|
@success="handleFormSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<room-detail-dialog
|
||||||
|
v-model:visible="detailVisible"
|
||||||
|
:room-id="currentRoomId"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
|
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
||||||
|
import type { FormItem } from '@/components/search-form/types'
|
||||||
|
import SearchTable from '@/components/search-table/index.vue'
|
||||||
|
import { searchFormConfig } from './config/search-form'
|
||||||
|
import { columns as columnsConfig } from './config/columns'
|
||||||
|
import { deleteRoom, fetchRoomList } from '@/api/ops/room'
|
||||||
|
import { fetchDatacenterList, fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||||
|
import RoomFormDialog from './components/RoomFormDialog.vue'
|
||||||
|
import RoomDetailDialog from './components/RoomDetailDialog.vue'
|
||||||
|
|
||||||
|
const statusMap: Record<string, { text: string; color: string }> = {
|
||||||
|
planning: { text: '规划中', color: 'blue' },
|
||||||
|
construction: { text: '建设中', color: 'orange' },
|
||||||
|
operating: { text: '运营中', color: 'green' },
|
||||||
|
maintenance: { text: '维护中', color: 'gold' },
|
||||||
|
offline: { text: '已下线', color: 'red' },
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref<any[]>([])
|
||||||
|
const formModel = ref({
|
||||||
|
keyword: '',
|
||||||
|
datacenter_id: undefined as number | undefined,
|
||||||
|
floor_id: undefined as number | undefined,
|
||||||
|
status: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
const datacenterSelectOptions = ref<{ label: string; value: number }[]>([])
|
||||||
|
const floorSelectOptions = ref<{ label: string; value: number }[]>([])
|
||||||
|
let datacenterSearchTimer: number | undefined
|
||||||
|
let floorSearchTimer: number | undefined
|
||||||
|
|
||||||
|
const formItems = computed<FormItem[]>(() =>
|
||||||
|
searchFormConfig.map((item) => {
|
||||||
|
if (item.field === 'datacenter_id') {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
options: datacenterSelectOptions.value,
|
||||||
|
allowSearch: true,
|
||||||
|
onSearch: handleDatacenterSearch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.field === 'floor_id') {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
options: floorSelectOptions.value,
|
||||||
|
allowSearch: true,
|
||||||
|
onSearch: handleFloorSearch,
|
||||||
|
disabled: !formModel.value.datacenter_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const columns = computed(() => columnsConfig)
|
||||||
|
|
||||||
|
const currentRoomId = ref<number | undefined>(undefined)
|
||||||
|
const editingRoom = ref<any>(null)
|
||||||
|
|
||||||
|
const formVisible = ref(false)
|
||||||
|
const detailVisible = ref(false)
|
||||||
|
|
||||||
|
const loadDatacenterOptions = async (keyword?: string) => {
|
||||||
|
try {
|
||||||
|
const res: any = await fetchDatacenterList({ keyword })
|
||||||
|
if (res.code === 0) {
|
||||||
|
const list = res.details || []
|
||||||
|
datacenterSelectOptions.value = Array.isArray(list)
|
||||||
|
? list.map((d: any) => ({
|
||||||
|
label: d.name || d.code || String(d.id),
|
||||||
|
value: d.id,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据中心列表失败:', error)
|
||||||
|
Message.error('获取数据中心列表失败')
|
||||||
|
datacenterSelectOptions.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadFloorOptions = async (datacenterId?: number, keyword?: string) => {
|
||||||
|
if (!datacenterId) {
|
||||||
|
floorSelectOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res: any = await fetchFloorListByDatacenter(datacenterId, { name: keyword })
|
||||||
|
if (res.code === 0) {
|
||||||
|
const list = res.details?.data ?? res.data ?? res.details ?? []
|
||||||
|
floorSelectOptions.value = Array.isArray(list)
|
||||||
|
? list.map((f: any) => ({
|
||||||
|
label: f.name || String(f.id),
|
||||||
|
value: f.id,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取楼层列表失败:', error)
|
||||||
|
Message.error('获取楼层列表失败')
|
||||||
|
floorSelectOptions.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDatacenterSearch = (keyword: string) => {
|
||||||
|
if (datacenterSearchTimer) {
|
||||||
|
window.clearTimeout(datacenterSearchTimer)
|
||||||
|
}
|
||||||
|
datacenterSearchTimer = window.setTimeout(() => {
|
||||||
|
loadDatacenterOptions(keyword?.trim() || undefined)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFloorSearch = (keyword: string) => {
|
||||||
|
if (!formModel.value.datacenter_id) return
|
||||||
|
if (floorSearchTimer) {
|
||||||
|
window.clearTimeout(floorSearchTimer)
|
||||||
|
}
|
||||||
|
floorSearchTimer = window.setTimeout(() => {
|
||||||
|
loadFloorOptions(formModel.value.datacenter_id, keyword?.trim() || undefined)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => formModel.value.datacenter_id,
|
||||||
|
(newId, oldId) => {
|
||||||
|
if (newId !== oldId) {
|
||||||
|
formModel.value.floor_id = undefined
|
||||||
|
}
|
||||||
|
loadFloorOptions(newId)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const fetchRooms = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params: any = {
|
||||||
|
page: pagination.current,
|
||||||
|
page_size: pagination.pageSize,
|
||||||
|
keyword: formModel.value.keyword || undefined,
|
||||||
|
datacenter_id: formModel.value.datacenter_id ?? undefined,
|
||||||
|
floor_id: formModel.value.floor_id ?? undefined,
|
||||||
|
status: formModel.value.status || undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const res: any = await fetchRoomList(params)
|
||||||
|
tableData.value = res.details?.data || []
|
||||||
|
pagination.total = res.details?.total || 0
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取机房列表失败:', error)
|
||||||
|
Message.error('获取机房列表失败')
|
||||||
|
tableData.value = []
|
||||||
|
pagination.total = 0
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.current = 1
|
||||||
|
fetchRooms()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFormModelUpdate = (value: any) => {
|
||||||
|
formModel.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
formModel.value = {
|
||||||
|
keyword: '',
|
||||||
|
datacenter_id: undefined,
|
||||||
|
floor_id: undefined,
|
||||||
|
status: '',
|
||||||
|
}
|
||||||
|
pagination.current = 1
|
||||||
|
fetchRooms()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePageChange = (current: number) => {
|
||||||
|
pagination.current = current
|
||||||
|
fetchRooms()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
fetchRooms()
|
||||||
|
Message.success('数据已刷新')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
editingRoom.value = null
|
||||||
|
formVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (record: any) => {
|
||||||
|
editingRoom.value = record
|
||||||
|
formVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDetail = (record: any) => {
|
||||||
|
currentRoomId.value = record.id
|
||||||
|
detailVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (record: any) => {
|
||||||
|
try {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: `确认删除机房 ${record.name} 吗?`,
|
||||||
|
onOk: async () => {
|
||||||
|
const res: any = await deleteRoom(record.id)
|
||||||
|
if (res.code === 0) {
|
||||||
|
Message.success('删除成功')
|
||||||
|
fetchRooms()
|
||||||
|
} else {
|
||||||
|
Message.error(res.message || '删除失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除机房失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFormSuccess = () => {
|
||||||
|
formVisible.value = false
|
||||||
|
fetchRooms()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadDatacenterOptions()
|
||||||
|
fetchRooms()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'DataCenterRoom',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -182,6 +182,7 @@
|
|||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
import { Message, Modal } from '@arco-design/web-vue'
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
import { IconStorage, IconApps, IconPlus, IconLock, IconRefresh } from '@arco-design/web-vue/es/icon'
|
import { IconStorage, IconApps, IconPlus, IconLock, IconRefresh } from '@arco-design/web-vue/es/icon'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
import { fetchUnitList, allocateUnit, reserveUnit, cancelReservation, releaseUnit, updateUnitStatus } from '@/api/ops/unit'
|
import { fetchUnitList, allocateUnit, reserveUnit, cancelReservation, releaseUnit, updateUnitStatus } from '@/api/ops/unit'
|
||||||
import { fetchDatacenterList, fetchRackListByRoom } from '@/api/ops/rack'
|
import { fetchDatacenterList, fetchRackListByRoom } from '@/api/ops/rack'
|
||||||
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||||
@@ -210,6 +211,7 @@ let datacenterSearchTimer: number | undefined
|
|||||||
let floorSearchTimer: number | undefined
|
let floorSearchTimer: number | undefined
|
||||||
let roomSearchTimer: number | undefined
|
let roomSearchTimer: number | undefined
|
||||||
let rackSearchTimer: number | undefined
|
let rackSearchTimer: number | undefined
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
// 对话框可见性
|
// 对话框可见性
|
||||||
const allocateVisible = ref(false)
|
const allocateVisible = ref(false)
|
||||||
@@ -613,6 +615,11 @@ const handleCancelReservation = async (record: any) => {
|
|||||||
// 初始化
|
// 初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchDatacenters()
|
fetchDatacenters()
|
||||||
|
const rackId = Number(route.query.rack_id)
|
||||||
|
if (Number.isFinite(rackId) && rackId > 0) {
|
||||||
|
selectedRackId.value = rackId
|
||||||
|
fetchUnits(rackId)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,23 @@
|
|||||||
<a-input v-model="formData.location" placeholder="机房A-机架01" />
|
<a-input v-model="formData.location" placeholder="机房A-机架01" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item field="asset_id" label="关联资产">
|
||||||
|
<a-select
|
||||||
|
v-model="formData.asset_id"
|
||||||
|
:options="assetOptions"
|
||||||
|
:loading="assetLoading"
|
||||||
|
allow-clear
|
||||||
|
show-search
|
||||||
|
:filter-option="false"
|
||||||
|
placeholder="请选择资产(可选)"
|
||||||
|
@search="handleAssetSearch"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-row :gutter="20">
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item field="tags" label="标签">
|
<a-form-item field="tags" label="标签">
|
||||||
<a-input v-model="formData.tags" placeholder="多个标签逗号分隔" />
|
<a-input v-model="formData.tags" placeholder="多个标签逗号分隔" />
|
||||||
@@ -147,6 +164,7 @@ import { Message } from '@arco-design/web-vue'
|
|||||||
import type { FormInstance } from '@arco-design/web-vue'
|
import type { FormInstance } from '@arco-design/web-vue'
|
||||||
import { createServer, updateServer } from '@/api/ops/server'
|
import { createServer, updateServer } from '@/api/ops/server'
|
||||||
import type { ServerFormData, ServerItem } from '@/api/ops/server'
|
import type { ServerFormData, ServerItem } from '@/api/ops/server'
|
||||||
|
import { fetchAssetAll } from '@/api/ops/asset'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean
|
visible: boolean
|
||||||
@@ -161,6 +179,9 @@ const emit = defineEmits(['update:visible', 'success'])
|
|||||||
|
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const confirmLoading = ref(false)
|
const confirmLoading = ref(false)
|
||||||
|
const assetLoading = ref(false)
|
||||||
|
const assetOptions = ref<{ label: string; value: number }[]>([])
|
||||||
|
let assetSearchTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
const isEdit = computed(() => !!props.record?.id)
|
const isEdit = computed(() => !!props.record?.id)
|
||||||
|
|
||||||
@@ -176,6 +197,7 @@ const formData = reactive<ServerFormData>({
|
|||||||
server_type: '',
|
server_type: '',
|
||||||
tags: '',
|
tags: '',
|
||||||
location: '',
|
location: '',
|
||||||
|
asset_id: undefined,
|
||||||
remote_access: '',
|
remote_access: '',
|
||||||
remote_port: 0,
|
remote_port: 0,
|
||||||
agent_config: '',
|
agent_config: '',
|
||||||
@@ -280,6 +302,7 @@ watch(
|
|||||||
() => props.visible,
|
() => props.visible,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
|
loadAssetOptions()
|
||||||
if (isEdit.value && props.record) {
|
if (isEdit.value && props.record) {
|
||||||
Object.assign(formData, {
|
Object.assign(formData, {
|
||||||
server_identity: props.record.server_identity || '',
|
server_identity: props.record.server_identity || '',
|
||||||
@@ -293,6 +316,7 @@ watch(
|
|||||||
server_type: props.record.server_type || '',
|
server_type: props.record.server_type || '',
|
||||||
tags: props.record.tags || '',
|
tags: props.record.tags || '',
|
||||||
location: props.record.location || '',
|
location: props.record.location || '',
|
||||||
|
asset_id: props.record.asset_id,
|
||||||
remote_access: props.record.remote_access || '',
|
remote_access: props.record.remote_access || '',
|
||||||
remote_port: props.record.remote_port || 0,
|
remote_port: props.record.remote_port || 0,
|
||||||
agent_config: props.record.agent_config || '',
|
agent_config: props.record.agent_config || '',
|
||||||
@@ -316,6 +340,7 @@ watch(
|
|||||||
server_type: '',
|
server_type: '',
|
||||||
tags: '',
|
tags: '',
|
||||||
location: '',
|
location: '',
|
||||||
|
asset_id: undefined,
|
||||||
remote_access: '',
|
remote_access: '',
|
||||||
remote_port: 0,
|
remote_port: 0,
|
||||||
agent_config: '',
|
agent_config: '',
|
||||||
@@ -330,6 +355,39 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async function loadAssetOptions(keyword?: string) {
|
||||||
|
assetLoading.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await fetchAssetAll({ keyword: (keyword || '').trim() || undefined })
|
||||||
|
if (res?.code !== 0) {
|
||||||
|
Message.warning(res?.message || '加载资产列表失败')
|
||||||
|
assetOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const rows = res?.details || []
|
||||||
|
assetOptions.value = rows.map((item: any) => ({
|
||||||
|
value: Number(item.id),
|
||||||
|
label: `${item.asset_code || item.id} | ${item.asset_name || '-'}`,
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载资产列表失败:', error)
|
||||||
|
Message.warning('加载资产列表失败')
|
||||||
|
assetOptions.value = []
|
||||||
|
} finally {
|
||||||
|
assetLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 资产下拉使用远程模糊查询,避免一次加载全部资产。
|
||||||
|
function handleAssetSearch(keyword: string) {
|
||||||
|
if (assetSearchTimer) {
|
||||||
|
clearTimeout(assetSearchTimer)
|
||||||
|
}
|
||||||
|
assetSearchTimer = setTimeout(() => {
|
||||||
|
loadAssetOptions(keyword)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => formData.host,
|
() => formData.host,
|
||||||
() => {
|
() => {
|
||||||
@@ -362,6 +420,7 @@ const handleOk = async () => {
|
|||||||
server_type: formData.server_type,
|
server_type: formData.server_type,
|
||||||
tags: formData.tags,
|
tags: formData.tags,
|
||||||
location: formData.location,
|
location: formData.location,
|
||||||
|
asset_id: formData.asset_id ?? (isEdit.value ? 0 : undefined),
|
||||||
remote_access: formData.remote_access,
|
remote_access: formData.remote_access,
|
||||||
remote_port: formData.remote_port,
|
remote_port: formData.remote_port,
|
||||||
agent_config: formData.agent_config,
|
agent_config: formData.agent_config,
|
||||||
|
|||||||
@@ -289,8 +289,6 @@ const subStatusItems = computed(() => {
|
|||||||
{ key: 'cpu_status', label: 'CPU' },
|
{ key: 'cpu_status', label: 'CPU' },
|
||||||
{ key: 'memory_status', label: '内存' },
|
{ key: 'memory_status', label: '内存' },
|
||||||
{ key: 'disk_status', label: '磁盘' },
|
{ key: 'disk_status', label: '磁盘' },
|
||||||
{ key: 'network_status', label: '网络' },
|
|
||||||
{ key: 'raid_status', label: 'RAID' },
|
|
||||||
]
|
]
|
||||||
return fields.map((f) => ({
|
return fields.map((f) => ({
|
||||||
key: String(f.key),
|
key: String(f.key),
|
||||||
|
|||||||
Reference in New Issue
Block a user