fix
This commit is contained in:
@@ -17,6 +17,7 @@ export interface ServerItem {
|
||||
server_type: string
|
||||
tags: string
|
||||
location: string
|
||||
asset_id?: number
|
||||
remote_access: string
|
||||
remote_port: number
|
||||
agent_config: string
|
||||
@@ -60,6 +61,7 @@ export interface ServerFormData {
|
||||
server_type?: string
|
||||
tags?: string
|
||||
location?: string
|
||||
asset_id?: number
|
||||
remote_access?: string
|
||||
remote_port?: number
|
||||
agent_config?: string
|
||||
|
||||
@@ -766,6 +766,22 @@ export const localMenuFlatItems: MenuItem[] = [
|
||||
sort_key: 39,
|
||||
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,
|
||||
identity: '019b591d-0343-7ce7-91bd-d82497ea0a11',
|
||||
|
||||
@@ -824,6 +824,23 @@ export const localMenuItems: MenuItem[] = [
|
||||
created_at: '2025-12-26T13:23:52.220159+08:00',
|
||||
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-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-col :span="5">
|
||||
<a-form-item label="所属数据中心" field="datacenter_id">
|
||||
<a-select
|
||||
v-model="form.datacenter_id"
|
||||
@@ -229,7 +229,7 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-col :span="5">
|
||||
<a-form-item label="所属楼层" field="floor_id">
|
||||
<a-select
|
||||
v-model="form.floor_id"
|
||||
@@ -251,7 +251,30 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</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-select
|
||||
v-model="form.rack_id"
|
||||
@@ -259,7 +282,7 @@
|
||||
allow-clear
|
||||
allow-search
|
||||
:loading="rackLoading"
|
||||
:disabled="!form.floor_id"
|
||||
:disabled="!form.room_id"
|
||||
:filter-option="false"
|
||||
@search="handleRackSearch"
|
||||
>
|
||||
@@ -273,7 +296,7 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<a-col :span="2">
|
||||
<a-form-item label="起始U位" field="unit_start">
|
||||
<a-input-number
|
||||
v-model="form.unit_start"
|
||||
@@ -284,7 +307,7 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<a-col :span="2">
|
||||
<a-form-item label="结束U位" field="unit_end">
|
||||
<a-input-number
|
||||
v-model="form.unit_end"
|
||||
@@ -387,8 +410,9 @@ import {
|
||||
AssetForm,
|
||||
} from '@/api/ops/asset'
|
||||
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 { fetchRoomListByFloor } from '@/api/ops/room'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -406,6 +430,7 @@ const categoryOptions = ref<any[]>([])
|
||||
const supplierOptions = ref<any[]>([])
|
||||
const datacenterOptions = ref<{ label: string; value: number }[]>([])
|
||||
const floorOptions = ref<{ label: string; value: number }[]>([])
|
||||
const roomOptions = ref<any[]>([])
|
||||
const rackOptions = ref<any[]>([])
|
||||
|
||||
// 加载状态
|
||||
@@ -413,11 +438,13 @@ const categoryLoading = ref(false)
|
||||
const supplierLoading = ref(false)
|
||||
const datacenterLoading = ref(false)
|
||||
const floorLoading = ref(false)
|
||||
const roomLoading = ref(false)
|
||||
const rackLoading = ref(false)
|
||||
|
||||
// 搜索防抖定时器
|
||||
let datacenterSearchTimer: number | undefined
|
||||
let floorSearchTimer: number | undefined
|
||||
let roomSearchTimer: number | undefined
|
||||
let rackSearchTimer: number | undefined
|
||||
|
||||
// 表单数据
|
||||
@@ -574,13 +601,13 @@ const handleFloorSearch = (keyword: string) => {
|
||||
|
||||
// 加载机柜列表
|
||||
const fetchRacks = async (keyword?: string) => {
|
||||
if (!form.value.floor_id) {
|
||||
if (!form.value.room_id) {
|
||||
rackOptions.value = []
|
||||
return
|
||||
}
|
||||
rackLoading.value = true
|
||||
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)
|
||||
} catch (error) {
|
||||
console.error('获取机柜列表失败:', error)
|
||||
@@ -592,7 +619,7 @@ const fetchRacks = async (keyword?: string) => {
|
||||
|
||||
// 机柜搜索(带防抖)
|
||||
const handleRackSearch = (keyword: string) => {
|
||||
if (!form.value.floor_id) return
|
||||
if (!form.value.room_id) return
|
||||
if (rackSearchTimer) {
|
||||
window.clearTimeout(rackSearchTimer)
|
||||
}
|
||||
@@ -601,11 +628,44 @@ const handleRackSearch = (keyword: string) => {
|
||||
}, 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) => {
|
||||
form.value.floor_id = undefined
|
||||
form.value.room_id = undefined
|
||||
form.value.rack_id = undefined
|
||||
floorOptions.value = []
|
||||
roomOptions.value = []
|
||||
rackOptions.value = []
|
||||
|
||||
if (value) {
|
||||
@@ -615,6 +675,18 @@ const handleDatacenterChange = 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
|
||||
rackOptions.value = []
|
||||
|
||||
@@ -675,7 +747,17 @@ const loadDeviceDetail = async () => {
|
||||
// 如果有楼层,加载机柜
|
||||
if (res.details.floor_id) {
|
||||
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)
|
||||
} catch (error) {
|
||||
console.error('加载机柜失败:', error)
|
||||
|
||||
@@ -56,6 +56,16 @@
|
||||
/>
|
||||
</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="area"
|
||||
@@ -152,6 +162,7 @@ interface Floor {
|
||||
name?: string
|
||||
datacenter_id?: number
|
||||
floor_number?: number
|
||||
status?: string
|
||||
area?: number
|
||||
height?: number
|
||||
load_bearing?: number
|
||||
@@ -184,6 +195,7 @@ const form = ref({
|
||||
name: '',
|
||||
datacenter_id: undefined as number | undefined,
|
||||
floor_number: 1,
|
||||
status: 'planning',
|
||||
area: undefined as number | undefined,
|
||||
height: undefined as number | undefined,
|
||||
load_bearing: undefined as number | undefined,
|
||||
@@ -252,6 +264,7 @@ watch(
|
||||
name: props.floor.name || '',
|
||||
datacenter_id: props.floor.datacenter_id,
|
||||
floor_number: props.floor.floor_number || 1,
|
||||
status: props.floor.status || 'planning',
|
||||
area: props.floor.area,
|
||||
height: props.floor.height,
|
||||
load_bearing: props.floor.load_bearing,
|
||||
@@ -277,6 +290,7 @@ watch(
|
||||
name: '',
|
||||
datacenter_id: undefined,
|
||||
floor_number: 1,
|
||||
status: 'planning',
|
||||
area: undefined,
|
||||
height: undefined,
|
||||
load_bearing: undefined,
|
||||
@@ -301,6 +315,7 @@ const handleOk = async () => {
|
||||
name: form.value.name,
|
||||
datacenter_id: form.value.datacenter_id,
|
||||
floor_number: form.value.floor_number,
|
||||
status: form.value.status,
|
||||
area: form.value.area,
|
||||
height: form.value.height,
|
||||
load_bearing: form.value.load_bearing,
|
||||
|
||||
@@ -74,7 +74,9 @@
|
||||
v-model="form.floor_id"
|
||||
placeholder="请选择所属楼层"
|
||||
:loading="loadingFloors"
|
||||
:disabled="!form.datacenter_id"
|
||||
allow-search
|
||||
@change="handleFloorChange"
|
||||
@search="handleFloorSearch"
|
||||
>
|
||||
<a-option
|
||||
@@ -88,6 +90,32 @@
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</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>
|
||||
@@ -438,9 +466,10 @@ import { Message } from '@arco-design/web-vue'
|
||||
import { createRack, updateRack } from '@/api/ops/rack'
|
||||
import {
|
||||
fetchDatacenterList,
|
||||
fetchRackListByDatacenter,
|
||||
fetchSupplierList,
|
||||
} from '@/api/ops/rack'
|
||||
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||
import { fetchRoomListByFloor } from '@/api/ops/room'
|
||||
|
||||
interface Rack {
|
||||
id?: number
|
||||
@@ -448,6 +477,7 @@ interface Rack {
|
||||
code?: string
|
||||
datacenter_id?: number
|
||||
floor_id?: number
|
||||
room_id?: number
|
||||
height?: number
|
||||
width?: number
|
||||
depth?: number
|
||||
@@ -496,12 +526,15 @@ const emit = defineEmits<Emits>()
|
||||
const formRef = ref()
|
||||
const loadingDatacenters = ref(false)
|
||||
const loadingFloors = ref(false)
|
||||
const loadingRooms = ref(false)
|
||||
const loadingSuppliers = ref(false)
|
||||
const submitting = ref(false)
|
||||
const datacenterList = ref<any[]>([])
|
||||
const floorList = ref<any[]>([])
|
||||
const roomList = ref<any[]>([])
|
||||
const supplierList = ref<any[]>([])
|
||||
let floorSearchTimer: number | undefined
|
||||
let roomSearchTimer: number | undefined
|
||||
|
||||
// 表单数据
|
||||
const form = ref({
|
||||
@@ -509,6 +542,7 @@ const form = ref({
|
||||
code: '',
|
||||
datacenter_id: undefined as number | undefined,
|
||||
floor_id: undefined as number | undefined,
|
||||
room_id: undefined as number | undefined,
|
||||
height: 42,
|
||||
width: undefined as number | undefined,
|
||||
depth: undefined as number | undefined,
|
||||
@@ -559,7 +593,7 @@ const loadDatacenterList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载楼层列表(通过机柜下拉接口提取去重楼层)
|
||||
// 加载楼层列表
|
||||
const loadFloorList = async (datacenterId?: number, keyword?: string) => {
|
||||
if (!datacenterId) {
|
||||
floorList.value = []
|
||||
@@ -567,20 +601,10 @@ const loadFloorList = async (datacenterId?: number, keyword?: string) => {
|
||||
}
|
||||
loadingFloors.value = true
|
||||
try {
|
||||
const res: any = await fetchRackListByDatacenter(datacenterId, { name: keyword })
|
||||
const res: any = await fetchFloorListByDatacenter(datacenterId, { name: keyword })
|
||||
if (res.code === 0) {
|
||||
const list = res.details?.data ?? res.data ?? res.details ?? []
|
||||
const rows = Array.isArray(list) ? list : []
|
||||
const floorMap = new Map<number, { id: number; name: string }>()
|
||||
rows.forEach((rack: any) => {
|
||||
const floor = rack?.floor
|
||||
if (!floor?.id || floorMap.has(floor.id)) return
|
||||
floorMap.set(floor.id, {
|
||||
id: floor.id,
|
||||
name: floor.name || String(floor.id),
|
||||
})
|
||||
})
|
||||
floorList.value = Array.from(floorMap.values())
|
||||
floorList.value = Array.isArray(list) ? list : []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取楼层列表失败:', error)
|
||||
@@ -607,9 +631,36 @@ const loadSupplierList = async () => {
|
||||
// 数据中心变化时重新加载楼层列表
|
||||
const handleDatacenterChange = async (value: number) => {
|
||||
form.value.floor_id = undefined
|
||||
form.value.room_id = undefined
|
||||
roomList.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) => {
|
||||
if (!form.value.datacenter_id) return
|
||||
if (floorSearchTimer) {
|
||||
@@ -620,6 +671,16 @@ const handleFloorSearch = (keyword: string) => {
|
||||
}, 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(
|
||||
() => props.visible,
|
||||
@@ -632,6 +693,7 @@ watch(
|
||||
code: props.rack.code || '',
|
||||
datacenter_id: props.rack.datacenter_id,
|
||||
floor_id: props.rack.floor_id,
|
||||
room_id: props.rack.room_id,
|
||||
height: props.rack.height || 42,
|
||||
width: props.rack.width,
|
||||
depth: props.rack.depth,
|
||||
@@ -667,6 +729,9 @@ watch(
|
||||
if (props.rack.datacenter_id) {
|
||||
loadFloorList(props.rack.datacenter_id)
|
||||
}
|
||||
if (props.rack.floor_id) {
|
||||
loadRoomList(props.rack.floor_id)
|
||||
}
|
||||
} else {
|
||||
// 新建模式:重置表单
|
||||
form.value = {
|
||||
@@ -674,6 +739,7 @@ watch(
|
||||
code: '',
|
||||
datacenter_id: undefined,
|
||||
floor_id: undefined,
|
||||
room_id: undefined,
|
||||
height: 42,
|
||||
width: undefined,
|
||||
depth: undefined,
|
||||
@@ -705,6 +771,8 @@ watch(
|
||||
description: '',
|
||||
remarks: '',
|
||||
}
|
||||
floorList.value = []
|
||||
roomList.value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -722,6 +790,7 @@ const handleOk = async () => {
|
||||
code: form.value.code,
|
||||
datacenter_id: form.value.datacenter_id,
|
||||
floor_id: form.value.floor_id,
|
||||
room_id: form.value.room_id,
|
||||
height: form.value.height,
|
||||
width: form.value.width,
|
||||
depth: form.value.depth,
|
||||
|
||||
@@ -24,6 +24,14 @@ export const searchFormConfig: FormItem[] = [
|
||||
options: [], // 需要动态加载
|
||||
span: 6,
|
||||
},
|
||||
{
|
||||
field: 'room_id',
|
||||
label: '机房',
|
||||
type: 'select',
|
||||
placeholder: '请选择机房',
|
||||
options: [], // 需要动态加载
|
||||
span: 6,
|
||||
},
|
||||
{
|
||||
field: 'rack_type',
|
||||
label: '机柜类型',
|
||||
|
||||
@@ -113,9 +113,10 @@ import { columns as columnsConfig } from './config/columns'
|
||||
import {
|
||||
fetchRackList,
|
||||
fetchDatacenterList,
|
||||
fetchRackListByDatacenter,
|
||||
deleteRack,
|
||||
} from '@/api/ops/rack'
|
||||
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||
import { fetchRoomListByFloor } from '@/api/ops/room'
|
||||
import RackDetailDialog from './components/RackDetailDialog.vue'
|
||||
import RackFormDialog from './components/RackFormDialog.vue'
|
||||
|
||||
@@ -128,6 +129,7 @@ const formModel = ref({
|
||||
keyword: '',
|
||||
datacenter_id: undefined,
|
||||
floor_id: undefined,
|
||||
room_id: undefined,
|
||||
rack_type: undefined,
|
||||
status: undefined,
|
||||
})
|
||||
@@ -141,8 +143,10 @@ const pagination = reactive({
|
||||
// 表单项配置
|
||||
const datacenterSelectOptions = ref<{ label: string; value: number }[]>([])
|
||||
const floorSelectOptions = ref<{ label: string; value: number }[]>([])
|
||||
const roomSelectOptions = ref<{ label: string; value: number }[]>([])
|
||||
let datacenterSearchTimer: number | undefined
|
||||
let floorSearchTimer: number | undefined
|
||||
let roomSearchTimer: number | undefined
|
||||
|
||||
const formItems = computed<FormItem[]>(() =>
|
||||
searchFormConfig.map((item) => {
|
||||
@@ -163,6 +167,15 @@ const formItems = computed<FormItem[]>(() =>
|
||||
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
|
||||
}),
|
||||
)
|
||||
@@ -199,25 +212,18 @@ const loadDatacenterOptions = async (keyword?: string) => {
|
||||
const loadFloorOptions = async (datacenterId?: number, keyword?: string) => {
|
||||
if (!datacenterId) {
|
||||
floorSelectOptions.value = []
|
||||
roomSelectOptions.value = []
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res: any = await fetchRackListByDatacenter(datacenterId, {
|
||||
name: keyword,
|
||||
})
|
||||
const res: any = await fetchFloorListByDatacenter(datacenterId, { name: keyword })
|
||||
if (res.code === 0) {
|
||||
const list = res.details?.data ?? res.data ?? res.details ?? []
|
||||
const rows = Array.isArray(list) ? list : []
|
||||
const floorMap = new Map<number, { label: string; value: number }>()
|
||||
rows.forEach((rack: any) => {
|
||||
const floor = rack?.floor
|
||||
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())
|
||||
floorSelectOptions.value = rows.map((floor: any) => ({
|
||||
label: floor.name || String(floor.id),
|
||||
value: floor.id,
|
||||
}))
|
||||
}
|
||||
} catch (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) => {
|
||||
if (datacenterSearchTimer) {
|
||||
window.clearTimeout(datacenterSearchTimer)
|
||||
@@ -245,16 +273,37 @@ const handleFloorSearch = (keyword: string) => {
|
||||
}, 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(
|
||||
() => formModel.value.datacenter_id,
|
||||
(newId, oldId) => {
|
||||
if (newId !== oldId) {
|
||||
formModel.value.floor_id = undefined
|
||||
formModel.value.room_id = undefined
|
||||
}
|
||||
loadFloorOptions(newId)
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => formModel.value.floor_id,
|
||||
(newId, oldId) => {
|
||||
if (newId !== oldId) {
|
||||
formModel.value.room_id = undefined
|
||||
}
|
||||
loadRoomOptions(newId)
|
||||
},
|
||||
)
|
||||
|
||||
// 获取机柜类型颜色
|
||||
const getRackTypeColor = (type?: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
@@ -314,6 +363,7 @@ const fetchRacks = async () => {
|
||||
keyword: formModel.value.keyword || undefined,
|
||||
datacenter_id: formModel.value.datacenter_id ?? undefined,
|
||||
floor_id: formModel.value.floor_id ?? undefined,
|
||||
room_id: formModel.value.room_id ?? undefined,
|
||||
rack_type: formModel.value.rack_type || undefined,
|
||||
status: formModel.value.status || undefined,
|
||||
}
|
||||
@@ -350,6 +400,7 @@ const handleReset = () => {
|
||||
keyword: '',
|
||||
datacenter_id: undefined,
|
||||
floor_id: undefined,
|
||||
room_id: undefined,
|
||||
rack_type: undefined,
|
||||
status: undefined,
|
||||
}
|
||||
@@ -411,7 +462,7 @@ const handleDelete = async (record: any) => {
|
||||
// U位管理
|
||||
const handleUnitManagement = (record: any) => {
|
||||
router.push({
|
||||
path: '/ops/datacenter/u-position',
|
||||
path: '/datacenter/u-position',
|
||||
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 { Message, Modal } from '@arco-design/web-vue'
|
||||
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 { fetchDatacenterList, fetchRackListByRoom } from '@/api/ops/rack'
|
||||
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||
@@ -210,6 +211,7 @@ let datacenterSearchTimer: number | undefined
|
||||
let floorSearchTimer: number | undefined
|
||||
let roomSearchTimer: number | undefined
|
||||
let rackSearchTimer: number | undefined
|
||||
const route = useRoute()
|
||||
|
||||
// 对话框可见性
|
||||
const allocateVisible = ref(false)
|
||||
@@ -613,6 +615,11 @@ const handleCancelReservation = async (record: any) => {
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
fetchDatacenters()
|
||||
const rackId = Number(route.query.rack_id)
|
||||
if (Number.isFinite(rackId) && rackId > 0) {
|
||||
selectedRackId.value = rackId
|
||||
fetchUnits(rackId)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -75,6 +75,23 @@
|
||||
<a-input v-model="formData.location" placeholder="机房A-机架01" />
|
||||
</a-form-item>
|
||||
</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-form-item field="tags" label="标签">
|
||||
<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 { createServer, updateServer } from '@/api/ops/server'
|
||||
import type { ServerFormData, ServerItem } from '@/api/ops/server'
|
||||
import { fetchAssetAll } from '@/api/ops/asset'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
@@ -161,6 +179,9 @@ const emit = defineEmits(['update:visible', 'success'])
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
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)
|
||||
|
||||
@@ -176,6 +197,7 @@ const formData = reactive<ServerFormData>({
|
||||
server_type: '',
|
||||
tags: '',
|
||||
location: '',
|
||||
asset_id: undefined,
|
||||
remote_access: '',
|
||||
remote_port: 0,
|
||||
agent_config: '',
|
||||
@@ -280,6 +302,7 @@ watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
loadAssetOptions()
|
||||
if (isEdit.value && props.record) {
|
||||
Object.assign(formData, {
|
||||
server_identity: props.record.server_identity || '',
|
||||
@@ -293,6 +316,7 @@ watch(
|
||||
server_type: props.record.server_type || '',
|
||||
tags: props.record.tags || '',
|
||||
location: props.record.location || '',
|
||||
asset_id: props.record.asset_id,
|
||||
remote_access: props.record.remote_access || '',
|
||||
remote_port: props.record.remote_port || 0,
|
||||
agent_config: props.record.agent_config || '',
|
||||
@@ -316,6 +340,7 @@ watch(
|
||||
server_type: '',
|
||||
tags: '',
|
||||
location: '',
|
||||
asset_id: undefined,
|
||||
remote_access: '',
|
||||
remote_port: 0,
|
||||
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(
|
||||
() => formData.host,
|
||||
() => {
|
||||
@@ -362,6 +420,7 @@ const handleOk = async () => {
|
||||
server_type: formData.server_type,
|
||||
tags: formData.tags,
|
||||
location: formData.location,
|
||||
asset_id: formData.asset_id ?? (isEdit.value ? 0 : undefined),
|
||||
remote_access: formData.remote_access,
|
||||
remote_port: formData.remote_port,
|
||||
agent_config: formData.agent_config,
|
||||
|
||||
@@ -289,8 +289,6 @@ const subStatusItems = computed(() => {
|
||||
{ key: 'cpu_status', label: 'CPU' },
|
||||
{ key: 'memory_status', label: '内存' },
|
||||
{ key: 'disk_status', label: '磁盘' },
|
||||
{ key: 'network_status', label: '网络' },
|
||||
{ key: 'raid_status', label: 'RAID' },
|
||||
]
|
||||
return fields.map((f) => ({
|
||||
key: String(f.key),
|
||||
|
||||
Reference in New Issue
Block a user