fix: 机柜图形化
This commit is contained in:
@@ -42,13 +42,6 @@ export interface AssetListParams {
|
|||||||
page_size?: number
|
page_size?: number
|
||||||
keyword?: string
|
keyword?: string
|
||||||
status?: string
|
status?: string
|
||||||
category_id?: number
|
|
||||||
supplier_id?: number
|
|
||||||
datacenter_id?: number
|
|
||||||
floor_id?: number
|
|
||||||
room_id?: number
|
|
||||||
rack_id?: number
|
|
||||||
department?: string
|
|
||||||
sort?: string
|
sort?: string
|
||||||
order?: string
|
order?: string
|
||||||
}
|
}
|
||||||
@@ -66,7 +59,7 @@ export interface AssetForm {
|
|||||||
purchase_date?: string
|
purchase_date?: string
|
||||||
original_value?: number
|
original_value?: number
|
||||||
supplier_id?: number
|
supplier_id?: number
|
||||||
warranty_period?: string
|
warranty_period?: number
|
||||||
warranty_expiry?: string
|
warranty_expiry?: string
|
||||||
department?: string
|
department?: string
|
||||||
user?: string
|
user?: string
|
||||||
@@ -75,9 +68,6 @@ export interface AssetForm {
|
|||||||
datacenter_id?: number
|
datacenter_id?: number
|
||||||
floor_id?: number
|
floor_id?: number
|
||||||
room_id?: number
|
room_id?: number
|
||||||
rack_id?: number
|
|
||||||
unit_start?: number
|
|
||||||
unit_end?: number
|
|
||||||
qr_code?: string
|
qr_code?: string
|
||||||
rfid_tag?: string
|
rfid_tag?: string
|
||||||
asset_tag?: string
|
asset_tag?: string
|
||||||
@@ -138,21 +128,6 @@ export const fetchSupplierOptions = () => {
|
|||||||
return request.get('/Assets/v1/supplier/all')
|
return request.get('/Assets/v1/supplier/all')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取数据中心列表(下拉) */
|
|
||||||
export const fetchDatacenterOptions = () => {
|
|
||||||
return request.get('/Assets/v1/datacenter/list')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 根据数据中心获取楼层列表 */
|
|
||||||
export const fetchFloorOptions = (datacenterId: number) => {
|
|
||||||
return request.get(`/Assets/v1/datacenter/${datacenterId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 获取机柜列表(下拉) */
|
|
||||||
export const fetchRackOptions = (params?: { datacenter_id?: number; floor_id?: number }) => {
|
|
||||||
return request.post('/Assets/v1/rack/list', params || {})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 绑定资产和监控资源 */
|
/** 绑定资产和监控资源 */
|
||||||
export const linkAssetResource = (data: {
|
export const linkAssetResource = (data: {
|
||||||
asset_id: number
|
asset_id: number
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { request } from '@/api/request'
|
|||||||
export interface Floor {
|
export interface Floor {
|
||||||
id?: number
|
id?: number
|
||||||
name?: string
|
name?: string
|
||||||
|
code?: string
|
||||||
datacenter_id?: number
|
datacenter_id?: number
|
||||||
floor_number?: number
|
floor_number?: number
|
||||||
status?: string
|
status?: string
|
||||||
@@ -54,7 +55,6 @@ export const deleteFloor = (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 获取数据中心列表(用于下拉选择) */
|
/** 获取数据中心列表(用于下拉选择) */
|
||||||
export const fetchDatacenterList = (params?: { keyword?: string; name?: string }) => {
|
export const fetchDatacenterList = (params?: { keyword?: string }) => {
|
||||||
const normalizedParams = params?.keyword ? { ...params, name: params.name ?? params.keyword } : params
|
return request.get('/Assets/v1/datacenter/all', { params })
|
||||||
return request.get('/Assets/v1/datacenter/all', { params: normalizedParams })
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,14 @@ export const fetchRackListByDatacenter = (datacenterId: number, params?: { name?
|
|||||||
return request.get(`/Assets/v1/rack/datacenter/${datacenterId}`, { params })
|
return request.get(`/Assets/v1/rack/datacenter/${datacenterId}`, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 根据楼层获取机柜列表(下拉,不分页)- 已废弃,请使用 fetchRackListByRoom */
|
/** 根据楼层获取机柜列表 */
|
||||||
export const fetchRackListByFloor = (floorId: number, params?: { name?: string }) => {
|
export const fetchRackListByFloor = (floorId: number, params?: { keyword?: string }) => {
|
||||||
return request.get(`/Assets/v1/rack/floor/${floorId}`, { params })
|
return fetchRackList({
|
||||||
|
page: 1,
|
||||||
|
page_size: 100,
|
||||||
|
floor_id: floorId,
|
||||||
|
keyword: params?.keyword,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 根据机房获取机柜列表(下拉,不分页) */
|
/** 根据机房获取机柜列表(下拉,不分页) */
|
||||||
@@ -62,13 +67,6 @@ export const fetchSupplierList = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 获取数据中心列表(用于下拉选择) */
|
/** 获取数据中心列表(用于下拉选择) */
|
||||||
export const fetchDatacenterList = (params?: { keyword?: string; name?: string }) => {
|
export const fetchDatacenterList = (params?: { keyword?: string }) => {
|
||||||
const normalizedParams = params?.keyword ? { ...params, name: params.name ?? params.keyword } : params
|
return request.get('/Assets/v1/datacenter/all', { params })
|
||||||
return request.get('/Assets/v1/datacenter/all', { params: normalizedParams })
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 获取楼层列表(用于下拉选择) */
|
|
||||||
export const fetchFloorListForSelect = (params?: { datacenter_id?: number; keyword?: string; name?: string }) => {
|
|
||||||
const normalizedParams = params?.keyword ? { ...params, name: params.name ?? params.keyword } : params
|
|
||||||
return request.get('/Assets/v1/floor/all', { params: normalizedParams })
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ export interface RoomItem {
|
|||||||
description?: string
|
description?: string
|
||||||
floor_id: number
|
floor_id: number
|
||||||
datacenter_id: number
|
datacenter_id: number
|
||||||
status?: string
|
layout_version: number
|
||||||
|
status: string
|
||||||
has3D: boolean
|
has3D: boolean
|
||||||
floor?: { id: number; name: string }
|
floor?: { id: number; name: string }
|
||||||
datacenter?: { id: number; name: string }
|
datacenter?: { id: number; name: string }
|
||||||
@@ -26,6 +27,13 @@ export interface RoomListResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 机房详情响应 */
|
||||||
|
export interface RoomDetailResponse {
|
||||||
|
code: number
|
||||||
|
message?: string
|
||||||
|
details: RoomItem
|
||||||
|
}
|
||||||
|
|
||||||
/** 机房列表查询参数 */
|
/** 机房列表查询参数 */
|
||||||
export interface RoomListParams {
|
export interface RoomListParams {
|
||||||
page?: number
|
page?: number
|
||||||
@@ -54,6 +62,12 @@ export interface RoomOptionItem {
|
|||||||
sort: number
|
sort: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
code: number
|
||||||
|
message?: string
|
||||||
|
details: T
|
||||||
|
}
|
||||||
|
|
||||||
/** 机房创建数据 */
|
/** 机房创建数据 */
|
||||||
export interface RoomCreateData {
|
export interface RoomCreateData {
|
||||||
name: string
|
name: string
|
||||||
@@ -61,8 +75,7 @@ export interface RoomCreateData {
|
|||||||
description?: string
|
description?: string
|
||||||
floor_id: number
|
floor_id: number
|
||||||
datacenter_id: number
|
datacenter_id: number
|
||||||
status?: string
|
status: string
|
||||||
has3D: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 机房更新数据 */
|
/** 机房更新数据 */
|
||||||
@@ -74,7 +87,6 @@ export interface RoomUpdateData {
|
|||||||
floor_id?: number
|
floor_id?: number
|
||||||
datacenter_id?: number
|
datacenter_id?: number
|
||||||
status?: string
|
status?: string
|
||||||
has3D?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取机房列表(分页) */
|
/** 获取机房列表(分页) */
|
||||||
@@ -84,17 +96,17 @@ export const fetchRoomList = (data?: RoomListParams) => {
|
|||||||
|
|
||||||
/** 获取机房详情 */
|
/** 获取机房详情 */
|
||||||
export const fetchRoomDetail = (id: number) => {
|
export const fetchRoomDetail = (id: number) => {
|
||||||
return request.get<RoomItem>(`/Assets/v1/room/detail/${id}`)
|
return request.get<RoomDetailResponse>(`/Assets/v1/room/detail/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取机房列表(下拉,不分页) */
|
/** 获取机房列表(下拉,不分页) */
|
||||||
export const fetchRoomOptions = (params?: RoomOptionsParams) => {
|
export const fetchRoomOptions = (params?: RoomOptionsParams) => {
|
||||||
return request.get<RoomOptionItem[]>('/Assets/v1/room/all', { params })
|
return request.get<ApiResponse<RoomOptionItem[]>>('/Assets/v1/room/all', { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 根据楼层获取机房列表(下拉,不分页) */
|
/** 根据楼层获取机房列表(下拉,不分页) */
|
||||||
export const fetchRoomListByFloor = (floorId: number, params?: { name?: string }) => {
|
export const fetchRoomListByFloor = (floorId: number, params?: { name?: string }) => {
|
||||||
return request.get<RoomItem[]>(`/Assets/v1/room/floor/${floorId}`, { params })
|
return request.get<ApiResponse<RoomItem[]>>(`/Assets/v1/room/floor/${floorId}`, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建机房 */
|
/** 创建机房 */
|
||||||
|
|||||||
@@ -1,31 +1,100 @@
|
|||||||
import { request } from '@/api/request'
|
import { request } from '@/api/request'
|
||||||
|
|
||||||
|
/** U 位状态 */
|
||||||
|
export type RackUnitStatus = 'available' | 'occupied' | 'reserved' | 'disabled'
|
||||||
|
|
||||||
|
/** U 位关联资产摘要 */
|
||||||
|
export interface RackUnitAssetSummary {
|
||||||
|
unit_start: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** U 位公共字段 */
|
||||||
|
export interface RackUnitBase {
|
||||||
|
unit_number: number
|
||||||
|
occupied_units: number
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 可用 U 位 */
|
||||||
|
export interface AvailableRackUnit extends RackUnitBase {
|
||||||
|
status: 'available'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 已占用 U 位 */
|
||||||
|
export interface OccupiedRackUnit extends RackUnitBase {
|
||||||
|
status: 'occupied'
|
||||||
|
asset_id: number
|
||||||
|
asset_name: string
|
||||||
|
asset_code: string
|
||||||
|
asset_type: string
|
||||||
|
power_consumption: number
|
||||||
|
asset: RackUnitAssetSummary
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 已预留 U 位 */
|
||||||
|
export interface ReservedRackUnit extends RackUnitBase {
|
||||||
|
status: 'reserved'
|
||||||
|
reserved_for: string
|
||||||
|
reserved_until: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 已禁用 U 位 */
|
||||||
|
export interface DisabledRackUnit extends RackUnitBase {
|
||||||
|
status: 'disabled'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 单个 U 位数据 */
|
||||||
|
export type RackUnitItem = AvailableRackUnit | OccupiedRackUnit | ReservedRackUnit | DisabledRackUnit
|
||||||
|
|
||||||
|
/** U 位页面使用的机柜摘要 */
|
||||||
|
export interface RackUnitRackInfo {
|
||||||
|
id?: number
|
||||||
|
room_id: number
|
||||||
|
height: number
|
||||||
|
status?: string
|
||||||
|
used_power?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取机柜 U 位状态响应 */
|
||||||
|
export interface RackUnitListResponse {
|
||||||
|
code: number
|
||||||
|
message?: string
|
||||||
|
details: {
|
||||||
|
rack: RackUnitRackInfo
|
||||||
|
units: RackUnitItem[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 获取机柜U位状态 */
|
/** 获取机柜U位状态 */
|
||||||
export const fetchRackUnits = (rackId: number) => {
|
export const fetchRackUnits = (rackId: number) => {
|
||||||
return request.get(`/Assets/v1/unit/rack/${rackId}`)
|
return request.get<RackUnitListResponse>(`/Assets/v1/unit/rack/${rackId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取U位列表(别名) */
|
/** 获取U位列表(别名) */
|
||||||
export const fetchUnitList = fetchRackUnits
|
export const fetchUnitList = fetchRackUnits
|
||||||
|
|
||||||
/** 分配U位 */
|
/** 机柜设备上架数据 */
|
||||||
export const allocateUnit = (data: {
|
export interface RackPlacementData {
|
||||||
|
expected_version: number
|
||||||
rack_id: number
|
rack_id: number
|
||||||
start_unit: number
|
start_unit: number
|
||||||
occupied_units: number
|
occupied_units: number
|
||||||
asset_id?: number
|
power_consumption: number
|
||||||
asset_code?: string
|
|
||||||
asset_name: string
|
|
||||||
asset_type: string
|
|
||||||
power_consumption?: number
|
|
||||||
description?: string
|
|
||||||
}) => {
|
|
||||||
return request.post('/Assets/v1/unit/allocate', data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 释放U位 */
|
/** 将资产上架到机柜U位 */
|
||||||
export const releaseUnit = (data: { rack_id: number; start_unit: number; end_unit: number }) => {
|
export const placeAssetInRack = (roomId: number, assetId: number, data: RackPlacementData) => {
|
||||||
return request.post('/Assets/v1/unit/release', data)
|
return request.put(`/Assets/v1/three-d/rooms/${roomId}/devices/${assetId}/placement`, {
|
||||||
|
...data,
|
||||||
|
placement_type: 'rack',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取消资产在机房中的放置 */
|
||||||
|
export const unplaceAsset = (roomId: number, assetId: number, expectedVersion: number) => {
|
||||||
|
return request.delete(`/Assets/v1/three-d/rooms/${roomId}/devices/${assetId}/placement`, {
|
||||||
|
data: { expected_version: expectedVersion },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 预留U位 */
|
/** 预留U位 */
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<a-form-item label="质保期" field="warranty_period">
|
<a-form-item label="质保期" field="warranty_period">
|
||||||
<a-input v-model="form.warranty_period" placeholder="请输入质保期(如:36个月)" :max-length="50" allow-clear />
|
<a-input-number v-model="form.warranty_period" placeholder="请输入质保月数" :min="0" :precision="0" style="width: 100%" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
@@ -146,8 +146,9 @@
|
|||||||
|
|
||||||
<!-- 位置信息 -->
|
<!-- 位置信息 -->
|
||||||
<a-card class="info-card" title="位置信息">
|
<a-card class="info-card" title="位置信息">
|
||||||
|
<a-alert v-if="locationLocked" type="info" style="margin-bottom: 16px">已放置资产的位置由 U 位或 3D 机房页面维护。</a-alert>
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="5">
|
<a-col :span="8">
|
||||||
<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"
|
||||||
@@ -156,6 +157,7 @@
|
|||||||
allow-search
|
allow-search
|
||||||
:loading="datacenterLoading"
|
:loading="datacenterLoading"
|
||||||
:filter-option="false"
|
:filter-option="false"
|
||||||
|
:disabled="locationLocked"
|
||||||
@search="handleDatacenterSearch"
|
@search="handleDatacenterSearch"
|
||||||
@change="handleDatacenterChange"
|
@change="handleDatacenterChange"
|
||||||
>
|
>
|
||||||
@@ -163,7 +165,7 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="5">
|
<a-col :span="8">
|
||||||
<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"
|
||||||
@@ -171,7 +173,7 @@
|
|||||||
allow-clear
|
allow-clear
|
||||||
allow-search
|
allow-search
|
||||||
:loading="floorLoading"
|
:loading="floorLoading"
|
||||||
:disabled="!form.datacenter_id"
|
:disabled="locationLocked || !form.datacenter_id"
|
||||||
:filter-option="false"
|
:filter-option="false"
|
||||||
@search="handleFloorSearch"
|
@search="handleFloorSearch"
|
||||||
@change="handleFloorChange"
|
@change="handleFloorChange"
|
||||||
@@ -180,7 +182,7 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="5">
|
<a-col :span="8">
|
||||||
<a-form-item label="所属机房" field="room_id">
|
<a-form-item label="所属机房" field="room_id">
|
||||||
<a-select
|
<a-select
|
||||||
v-model="form.room_id"
|
v-model="form.room_id"
|
||||||
@@ -188,41 +190,14 @@
|
|||||||
allow-clear
|
allow-clear
|
||||||
allow-search
|
allow-search
|
||||||
:loading="roomLoading"
|
:loading="roomLoading"
|
||||||
:disabled="!form.floor_id"
|
:disabled="locationLocked || !form.floor_id"
|
||||||
:filter-option="false"
|
:filter-option="false"
|
||||||
@search="handleRoomSearch"
|
@search="handleRoomSearch"
|
||||||
@change="handleRoomChange"
|
|
||||||
>
|
>
|
||||||
<a-option v-for="item in roomOptions" :key="item.id" :value="item.id">{{ item.name }} ({{ item.code }})</a-option>
|
<a-option v-for="item in roomOptions" :key="item.id" :value="item.id">{{ item.name }} ({{ item.code }})</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="5">
|
|
||||||
<a-form-item label="所属机柜" field="rack_id">
|
|
||||||
<a-select
|
|
||||||
v-model="form.rack_id"
|
|
||||||
placeholder="请选择机柜"
|
|
||||||
allow-clear
|
|
||||||
allow-search
|
|
||||||
:loading="rackLoading"
|
|
||||||
:disabled="!form.room_id"
|
|
||||||
:filter-option="false"
|
|
||||||
@search="handleRackSearch"
|
|
||||||
>
|
|
||||||
<a-option v-for="item in rackOptions" :key="item.id" :value="item.id">{{ item.name }} ({{ item.code }})</a-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="2">
|
|
||||||
<a-form-item label="起始U位" field="unit_start">
|
|
||||||
<a-input-number v-model="form.unit_start" placeholder="起始U位" :min="1" :max="48" style="width: 100%" />
|
|
||||||
</a-form-item>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="2">
|
|
||||||
<a-form-item label="结束U位" field="unit_end">
|
|
||||||
<a-input-number v-model="form.unit_end" placeholder="结束U位" :min="1" :max="48" style="width: 100%" />
|
|
||||||
</a-form-item>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
@@ -282,13 +257,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { computed, ref, onMounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { Message } from '@arco-design/web-vue'
|
import { Message } from '@arco-design/web-vue'
|
||||||
import type { FormInstance } from '@arco-design/web-vue/es/form'
|
import type { FormInstance } from '@arco-design/web-vue/es/form'
|
||||||
import { createAsset, updateAsset, fetchAssetDetail, fetchCategoryOptions, assetStatusOptions, AssetForm } from '@/api/ops/asset'
|
import { createAsset, updateAsset, fetchAssetDetail, fetchCategoryOptions, assetStatusOptions, AssetForm } from '@/api/ops/asset'
|
||||||
import { fetchAllSuppliers } from '@/api/ops/supplier'
|
import { fetchAllSuppliers } from '@/api/ops/supplier'
|
||||||
import { fetchDatacenterList, fetchRackListByRoom } from '@/api/ops/rack'
|
import { fetchDatacenterList } from '@/api/ops/rack'
|
||||||
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
import { fetchFloorListByDatacenter } from '@/api/ops/floor'
|
||||||
import { fetchRoomListByFloor } from '@/api/ops/room'
|
import { fetchRoomListByFloor } from '@/api/ops/room'
|
||||||
|
|
||||||
@@ -297,6 +272,7 @@ const route = useRoute()
|
|||||||
|
|
||||||
// 页面状态
|
// 页面状态
|
||||||
const isEdit = ref(false)
|
const isEdit = ref(false)
|
||||||
|
const placementType = ref('unplaced')
|
||||||
const deviceId = ref<number | null>(null)
|
const deviceId = ref<number | null>(null)
|
||||||
const pageTitle = ref('新增设备')
|
const pageTitle = ref('新增设备')
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -309,7 +285,6 @@ 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 roomOptions = ref<any[]>([])
|
||||||
const rackOptions = ref<any[]>([])
|
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
const categoryLoading = ref(false)
|
const categoryLoading = ref(false)
|
||||||
@@ -317,57 +292,50 @@ 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 roomLoading = 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 roomSearchTimer: number | undefined
|
||||||
let rackSearchTimer: number | undefined
|
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const form = ref<AssetForm>({
|
const createAssetForm = (data: any = {}): AssetForm => ({
|
||||||
asset_name: '',
|
asset_name: data.asset_name || '',
|
||||||
asset_code: '',
|
asset_code: data.asset_code || '',
|
||||||
category_id: undefined,
|
category_id: data.category_id,
|
||||||
model: '',
|
model: data.model || '',
|
||||||
manufacturer: '',
|
manufacturer: data.manufacturer || '',
|
||||||
serial_number: '',
|
serial_number: data.serial_number || '',
|
||||||
source_address: '',
|
source_address: data.source_address || '',
|
||||||
purchase_date: '',
|
purchase_date: data.purchase_date || '',
|
||||||
original_value: undefined,
|
original_value: data.original_value,
|
||||||
supplier_id: undefined,
|
supplier_id: data.supplier_id,
|
||||||
warranty_period: '',
|
warranty_period: data.warranty_period,
|
||||||
warranty_expiry: '',
|
warranty_expiry: data.warranty_expiry || '',
|
||||||
department: '',
|
department: data.department || '',
|
||||||
user: '',
|
user: data.user || '',
|
||||||
status: 'in_use',
|
status: data.status || 'in_use',
|
||||||
location: '',
|
location: data.location || '',
|
||||||
datacenter_id: undefined,
|
datacenter_id: data.datacenter_id,
|
||||||
floor_id: undefined,
|
floor_id: data.floor_id,
|
||||||
rack_id: undefined,
|
room_id: data.room_id,
|
||||||
unit_start: undefined,
|
qr_code: data.qr_code || '',
|
||||||
unit_end: undefined,
|
rfid_tag: data.rfid_tag || '',
|
||||||
qr_code: '',
|
asset_tag: data.asset_tag || '',
|
||||||
rfid_tag: '',
|
specifications: data.specifications || '',
|
||||||
asset_tag: '',
|
description: data.description || '',
|
||||||
specifications: '',
|
remarks: data.remarks || '',
|
||||||
description: '',
|
|
||||||
remarks: '',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const form = ref<AssetForm>(createAssetForm())
|
||||||
|
const locationLocked = computed(() => isEdit.value && placementType.value !== 'unplaced')
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const rules = {
|
const rules = {
|
||||||
asset_name: [{ required: true, message: '请输入资产名称' }],
|
asset_name: [{ required: true, message: '请输入资产名称' }],
|
||||||
asset_code: [{ required: true, message: '请输入资产编号' }],
|
asset_code: [{ required: true, message: '请输入资产编号' }],
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取列表数据的辅助函数
|
|
||||||
const extractList = (res: any): any[] => {
|
|
||||||
const candidate = res?.data?.data ?? res?.details?.data ?? res?.data ?? res?.details ?? []
|
|
||||||
return Array.isArray(candidate) ? candidate : []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载下拉选项
|
// 加载下拉选项
|
||||||
const loadOptions = async () => {
|
const loadOptions = async () => {
|
||||||
// 加载资产分类
|
// 加载资产分类
|
||||||
@@ -375,7 +343,7 @@ const loadOptions = async () => {
|
|||||||
try {
|
try {
|
||||||
const res = await fetchCategoryOptions()
|
const res = await fetchCategoryOptions()
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
categoryOptions.value = res.details || []
|
categoryOptions.value = res.details
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载资产分类失败:', error)
|
console.error('加载资产分类失败:', error)
|
||||||
@@ -393,7 +361,7 @@ const loadSupplierOptions = async (keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res = await fetchAllSuppliers(keyword, 'active', keyword ? undefined : 20)
|
const res = await fetchAllSuppliers(keyword, 'active', keyword ? undefined : 20)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
supplierOptions.value = res.details?.data || res.details || []
|
supplierOptions.value = res.details
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载供应商失败:', error)
|
console.error('加载供应商失败:', error)
|
||||||
@@ -419,7 +387,7 @@ const fetchDatacenters = async (keyword?: string) => {
|
|||||||
datacenterLoading.value = true
|
datacenterLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res: any = await fetchDatacenterList({ keyword })
|
const res: any = await fetchDatacenterList({ keyword })
|
||||||
const rows = extractList(res)
|
const rows = res.details
|
||||||
datacenterOptions.value = rows.map((d: any) => ({
|
datacenterOptions.value = rows.map((d: any) => ({
|
||||||
label: d.name || d.code || String(d.id),
|
label: d.name || d.code || String(d.id),
|
||||||
value: d.id,
|
value: d.id,
|
||||||
@@ -453,7 +421,7 @@ const fetchFloors = async (keyword?: string) => {
|
|||||||
const res: any = await fetchFloorListByDatacenter(form.value.datacenter_id, {
|
const res: any = await fetchFloorListByDatacenter(form.value.datacenter_id, {
|
||||||
name: keyword || undefined,
|
name: keyword || undefined,
|
||||||
})
|
})
|
||||||
const rows = extractList(res)
|
const rows = res.details
|
||||||
floorOptions.value = rows.map((floor: any) => ({
|
floorOptions.value = rows.map((floor: any) => ({
|
||||||
label: floor.name || floor.code || String(floor.id),
|
label: floor.name || floor.code || String(floor.id),
|
||||||
value: floor.id,
|
value: floor.id,
|
||||||
@@ -477,35 +445,6 @@ const handleFloorSearch = (keyword: string) => {
|
|||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载机柜列表
|
|
||||||
const fetchRacks = async (keyword?: string) => {
|
|
||||||
if (!form.value.room_id) {
|
|
||||||
rackOptions.value = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rackLoading.value = true
|
|
||||||
try {
|
|
||||||
const res: any = await fetchRackListByRoom(form.value.room_id, { name: keyword })
|
|
||||||
rackOptions.value = extractList(res)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取机柜列表失败:', error)
|
|
||||||
rackOptions.value = []
|
|
||||||
} finally {
|
|
||||||
rackLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 机柜搜索(带防抖)
|
|
||||||
const handleRackSearch = (keyword: string) => {
|
|
||||||
if (!form.value.room_id) return
|
|
||||||
if (rackSearchTimer) {
|
|
||||||
window.clearTimeout(rackSearchTimer)
|
|
||||||
}
|
|
||||||
rackSearchTimer = window.setTimeout(() => {
|
|
||||||
fetchRacks(keyword?.trim() || undefined)
|
|
||||||
}, 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载机房列表
|
// 加载机房列表
|
||||||
const fetchRooms = async (keyword?: string) => {
|
const fetchRooms = async (keyword?: string) => {
|
||||||
if (!form.value.floor_id) {
|
if (!form.value.floor_id) {
|
||||||
@@ -517,7 +456,7 @@ const fetchRooms = async (keyword?: string) => {
|
|||||||
const res: any = await fetchRoomListByFloor(form.value.floor_id, {
|
const res: any = await fetchRoomListByFloor(form.value.floor_id, {
|
||||||
name: keyword || undefined,
|
name: keyword || undefined,
|
||||||
})
|
})
|
||||||
roomOptions.value = extractList(res)
|
roomOptions.value = res.details
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取机房列表失败:', error)
|
console.error('获取机房列表失败:', error)
|
||||||
roomOptions.value = []
|
roomOptions.value = []
|
||||||
@@ -541,38 +480,24 @@ const handleRoomSearch = (keyword: string) => {
|
|||||||
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.room_id = undefined
|
||||||
form.value.rack_id = undefined
|
|
||||||
floorOptions.value = []
|
floorOptions.value = []
|
||||||
roomOptions.value = []
|
roomOptions.value = []
|
||||||
rackOptions.value = []
|
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
await fetchFloors()
|
await fetchFloors()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 楼层变化时加载机柜
|
// 楼层变化时加载机房
|
||||||
const handleFloorChange = async (value: number | undefined) => {
|
const handleFloorChange = async (value: number | undefined) => {
|
||||||
form.value.room_id = undefined
|
form.value.room_id = undefined
|
||||||
form.value.rack_id = undefined
|
|
||||||
roomOptions.value = []
|
roomOptions.value = []
|
||||||
rackOptions.value = []
|
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
await fetchRooms()
|
await fetchRooms()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 机房变化时加载机柜
|
|
||||||
const handleRoomChange = async (value: number | undefined) => {
|
|
||||||
form.value.rack_id = undefined
|
|
||||||
rackOptions.value = []
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
await fetchRacks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化页面
|
// 初始化页面
|
||||||
const initPage = async () => {
|
const initPage = async () => {
|
||||||
await loadOptions()
|
await loadOptions()
|
||||||
@@ -594,14 +519,15 @@ const loadDeviceDetail = async () => {
|
|||||||
try {
|
try {
|
||||||
const res = await fetchAssetDetail(deviceId.value)
|
const res = await fetchAssetDetail(deviceId.value)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
form.value = { ...res.details } as AssetForm
|
placementType.value = res.details.placement_type
|
||||||
|
form.value = createAssetForm(res.details)
|
||||||
|
|
||||||
// 如果有供应商,加载供应商选项
|
// 如果有供应商,加载供应商选项
|
||||||
if (res.details.supplier_id) {
|
if (res.details.supplier_id) {
|
||||||
try {
|
try {
|
||||||
const supplierRes = await fetchAllSuppliers()
|
const supplierRes = await fetchAllSuppliers()
|
||||||
if (supplierRes.code === 0) {
|
if (supplierRes.code === 0) {
|
||||||
supplierOptions.value = supplierRes.details?.data || supplierRes.details || []
|
supplierOptions.value = supplierRes.details
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载供应商失败:', error)
|
console.error('加载供应商失败:', error)
|
||||||
@@ -612,7 +538,7 @@ const loadDeviceDetail = async () => {
|
|||||||
if (res.details.datacenter_id) {
|
if (res.details.datacenter_id) {
|
||||||
try {
|
try {
|
||||||
const floorRes: any = await fetchFloorListByDatacenter(res.details.datacenter_id)
|
const floorRes: any = await fetchFloorListByDatacenter(res.details.datacenter_id)
|
||||||
const rows = extractList(floorRes)
|
const rows = floorRes.details
|
||||||
floorOptions.value = rows.map((floor: any) => ({
|
floorOptions.value = rows.map((floor: any) => ({
|
||||||
label: floor.name || floor.code || String(floor.id),
|
label: floor.name || floor.code || String(floor.id),
|
||||||
value: floor.id,
|
value: floor.id,
|
||||||
@@ -626,21 +552,11 @@ const loadDeviceDetail = async () => {
|
|||||||
if (res.details.floor_id) {
|
if (res.details.floor_id) {
|
||||||
try {
|
try {
|
||||||
const roomRes: any = await fetchRoomListByFloor(res.details.floor_id)
|
const roomRes: any = await fetchRoomListByFloor(res.details.floor_id)
|
||||||
roomOptions.value = extractList(roomRes)
|
roomOptions.value = roomRes.details
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载机房失败:', 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Message.error(res.message || '获取详情失败')
|
Message.error(res.message || '获取详情失败')
|
||||||
router.push('/ops/assets/device')
|
router.push('/ops/assets/device')
|
||||||
|
|||||||
@@ -203,8 +203,8 @@ const fetchDevices = async () => {
|
|||||||
|
|
||||||
const res = await fetchAssetList(params)
|
const res = await fetchAssetList(params)
|
||||||
|
|
||||||
tableData.value = res.details?.data || []
|
tableData.value = res.details.data
|
||||||
pagination.total = res.details?.total || 0
|
pagination.total = res.details.total
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取设备列表失败:', error)
|
console.error('获取设备列表失败:', error)
|
||||||
Message.error('获取设备列表失败')
|
Message.error('获取设备列表失败')
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
<a-descriptions-item label="楼层名称" :span="2">
|
<a-descriptions-item label="楼层名称" :span="2">
|
||||||
{{ floorDetail.name }}
|
{{ floorDetail.name }}
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="楼层编码">
|
||||||
|
{{ floorDetail.code || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="所属中心">
|
<a-descriptions-item label="所属中心">
|
||||||
{{ floorDetail.datacenter?.name || '-' }}
|
{{ floorDetail.datacenter?.name || '-' }}
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
@@ -17,7 +20,7 @@
|
|||||||
<a-descriptions-item label="楼高">
|
<a-descriptions-item label="楼高">
|
||||||
{{ floorDetail.height ? `${floorDetail.height} 米` : '-' }}
|
{{ floorDetail.height ? `${floorDetail.height} 米` : '-' }}
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="称重">
|
<a-descriptions-item label="承重">
|
||||||
{{ floorDetail.load_bearing ? `${floorDetail.load_bearing} kg/m²` : '-' }}
|
{{ floorDetail.load_bearing ? `${floorDetail.load_bearing} kg/m²` : '-' }}
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="可容纳机柜数">
|
<a-descriptions-item label="可容纳机柜数">
|
||||||
|
|||||||
@@ -13,8 +13,12 @@
|
|||||||
<a-input v-model="form.name" placeholder="请输入楼层名称" :max-length="200" />
|
<a-input v-model="form.name" placeholder="请输入楼层名称" :max-length="200" />
|
||||||
</a-form-item>
|
</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-form-item label="所属中心" field="datacenter_id" :rules="[{ required: true, message: '请选择所属中心' }]">
|
||||||
<a-select v-model="form.datacenter_id" placeholder="请选择所属中心" :loading="loadingDatacenters" allow-search>
|
<a-select v-model="form.datacenter_id" placeholder="请选择所属中心" :loading="loadingDatacenters" :disabled="isEdit" allow-search>
|
||||||
<a-option v-for="item in datacenterList" :key="item.id" :value="item.id">
|
<a-option v-for="item in datacenterList" :key="item.id" :value="item.id">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</a-option>
|
</a-option>
|
||||||
@@ -43,8 +47,8 @@
|
|||||||
<a-input-number v-model="form.height" placeholder="请输入楼高" :min="0" :precision="2" style="width: 100%" />
|
<a-input-number v-model="form.height" placeholder="请输入楼高" :min="0" :precision="2" style="width: 100%" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="称重(kg/m²)" field="load_bearing">
|
<a-form-item label="承重(kg/m²)" field="load_bearing">
|
||||||
<a-input-number v-model="form.load_bearing" placeholder="请输入称重" :min="0" :precision="2" style="width: 100%" />
|
<a-input-number v-model="form.load_bearing" placeholder="请输入承重" :min="0" :precision="2" style="width: 100%" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="启用" field="enabled">
|
<a-form-item label="启用" field="enabled">
|
||||||
@@ -59,8 +63,7 @@
|
|||||||
:file-list="fileList"
|
:file-list="fileList"
|
||||||
list-type="picture-card"
|
list-type="picture-card"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
>
|
></a-upload>
|
||||||
</a-upload>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="描述" field="description">
|
<a-form-item label="描述" field="description">
|
||||||
@@ -100,6 +103,7 @@ const localPreviewURL = ref('')
|
|||||||
// 表单数据
|
// 表单数据
|
||||||
const form = ref({
|
const form = ref({
|
||||||
name: '',
|
name: '',
|
||||||
|
code: '',
|
||||||
datacenter_id: undefined as number | undefined,
|
datacenter_id: undefined as number | undefined,
|
||||||
floor_number: 1,
|
floor_number: 1,
|
||||||
status: 'planning',
|
status: 'planning',
|
||||||
@@ -128,7 +132,7 @@ const loadDatacenterList = async () => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchDatacenterList()
|
const res: any = await fetchDatacenterList()
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
datacenterList.value = res.details || []
|
datacenterList.value = res.details
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取数据中心列表失败:', error)
|
console.error('获取数据中心列表失败:', error)
|
||||||
@@ -177,6 +181,7 @@ watch(
|
|||||||
// 编辑模式:填充表单
|
// 编辑模式:填充表单
|
||||||
form.value = {
|
form.value = {
|
||||||
name: props.floor.name || '',
|
name: props.floor.name || '',
|
||||||
|
code: props.floor.code || '',
|
||||||
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',
|
status: props.floor.status || 'planning',
|
||||||
@@ -204,6 +209,7 @@ watch(
|
|||||||
// 新建模式:重置表单
|
// 新建模式:重置表单
|
||||||
form.value = {
|
form.value = {
|
||||||
name: '',
|
name: '',
|
||||||
|
code: '',
|
||||||
datacenter_id: undefined,
|
datacenter_id: undefined,
|
||||||
floor_number: 1,
|
floor_number: 1,
|
||||||
status: 'planning',
|
status: 'planning',
|
||||||
@@ -230,6 +236,7 @@ const handleOk = async () => {
|
|||||||
try {
|
try {
|
||||||
const data: SaveFloorPayload = {
|
const data: SaveFloorPayload = {
|
||||||
name: form.value.name,
|
name: form.value.name,
|
||||||
|
code: form.value.code,
|
||||||
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,
|
status: form.value.status,
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ export const columns: TableColumnData[] = [
|
|||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
width: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '楼层编码',
|
||||||
|
dataIndex: 'code',
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '所属中心',
|
title: '所属中心',
|
||||||
dataIndex: 'datacenter',
|
dataIndex: 'datacenter',
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<template #actions="{ record }">
|
<template #actions="{ record }">
|
||||||
<a-button type="text" size="small" @click="handleDetail(record)">详情</a-button>
|
<a-button type="text" size="small" @click="handleDetail(record)">详情</a-button>
|
||||||
|
<a-button type="text" size="small" @click="handleRooms(record)">机房</a-button>
|
||||||
<a-button type="text" size="small" @click="handleEdit(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>
|
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">删除</a-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -60,6 +61,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { Message, Modal } from '@arco-design/web-vue'
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
||||||
import type { FormItem } from '@/components/search-form/types'
|
import type { FormItem } from '@/components/search-form/types'
|
||||||
@@ -70,6 +72,17 @@ import { fetchFloorList, fetchDatacenterList, deleteFloor } from '@/api/ops/floo
|
|||||||
import FloorDetailDialog from './components/FloorDetailDialog.vue'
|
import FloorDetailDialog from './components/FloorDetailDialog.vue'
|
||||||
import FloorFormDialog from './components/FloorFormDialog.vue'
|
import FloorFormDialog from './components/FloorFormDialog.vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const getRouteDatacenterId = () => {
|
||||||
|
const value = route.query.datacenter_id
|
||||||
|
if (typeof value !== 'string') return undefined
|
||||||
|
|
||||||
|
const datacenterId = Number(value)
|
||||||
|
return Number.isInteger(datacenterId) && datacenterId > 0 ? datacenterId : undefined
|
||||||
|
}
|
||||||
|
|
||||||
// 状态映射
|
// 状态映射
|
||||||
const statusMap: Record<string, { text: string; color: string }> = {
|
const statusMap: Record<string, { text: string; color: string }> = {
|
||||||
planning: { text: '规划中', color: 'blue' },
|
planning: { text: '规划中', color: 'blue' },
|
||||||
@@ -85,7 +98,7 @@ const tableData = ref<any[]>([])
|
|||||||
const formModel = ref({
|
const formModel = ref({
|
||||||
keyword: '',
|
keyword: '',
|
||||||
status: '',
|
status: '',
|
||||||
datacenter_id: undefined as number | undefined,
|
datacenter_id: getRouteDatacenterId() as number | undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
@@ -128,7 +141,7 @@ const loadDatacenterOptions = async (keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchDatacenterList({ keyword })
|
const res: any = await fetchDatacenterList({ keyword })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const list = res.details || []
|
const list = res.details
|
||||||
datacenterSelectOptions.value = Array.isArray(list)
|
datacenterSelectOptions.value = Array.isArray(list)
|
||||||
? list.map((d: any) => ({
|
? list.map((d: any) => ({
|
||||||
label: d.name || d.code || String(d.id),
|
label: d.name || d.code || String(d.id),
|
||||||
@@ -167,8 +180,8 @@ const fetchFloors = async () => {
|
|||||||
|
|
||||||
const res = await fetchFloorList(params)
|
const res = await fetchFloorList(params)
|
||||||
|
|
||||||
tableData.value = res.details?.data || []
|
tableData.value = res.details.data
|
||||||
pagination.total = res.details?.total || 0
|
pagination.total = res.details.total
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取楼层列表失败:', error)
|
console.error('获取楼层列表失败:', error)
|
||||||
Message.error('获取楼层列表失败')
|
Message.error('获取楼层列表失败')
|
||||||
@@ -197,6 +210,10 @@ const handleReset = () => {
|
|||||||
status: '',
|
status: '',
|
||||||
datacenter_id: undefined,
|
datacenter_id: undefined,
|
||||||
}
|
}
|
||||||
|
router.replace({
|
||||||
|
path: route.path,
|
||||||
|
query: { ...route.query, datacenter_id: undefined },
|
||||||
|
})
|
||||||
pagination.current = 1
|
pagination.current = 1
|
||||||
fetchFloors()
|
fetchFloors()
|
||||||
}
|
}
|
||||||
@@ -233,6 +250,16 @@ const handleDetail = (record: any) => {
|
|||||||
detailVisible.value = true
|
detailVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleRooms = (record: any) => {
|
||||||
|
router.push({
|
||||||
|
path: '/datacenter/room',
|
||||||
|
query: {
|
||||||
|
datacenter_id: String(record.datacenter_id),
|
||||||
|
floor_id: String(record.id),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 删除楼层
|
// 删除楼层
|
||||||
const handleDelete = async (record: any) => {
|
const handleDelete = async (record: any) => {
|
||||||
console.log('删除楼层:', record)
|
console.log('删除楼层:', record)
|
||||||
|
|||||||
@@ -113,16 +113,11 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="24">
|
||||||
<a-form-item label="总电力(KW)" field="total_power">
|
<a-form-item label="总电力(KW)" field="total_power">
|
||||||
<a-input-number v-model="form.total_power" placeholder="请输入总电力" :min="0" :precision="2" style="width: 100%" />
|
<a-input-number v-model="form.total_power" placeholder="请输入总电力" :min="0" :precision="2" style="width: 100%" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
|
||||||
<a-form-item label="已用电力(KW)" field="used_power">
|
|
||||||
<a-input-number v-model="form.used_power" placeholder="请输入已用电力" :min="0" :precision="2" style="width: 100%" />
|
|
||||||
</a-form-item>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
<a-form-item label="可容纳机柜数" field="max_rack_capacity">
|
<a-form-item label="可容纳机柜数" field="max_rack_capacity">
|
||||||
@@ -178,7 +173,6 @@ interface Datacenter {
|
|||||||
total_area?: number
|
total_area?: number
|
||||||
it_area?: number
|
it_area?: number
|
||||||
total_power?: number
|
total_power?: number
|
||||||
used_power?: number
|
|
||||||
max_rack_capacity?: number
|
max_rack_capacity?: number
|
||||||
iso27001?: boolean
|
iso27001?: boolean
|
||||||
iso9001?: boolean
|
iso9001?: boolean
|
||||||
@@ -219,7 +213,6 @@ const form = ref({
|
|||||||
total_area: undefined as number | undefined,
|
total_area: undefined as number | undefined,
|
||||||
it_area: undefined as number | undefined,
|
it_area: undefined as number | undefined,
|
||||||
total_power: undefined as number | undefined,
|
total_power: undefined as number | undefined,
|
||||||
used_power: undefined as number | undefined,
|
|
||||||
max_rack_capacity: undefined as number | undefined,
|
max_rack_capacity: undefined as number | undefined,
|
||||||
iso27001: false,
|
iso27001: false,
|
||||||
iso9001: false,
|
iso9001: false,
|
||||||
@@ -254,7 +247,7 @@ const fetchProvinces = async (keyword = '') => {
|
|||||||
provinceLoading.value = true
|
provinceLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await fetchProvinceList({ keyword: keyword || undefined })
|
const res = await fetchProvinceList({ keyword: keyword || undefined })
|
||||||
provinceOptions.value = res.details || []
|
provinceOptions.value = res.details
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取省份列表失败:', error)
|
console.error('获取省份列表失败:', error)
|
||||||
provinceOptions.value = []
|
provinceOptions.value = []
|
||||||
@@ -267,7 +260,7 @@ const fetchCitiesByProvince = async (provinceId: number, keyword = '') => {
|
|||||||
cityLoading.value = true
|
cityLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await fetchCityList(provinceId, { keyword: keyword || undefined })
|
const res = await fetchCityList(provinceId, { keyword: keyword || undefined })
|
||||||
cityOptions.value = res.details || []
|
cityOptions.value = res.details
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取城市列表失败:', error)
|
console.error('获取城市列表失败:', error)
|
||||||
cityOptions.value = []
|
cityOptions.value = []
|
||||||
@@ -323,7 +316,6 @@ watch(
|
|||||||
total_area: props.datacenter.total_area,
|
total_area: props.datacenter.total_area,
|
||||||
it_area: props.datacenter.it_area,
|
it_area: props.datacenter.it_area,
|
||||||
total_power: props.datacenter.total_power,
|
total_power: props.datacenter.total_power,
|
||||||
used_power: props.datacenter.used_power,
|
|
||||||
max_rack_capacity: props.datacenter.max_rack_capacity,
|
max_rack_capacity: props.datacenter.max_rack_capacity,
|
||||||
iso27001: props.datacenter.iso27001 !== undefined ? props.datacenter.iso27001 : false,
|
iso27001: props.datacenter.iso27001 !== undefined ? props.datacenter.iso27001 : false,
|
||||||
iso9001: props.datacenter.iso9001 !== undefined ? props.datacenter.iso9001 : false,
|
iso9001: props.datacenter.iso9001 !== undefined ? props.datacenter.iso9001 : false,
|
||||||
@@ -351,7 +343,6 @@ watch(
|
|||||||
total_area: undefined,
|
total_area: undefined,
|
||||||
it_area: undefined,
|
it_area: undefined,
|
||||||
total_power: undefined,
|
total_power: undefined,
|
||||||
used_power: undefined,
|
|
||||||
max_rack_capacity: undefined,
|
max_rack_capacity: undefined,
|
||||||
iso27001: false,
|
iso27001: false,
|
||||||
iso9001: false,
|
iso9001: false,
|
||||||
@@ -387,7 +378,6 @@ const handleOk = async () => {
|
|||||||
total_area: form.value.total_area,
|
total_area: form.value.total_area,
|
||||||
it_area: form.value.it_area,
|
it_area: form.value.it_area,
|
||||||
total_power: form.value.total_power,
|
total_power: form.value.total_power,
|
||||||
used_power: form.value.used_power,
|
|
||||||
max_rack_capacity: form.value.max_rack_capacity,
|
max_rack_capacity: form.value.max_rack_capacity,
|
||||||
iso27001: form.value.iso27001,
|
iso27001: form.value.iso27001,
|
||||||
iso9001: form.value.iso9001,
|
iso9001: form.value.iso9001,
|
||||||
|
|||||||
@@ -109,6 +109,7 @@
|
|||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<template #actions="{ record }">
|
<template #actions="{ record }">
|
||||||
<a-button type="text" size="small" @click="handleDetail(record)">详情</a-button>
|
<a-button type="text" size="small" @click="handleDetail(record)">详情</a-button>
|
||||||
|
<a-button type="text" size="small" @click="handleFloors(record)">楼层</a-button>
|
||||||
<a-button type="text" size="small" @click="handleEdit(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>
|
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">删除</a-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -129,6 +130,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { Message, Modal } from '@arco-design/web-vue'
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
||||||
import type { FormItem } from '@/components/search-form/types'
|
import type { FormItem } from '@/components/search-form/types'
|
||||||
@@ -139,6 +141,8 @@ import { fetchDatacenterList, deleteDatacenter, fetchProvinceList, fetchCityList
|
|||||||
import DatacenterDetailDialog from './components/DatacenterDetailDialog.vue'
|
import DatacenterDetailDialog from './components/DatacenterDetailDialog.vue'
|
||||||
import DatacenterFormDialog from './components/DatacenterFormDialog.vue'
|
import DatacenterFormDialog from './components/DatacenterFormDialog.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// 状态映射
|
// 状态映射
|
||||||
const statusMap: Record<string, { text: string; color: string }> = {
|
const statusMap: Record<string, { text: string; color: string }> = {
|
||||||
planning: { text: '规划中', color: 'blue' },
|
planning: { text: '规划中', color: 'blue' },
|
||||||
@@ -192,7 +196,7 @@ const fetchProvinces = async (keyword = '') => {
|
|||||||
provinceSelectLoading.value = true
|
provinceSelectLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await fetchProvinceList({ keyword: keyword || undefined })
|
const res = await fetchProvinceList({ keyword: keyword || undefined })
|
||||||
const list = res.details || []
|
const list = res.details
|
||||||
provinceSelectOptions.value = list
|
provinceSelectOptions.value = list
|
||||||
if (!keyword) {
|
if (!keyword) {
|
||||||
provinces.value = list
|
provinces.value = list
|
||||||
@@ -210,7 +214,7 @@ const fetchCitiesByProvince = async (provinceId: number, keyword = '') => {
|
|||||||
citySelectLoading.value = true
|
citySelectLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await fetchCityList(provinceId, { keyword: keyword || undefined })
|
const res = await fetchCityList(provinceId, { keyword: keyword || undefined })
|
||||||
citySelectOptions.value = res.details || []
|
citySelectOptions.value = res.details
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取城市列表失败:', error)
|
console.error('获取城市列表失败:', error)
|
||||||
citySelectOptions.value = []
|
citySelectOptions.value = []
|
||||||
@@ -264,8 +268,8 @@ const fetchDatacenters = async () => {
|
|||||||
|
|
||||||
const res = await fetchDatacenterList(params)
|
const res = await fetchDatacenterList(params)
|
||||||
|
|
||||||
tableData.value = res.details?.data || []
|
tableData.value = res.details.data
|
||||||
pagination.total = res.details?.total || 0
|
pagination.total = res.details.total
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取数据中心列表失败:', error)
|
console.error('获取数据中心列表失败:', error)
|
||||||
Message.error('获取数据中心列表失败')
|
Message.error('获取数据中心列表失败')
|
||||||
@@ -333,6 +337,13 @@ const handleDetail = (record: any) => {
|
|||||||
detailVisible.value = true
|
detailVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFloors = (record: any) => {
|
||||||
|
router.push({
|
||||||
|
path: '/datacenter/floor',
|
||||||
|
query: { datacenter_id: String(record.id) },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 删除数据中心
|
// 删除数据中心
|
||||||
const handleDelete = async (record: any) => {
|
const handleDelete = async (record: any) => {
|
||||||
console.log('删除数据中心:', record)
|
console.log('删除数据中心:', record)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<a-descriptions-item label="可用U位数">{{ rackDetail.available_units || '-' }}</a-descriptions-item>
|
<a-descriptions-item label="可用U位数">{{ rackDetail.available_units || '-' }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="使用率">
|
<a-descriptions-item label="使用率">
|
||||||
<a-progress
|
<a-progress
|
||||||
:percent="rackDetail.utilization_rate || 0"
|
:percent="(rackDetail.utilization_rate || 0) / 100"
|
||||||
:color="(rackDetail.utilization_rate || 0) > 80 ? '#f53f3f' : (rackDetail.utilization_rate || 0) > 50 ? '#ff7d00' : '#00b42a'"
|
:color="(rackDetail.utilization_rate || 0) > 80 ? '#f53f3f' : (rackDetail.utilization_rate || 0) > 50 ? '#ff7d00' : '#00b42a'"
|
||||||
/>
|
/>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
v-model="form.datacenter_id"
|
v-model="form.datacenter_id"
|
||||||
placeholder="请选择所属中心"
|
placeholder="请选择所属中心"
|
||||||
:loading="loadingDatacenters"
|
:loading="loadingDatacenters"
|
||||||
|
:disabled="isEdit"
|
||||||
allow-search
|
allow-search
|
||||||
@change="handleDatacenterChange"
|
@change="handleDatacenterChange"
|
||||||
>
|
>
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
v-model="form.floor_id"
|
v-model="form.floor_id"
|
||||||
placeholder="请选择所属楼层"
|
placeholder="请选择所属楼层"
|
||||||
:loading="loadingFloors"
|
:loading="loadingFloors"
|
||||||
:disabled="!form.datacenter_id"
|
:disabled="isEdit || !form.datacenter_id"
|
||||||
allow-search
|
allow-search
|
||||||
@change="handleFloorChange"
|
@change="handleFloorChange"
|
||||||
@search="handleFloorSearch"
|
@search="handleFloorSearch"
|
||||||
@@ -65,7 +66,7 @@
|
|||||||
v-model="form.room_id"
|
v-model="form.room_id"
|
||||||
placeholder="请选择所属机房"
|
placeholder="请选择所属机房"
|
||||||
:loading="loadingRooms"
|
:loading="loadingRooms"
|
||||||
:disabled="!form.floor_id"
|
:disabled="isEdit || !form.floor_id"
|
||||||
allow-search
|
allow-search
|
||||||
@search="handleRoomSearch"
|
@search="handleRoomSearch"
|
||||||
>
|
>
|
||||||
@@ -81,18 +82,18 @@
|
|||||||
<a-divider orientation="left">规格参数</a-divider>
|
<a-divider orientation="left">规格参数</a-divider>
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item label="高度(U)" field="height">
|
<a-form-item label="高度(U)" field="height" :rules="[{ required: true, message: '请输入机柜高度' }]">
|
||||||
<a-input-number v-model="form.height" placeholder="默认42" :min="1" :max="100" style="width: 100%" />
|
<a-input-number v-model="form.height" placeholder="默认42" :min="1" :max="100" style="width: 100%" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item label="宽度(mm)" field="width">
|
<a-form-item label="宽度(mm)" field="width" :rules="[{ required: true, message: '请输入大于0的机柜宽度' }]">
|
||||||
<a-input-number v-model="form.width" placeholder="请输入宽度" :min="0" :precision="2" style="width: 100%" />
|
<a-input-number v-model="form.width" placeholder="请输入宽度" :min="1" :precision="2" :disabled="isEdit" style="width: 100%" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item label="深度(mm)" field="depth">
|
<a-form-item label="深度(mm)" field="depth" :rules="[{ required: true, message: '请输入大于0的机柜深度' }]">
|
||||||
<a-input-number v-model="form.depth" placeholder="请输入深度" :min="0" :precision="2" style="width: 100%" />
|
<a-input-number v-model="form.depth" placeholder="请输入深度" :min="1" :precision="2" :disabled="isEdit" style="width: 100%" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -244,24 +245,14 @@
|
|||||||
<!-- 位置信息 -->
|
<!-- 位置信息 -->
|
||||||
<a-divider orientation="left">位置信息</a-divider>
|
<a-divider orientation="left">位置信息</a-divider>
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="6">
|
<a-col :span="12">
|
||||||
<a-form-item label="行号" field="row">
|
<a-form-item label="行号" field="row">
|
||||||
<a-input-number v-model="form.row" placeholder="请输入行号" :min="0" style="width: 100%" />
|
<a-input-number v-model="form.row" placeholder="请输入行号" :min="0" :disabled="isEdit" style="width: 100%" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="12">
|
||||||
<a-form-item label="列号" field="column">
|
<a-form-item label="列号" field="column">
|
||||||
<a-input-number v-model="form.column" placeholder="请输入列号" :min="0" style="width: 100%" />
|
<a-input-number v-model="form.column" placeholder="请输入列号" :min="0" :disabled="isEdit" style="width: 100%" />
|
||||||
</a-form-item>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="6">
|
|
||||||
<a-form-item label="X坐标" field="position_x">
|
|
||||||
<a-input-number v-model="form.position_x" placeholder="请输入X坐标" :precision="2" style="width: 100%" />
|
|
||||||
</a-form-item>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="6">
|
|
||||||
<a-form-item label="Y坐标" field="position_y">
|
|
||||||
<a-input-number v-model="form.position_y" placeholder="请输入Y坐标" :precision="2" style="width: 100%" />
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -332,8 +323,6 @@ interface Rack {
|
|||||||
department?: string
|
department?: string
|
||||||
row?: number
|
row?: number
|
||||||
column?: number
|
column?: number
|
||||||
position_x?: number
|
|
||||||
position_y?: number
|
|
||||||
color?: string
|
color?: string
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
description?: string
|
description?: string
|
||||||
@@ -397,8 +386,6 @@ const form = ref({
|
|||||||
department: '',
|
department: '',
|
||||||
row: 0,
|
row: 0,
|
||||||
column: 0,
|
column: 0,
|
||||||
position_x: undefined as number | undefined,
|
|
||||||
position_y: undefined as number | undefined,
|
|
||||||
color: '',
|
color: '',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
description: '',
|
description: '',
|
||||||
@@ -414,7 +401,7 @@ const loadDatacenterList = async () => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchDatacenterList()
|
const res: any = await fetchDatacenterList()
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
datacenterList.value = res.details || []
|
datacenterList.value = res.details
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取数据中心列表失败:', error)
|
console.error('获取数据中心列表失败:', error)
|
||||||
@@ -433,8 +420,7 @@ const loadFloorList = async (datacenterId?: number, keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchFloorListByDatacenter(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 ?? []
|
floorList.value = res.details
|
||||||
floorList.value = Array.isArray(list) ? list : []
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取楼层列表失败:', error)
|
console.error('获取楼层列表失败:', error)
|
||||||
@@ -449,7 +435,7 @@ const loadSupplierList = async () => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchSupplierList()
|
const res: any = await fetchSupplierList()
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
supplierList.value = res.details || []
|
supplierList.value = res.details
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取供应商列表失败:', error)
|
console.error('获取供应商列表失败:', error)
|
||||||
@@ -475,8 +461,7 @@ const loadRoomList = async (floorId?: number, keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchRoomListByFloor(floorId, { name: keyword })
|
const res: any = await fetchRoomListByFloor(floorId, { name: keyword })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const list = res.details?.data ?? res.data ?? res.details ?? []
|
roomList.value = res.details
|
||||||
roomList.value = Array.isArray(list) ? list : []
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取机房列表失败:', error)
|
console.error('获取机房列表失败:', error)
|
||||||
@@ -548,8 +533,6 @@ watch(
|
|||||||
department: props.rack.department || '',
|
department: props.rack.department || '',
|
||||||
row: props.rack.row || 0,
|
row: props.rack.row || 0,
|
||||||
column: props.rack.column || 0,
|
column: props.rack.column || 0,
|
||||||
position_x: props.rack.position_x,
|
|
||||||
position_y: props.rack.position_y,
|
|
||||||
color: props.rack.color || '',
|
color: props.rack.color || '',
|
||||||
enabled: props.rack.enabled !== undefined ? props.rack.enabled : true,
|
enabled: props.rack.enabled !== undefined ? props.rack.enabled : true,
|
||||||
description: props.rack.description || '',
|
description: props.rack.description || '',
|
||||||
@@ -594,8 +577,6 @@ watch(
|
|||||||
department: '',
|
department: '',
|
||||||
row: 0,
|
row: 0,
|
||||||
column: 0,
|
column: 0,
|
||||||
position_x: undefined,
|
|
||||||
position_y: undefined,
|
|
||||||
color: '',
|
color: '',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
description: '',
|
description: '',
|
||||||
@@ -645,8 +626,6 @@ const handleOk = async () => {
|
|||||||
department: form.value.department,
|
department: form.value.department,
|
||||||
row: form.value.row,
|
row: form.value.row,
|
||||||
column: form.value.column,
|
column: form.value.column,
|
||||||
position_x: form.value.position_x,
|
|
||||||
position_y: form.value.position_y,
|
|
||||||
color: form.value.color,
|
color: form.value.color,
|
||||||
enabled: form.value.enabled,
|
enabled: form.value.enabled,
|
||||||
description: form.value.description,
|
description: form.value.description,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||||
import { Message, Modal } from '@arco-design/web-vue'
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import type { FormItem } from '@/components/search-form/types'
|
import type { FormItem } from '@/components/search-form/types'
|
||||||
import SearchTable from '@/components/search-table/index.vue'
|
import SearchTable from '@/components/search-table/index.vue'
|
||||||
import { searchFormConfig } from './config/search-form'
|
import { searchFormConfig } from './config/search-form'
|
||||||
@@ -97,16 +97,25 @@ 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'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const getRouteId = (key: 'datacenter_id' | 'floor_id' | 'room_id') => {
|
||||||
|
const value = route.query[key]
|
||||||
|
if (typeof value !== 'string') return undefined
|
||||||
|
|
||||||
|
const id = Number(value)
|
||||||
|
return Number.isInteger(id) && id > 0 ? id : undefined
|
||||||
|
}
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableData = ref<any[]>([])
|
const tableData = ref<any[]>([])
|
||||||
const formModel = ref({
|
const formModel = ref({
|
||||||
keyword: '',
|
keyword: '',
|
||||||
datacenter_id: undefined,
|
datacenter_id: getRouteId('datacenter_id') as number | undefined,
|
||||||
floor_id: undefined,
|
floor_id: getRouteId('floor_id') as number | undefined,
|
||||||
room_id: undefined,
|
room_id: getRouteId('room_id') as number | undefined,
|
||||||
rack_type: undefined,
|
rack_type: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
})
|
})
|
||||||
@@ -172,9 +181,7 @@ const loadDatacenterOptions = async (keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchDatacenterList({ keyword })
|
const res: any = await fetchDatacenterList({ keyword })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const list = res.details?.data ?? res.data ?? res.details ?? []
|
datacenterSelectOptions.value = res.details.map((d: any) => ({
|
||||||
const rows = Array.isArray(list) ? list : []
|
|
||||||
datacenterSelectOptions.value = rows.map((d: any) => ({
|
|
||||||
label: d.name || d.code || String(d.id),
|
label: d.name || d.code || String(d.id),
|
||||||
value: d.id,
|
value: d.id,
|
||||||
}))
|
}))
|
||||||
@@ -195,9 +202,7 @@ const loadFloorOptions = async (datacenterId?: number, keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchFloorListByDatacenter(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 ?? []
|
floorSelectOptions.value = res.details.map((floor: any) => ({
|
||||||
const rows = Array.isArray(list) ? list : []
|
|
||||||
floorSelectOptions.value = rows.map((floor: any) => ({
|
|
||||||
label: floor.name || String(floor.id),
|
label: floor.name || String(floor.id),
|
||||||
value: floor.id,
|
value: floor.id,
|
||||||
}))
|
}))
|
||||||
@@ -217,9 +222,7 @@ const loadRoomOptions = async (floorId?: number, keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchRoomListByFloor(floorId, { name: keyword })
|
const res: any = await fetchRoomListByFloor(floorId, { name: keyword })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const list = res.details?.data ?? res.data ?? res.details ?? []
|
roomSelectOptions.value = res.details.map((room: any) => ({
|
||||||
const rows = Array.isArray(list) ? list : []
|
|
||||||
roomSelectOptions.value = rows.map((room: any) => ({
|
|
||||||
label: room.name || String(room.id),
|
label: room.name || String(room.id),
|
||||||
value: room.id,
|
value: room.id,
|
||||||
}))
|
}))
|
||||||
@@ -347,9 +350,8 @@ const fetchRacks = async () => {
|
|||||||
|
|
||||||
const res = await fetchRackList(params)
|
const res = await fetchRackList(params)
|
||||||
|
|
||||||
const payload = res?.data ?? res?.details ?? {}
|
tableData.value = res.details.data
|
||||||
tableData.value = Array.isArray(payload?.data) ? payload.data : []
|
pagination.total = res.details.total
|
||||||
pagination.total = payload?.total ?? 0
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取机柜列表失败:', error)
|
console.error('获取机柜列表失败:', error)
|
||||||
Message.error('获取机柜列表失败')
|
Message.error('获取机柜列表失败')
|
||||||
@@ -381,6 +383,15 @@ const handleReset = () => {
|
|||||||
rack_type: undefined,
|
rack_type: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
}
|
}
|
||||||
|
router.replace({
|
||||||
|
path: route.path,
|
||||||
|
query: {
|
||||||
|
...route.query,
|
||||||
|
datacenter_id: undefined,
|
||||||
|
floor_id: undefined,
|
||||||
|
room_id: undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
pagination.current = 1
|
pagination.current = 1
|
||||||
fetchRacks()
|
fetchRacks()
|
||||||
}
|
}
|
||||||
@@ -453,6 +464,8 @@ const handleFormSuccess = () => {
|
|||||||
// 初始化加载数据
|
// 初始化加载数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadDatacenterOptions()
|
loadDatacenterOptions()
|
||||||
|
loadFloorOptions(formModel.value.datacenter_id)
|
||||||
|
loadRoomOptions(formModel.value.floor_id)
|
||||||
fetchRacks()
|
fetchRacks()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -56,10 +56,6 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="已创建3D机房" field="has3D">
|
|
||||||
<a-switch v-model="form.has3D" />
|
|
||||||
</a-form-item>
|
|
||||||
|
|
||||||
<a-form-item label="描述" field="description">
|
<a-form-item label="描述" field="description">
|
||||||
<a-textarea v-model="form.description" placeholder="请输入描述" :auto-size="{ minRows: 4, maxRows: 8 }" :max-length="500" />
|
<a-textarea v-model="form.description" placeholder="请输入描述" :auto-size="{ minRows: 4, maxRows: 8 }" :max-length="500" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -81,7 +77,6 @@ interface Room {
|
|||||||
datacenter_id?: number
|
datacenter_id?: number
|
||||||
floor_id?: number
|
floor_id?: number
|
||||||
status?: string
|
status?: string
|
||||||
has3D: boolean
|
|
||||||
description?: string
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +107,6 @@ const form = ref({
|
|||||||
datacenter_id: undefined as number | undefined,
|
datacenter_id: undefined as number | undefined,
|
||||||
floor_id: undefined as number | undefined,
|
floor_id: undefined as number | undefined,
|
||||||
status: 'planning',
|
status: 'planning',
|
||||||
has3D: false,
|
|
||||||
description: '',
|
description: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -123,7 +117,7 @@ const loadDatacenterList = async () => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchDatacenterList()
|
const res: any = await fetchDatacenterList()
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
datacenterList.value = res.details || []
|
datacenterList.value = res.details
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取数据中心列表失败:', error)
|
console.error('获取数据中心列表失败:', error)
|
||||||
@@ -141,8 +135,7 @@ const loadFloorList = async (datacenterId?: number, keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchFloorListByDatacenter(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 ?? []
|
floorList.value = res.details
|
||||||
floorList.value = Array.isArray(list) ? list : []
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取楼层列表失败:', error)
|
console.error('获取楼层列表失败:', error)
|
||||||
@@ -179,7 +172,6 @@ watch(
|
|||||||
datacenter_id: props.room.datacenter_id,
|
datacenter_id: props.room.datacenter_id,
|
||||||
floor_id: props.room.floor_id,
|
floor_id: props.room.floor_id,
|
||||||
status: props.room.status || 'planning',
|
status: props.room.status || 'planning',
|
||||||
has3D: props.room.has3D,
|
|
||||||
description: props.room.description || '',
|
description: props.room.description || '',
|
||||||
}
|
}
|
||||||
if (props.room.datacenter_id) {
|
if (props.room.datacenter_id) {
|
||||||
@@ -192,7 +184,6 @@ watch(
|
|||||||
datacenter_id: undefined,
|
datacenter_id: undefined,
|
||||||
floor_id: undefined,
|
floor_id: undefined,
|
||||||
status: 'planning',
|
status: 'planning',
|
||||||
has3D: false,
|
|
||||||
description: '',
|
description: '',
|
||||||
}
|
}
|
||||||
floorList.value = []
|
floorList.value = []
|
||||||
@@ -212,7 +203,6 @@ const handleOk = async () => {
|
|||||||
datacenter_id: form.value.datacenter_id,
|
datacenter_id: form.value.datacenter_id,
|
||||||
floor_id: form.value.floor_id,
|
floor_id: form.value.floor_id,
|
||||||
status: form.value.status,
|
status: form.value.status,
|
||||||
has3D: form.value.has3D,
|
|
||||||
description: form.value.description,
|
description: form.value.description,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,16 +45,9 @@
|
|||||||
|
|
||||||
<template #actions="{ record }">
|
<template #actions="{ record }">
|
||||||
<a-button type="text" size="small" @click="handleDetail(record)">详情</a-button>
|
<a-button type="text" size="small" @click="handleDetail(record)">详情</a-button>
|
||||||
|
<a-button type="text" size="small" @click="handleRacks(record)">机柜</a-button>
|
||||||
<a-button type="text" size="small" @click="handleEdit(record)">编辑</a-button>
|
<a-button type="text" size="small" @click="handleEdit(record)">编辑</a-button>
|
||||||
<a-button
|
<a-button type="text" size="small" @click="handleThreeDRoom(record)">3D机房</a-button>
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
:disabled="!record.has3D"
|
|
||||||
:style="{ color: record.has3D ? undefined : 'rgb(var(--gray-6))' }"
|
|
||||||
@click="handleThreeDRoom"
|
|
||||||
>
|
|
||||||
3d机房
|
|
||||||
</a-button>
|
|
||||||
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">删除</a-button>
|
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">删除</a-button>
|
||||||
</template>
|
</template>
|
||||||
</search-table>
|
</search-table>
|
||||||
@@ -67,9 +60,9 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { Message, Modal } from '@arco-design/web-vue'
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import type { FormItem } from '@/components/search-form/types'
|
import type { FormItem } from '@/components/search-form/types'
|
||||||
import SearchTable from '@/components/search-table/index.vue'
|
import SearchTable from '@/components/search-table/index.vue'
|
||||||
import { searchFormConfig } from './config/search-form'
|
import { searchFormConfig } from './config/search-form'
|
||||||
@@ -79,8 +72,17 @@ import { fetchDatacenterList, fetchFloorListByDatacenter } from '@/api/ops/floor
|
|||||||
import RoomFormDialog from './components/RoomFormDialog.vue'
|
import RoomFormDialog from './components/RoomFormDialog.vue'
|
||||||
import RoomDetailDialog from './components/RoomDetailDialog.vue'
|
import RoomDetailDialog from './components/RoomDetailDialog.vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const getRouteId = (key: 'datacenter_id' | 'floor_id') => {
|
||||||
|
const value = route.query[key]
|
||||||
|
if (typeof value !== 'string') return undefined
|
||||||
|
|
||||||
|
const id = Number(value)
|
||||||
|
return Number.isInteger(id) && id > 0 ? id : undefined
|
||||||
|
}
|
||||||
|
|
||||||
const statusMap: Record<string, { text: string; color: string }> = {
|
const statusMap: Record<string, { text: string; color: string }> = {
|
||||||
planning: { text: '规划中', color: 'blue' },
|
planning: { text: '规划中', color: 'blue' },
|
||||||
construction: { text: '建设中', color: 'orange' },
|
construction: { text: '建设中', color: 'orange' },
|
||||||
@@ -93,8 +95,8 @@ const loading = ref(false)
|
|||||||
const tableData = ref<any[]>([])
|
const tableData = ref<any[]>([])
|
||||||
const formModel = ref({
|
const formModel = ref({
|
||||||
keyword: '',
|
keyword: '',
|
||||||
datacenter_id: undefined as number | undefined,
|
datacenter_id: getRouteId('datacenter_id') as number | undefined,
|
||||||
floor_id: undefined as number | undefined,
|
floor_id: getRouteId('floor_id') as number | undefined,
|
||||||
status: '',
|
status: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -144,7 +146,7 @@ const loadDatacenterOptions = async (keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchDatacenterList({ keyword })
|
const res: any = await fetchDatacenterList({ keyword })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const list = res.details || []
|
const list = res.details
|
||||||
datacenterSelectOptions.value = Array.isArray(list)
|
datacenterSelectOptions.value = Array.isArray(list)
|
||||||
? list.map((d: any) => ({
|
? list.map((d: any) => ({
|
||||||
label: d.name || d.code || String(d.id),
|
label: d.name || d.code || String(d.id),
|
||||||
@@ -167,13 +169,10 @@ const loadFloorOptions = async (datacenterId?: number, keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchFloorListByDatacenter(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 ?? []
|
floorSelectOptions.value = res.details.map((f: any) => ({
|
||||||
floorSelectOptions.value = Array.isArray(list)
|
|
||||||
? list.map((f: any) => ({
|
|
||||||
label: f.name || String(f.id),
|
label: f.name || String(f.id),
|
||||||
value: f.id,
|
value: f.id,
|
||||||
}))
|
}))
|
||||||
: []
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取楼层列表失败:', error)
|
console.error('获取楼层列表失败:', error)
|
||||||
@@ -224,8 +223,8 @@ const fetchRooms = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const res: any = await fetchRoomList(params)
|
const res: any = await fetchRoomList(params)
|
||||||
tableData.value = res.details?.data || []
|
tableData.value = res.details.data
|
||||||
pagination.total = res.details?.total || 0
|
pagination.total = res.details.total
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取机房列表失败:', error)
|
console.error('获取机房列表失败:', error)
|
||||||
Message.error('获取机房列表失败')
|
Message.error('获取机房列表失败')
|
||||||
@@ -252,6 +251,10 @@ const handleReset = () => {
|
|||||||
floor_id: undefined,
|
floor_id: undefined,
|
||||||
status: '',
|
status: '',
|
||||||
}
|
}
|
||||||
|
router.replace({
|
||||||
|
path: route.path,
|
||||||
|
query: { ...route.query, datacenter_id: undefined, floor_id: undefined },
|
||||||
|
})
|
||||||
pagination.current = 1
|
pagination.current = 1
|
||||||
fetchRooms()
|
fetchRooms()
|
||||||
}
|
}
|
||||||
@@ -281,8 +284,19 @@ const handleDetail = (record: any) => {
|
|||||||
detailVisible.value = true
|
detailVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleThreeDRoom = () => {
|
const handleRacks = (record: any) => {
|
||||||
router.push('/datacenter/room')
|
router.push({
|
||||||
|
path: '/datacenter/rack',
|
||||||
|
query: {
|
||||||
|
datacenter_id: String(record.datacenter_id),
|
||||||
|
floor_id: String(record.floor_id),
|
||||||
|
room_id: String(record.id),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleThreeDRoom = (record: any) => {
|
||||||
|
router.push(`/datacenter/room-3d/${record.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDelete = async (record: any) => {
|
const handleDelete = async (record: any) => {
|
||||||
@@ -312,6 +326,7 @@ const handleFormSuccess = () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadDatacenterOptions()
|
loadDatacenterOptions()
|
||||||
|
loadFloorOptions(formModel.value.datacenter_id)
|
||||||
fetchRooms()
|
fetchRooms()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ export default defineComponent({
|
|||||||
import { ref, reactive, computed, watch } from 'vue'
|
import { ref, reactive, computed, watch } from 'vue'
|
||||||
import { Message } from '@arco-design/web-vue'
|
import { Message } from '@arco-design/web-vue'
|
||||||
import { fetchAssetAll } from '@/api/ops/asset'
|
import { fetchAssetAll } from '@/api/ops/asset'
|
||||||
|
import { fetchRoomDetail } from '@/api/ops/room'
|
||||||
|
import { placeAssetInRack } from '@/api/ops/unit'
|
||||||
|
|
||||||
interface Asset {
|
interface Asset {
|
||||||
id: number
|
id: number
|
||||||
@@ -69,6 +71,8 @@ interface Props {
|
|||||||
visible: boolean
|
visible: boolean
|
||||||
rackId: number
|
rackId: number
|
||||||
rackHeight: number
|
rackHeight: number
|
||||||
|
roomId?: number
|
||||||
|
initialStartUnit: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
@@ -129,15 +133,13 @@ const loadAssetList = async (keyword?: string) => {
|
|||||||
try {
|
try {
|
||||||
const res: any = await fetchAssetAll({ keyword: keyword || undefined })
|
const res: any = await fetchAssetAll({ keyword: keyword || undefined })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const rowsCandidate = res?.data?.data ?? res?.details?.data ?? res?.data ?? res?.details ?? []
|
assetList.value = res.details.map((item: any) => ({
|
||||||
const rows = Array.isArray(rowsCandidate) ? rowsCandidate : []
|
|
||||||
assetList.value = rows.map((item: any) => ({
|
|
||||||
id: item.id,
|
id: item.id,
|
||||||
asset_code: item.asset_code || '',
|
asset_code: item.asset_code,
|
||||||
asset_name: item.asset_name || '',
|
asset_name: item.asset_name,
|
||||||
asset_type_name: item?.category?.name || item?.category_name || item?.asset_type_name || '',
|
asset_type_name: item.category?.name || '',
|
||||||
asset_type_code: item?.category?.code ?? item?.category_code ?? item?.asset_type ?? '',
|
asset_type_code: item.category?.code || '',
|
||||||
category_id: item?.category_id,
|
category_id: item.category_id,
|
||||||
power_consumption: item.power_consumption,
|
power_consumption: item.power_consumption,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
@@ -173,27 +175,36 @@ const handleBeforeOk = async () => {
|
|||||||
// 防止 change 事件未触发时字段未回写
|
// 防止 change 事件未触发时字段未回写
|
||||||
syncAssetFields(formData.asset_id)
|
syncAssetFields(formData.asset_id)
|
||||||
|
|
||||||
const { allocateUnit } = await import('@/api/ops/unit')
|
if (!props.roomId || !formData.asset_id) {
|
||||||
const params = {
|
Message.error('机房或设备信息不完整')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const roomRes = await fetchRoomDetail(props.roomId)
|
||||||
|
if (roomRes.code !== 0) {
|
||||||
|
Message.error(roomRes.message || '获取机房布局版本失败')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await placeAssetInRack(props.roomId, formData.asset_id, {
|
||||||
|
expected_version: roomRes.details.layout_version,
|
||||||
rack_id: props.rackId,
|
rack_id: props.rackId,
|
||||||
start_unit: formData.start_unit,
|
start_unit: formData.start_unit,
|
||||||
occupied_units: formData.occupied_units,
|
occupied_units: formData.occupied_units,
|
||||||
asset_id: formData.asset_id,
|
power_consumption: formData.power_consumption ?? 0,
|
||||||
asset_code: formData.asset_code,
|
})
|
||||||
asset_name: formData.asset_name || `未命名设备-${formData.start_unit}`,
|
if (res.code !== 0) {
|
||||||
// 后端字段 asset_type 使用分类标识(category.code)
|
Message.error(res.message || '分配失败')
|
||||||
asset_type: formData.asset_type_code,
|
return false
|
||||||
power_consumption: formData.power_consumption,
|
|
||||||
description: formData.description,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await allocateUnit(params)
|
|
||||||
if (res.code === 0) {
|
|
||||||
Message.success('分配成功')
|
Message.success('分配成功')
|
||||||
emit('success')
|
emit('success')
|
||||||
return true
|
return true
|
||||||
} else {
|
} catch (error) {
|
||||||
Message.error(res.message || '分配失败')
|
console.error('分配U位失败:', error)
|
||||||
|
Message.error('分配失败')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,12 +229,13 @@ const resetForm = () => {
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(val) => {
|
(visible) => {
|
||||||
if (val) {
|
if (visible) {
|
||||||
|
formData.start_unit = props.initialStartUnit
|
||||||
loadAssetList()
|
loadAssetList()
|
||||||
} else {
|
return
|
||||||
resetForm()
|
|
||||||
}
|
}
|
||||||
|
resetForm()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="units.length" class="graphic-layout">
|
||||||
|
<div class="unit-grid">
|
||||||
|
<button
|
||||||
|
v-for="unit in sortedUnits"
|
||||||
|
:key="unit.unit_number"
|
||||||
|
type="button"
|
||||||
|
class="unit-tile"
|
||||||
|
:class="[unit.status, { selected: selectedUnitNumbers.has(unit.unit_number) }]"
|
||||||
|
:title="`U${unit.unit_number} · ${getTileText(unit)}`"
|
||||||
|
@click="emit('select', unit)"
|
||||||
|
>
|
||||||
|
<strong>U{{ unit.unit_number }}</strong>
|
||||||
|
<span>{{ getTileText(unit) }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside class="unit-detail">
|
||||||
|
<template v-if="selectedUnit">
|
||||||
|
<h3>U 位详情</h3>
|
||||||
|
<a-descriptions :column="1" size="small">
|
||||||
|
<a-descriptions-item label="U 位范围">U{{ selectedStartUnit }} - U{{ selectedEndUnit }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="状态">{{ statusMeta[selectedUnit.status].text }}</a-descriptions-item>
|
||||||
|
<template v-if="selectedUnit.status === 'occupied'">
|
||||||
|
<a-descriptions-item label="设备名称">{{ selectedUnit.asset_name }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="设备编号">{{ selectedUnit.asset_code }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="设备类型">{{ selectedUnit.asset_type }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="功耗">{{ selectedUnit.power_consumption }}W</a-descriptions-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="selectedUnit.status === 'reserved'">
|
||||||
|
<a-descriptions-item label="预留对象">{{ selectedUnit.reserved_for }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="截止时间">{{ selectedUnit.reserved_until }}</a-descriptions-item>
|
||||||
|
</template>
|
||||||
|
</a-descriptions>
|
||||||
|
|
||||||
|
<div class="detail-actions">
|
||||||
|
<template v-if="selectedUnit.status === 'available'">
|
||||||
|
<a-button type="primary" @click="emit('allocate', selectedUnit)">分配 U 位</a-button>
|
||||||
|
<a-button @click="emit('reserve', selectedUnit)">预留 U 位</a-button>
|
||||||
|
<a-button status="danger" @click="emit('disable', selectedUnit)">禁用</a-button>
|
||||||
|
</template>
|
||||||
|
<a-button v-else-if="selectedUnit.status === 'occupied'" status="danger" @click="emit('release', selectedUnit)">释放</a-button>
|
||||||
|
<a-button v-else-if="selectedUnit.status === 'reserved'" status="danger" @click="handleCancelReservation">取消预留</a-button>
|
||||||
|
<a-button v-else-if="selectedUnit.status === 'disabled'" @click="emit('enable', selectedUnit)">启用</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-empty v-else description="请选择 U 位" />
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
<a-empty v-else description="请先选择机柜" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import type { RackUnitItem, RackUnitStatus } from '@/api/ops/unit'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
units: RackUnitItem[]
|
||||||
|
selectedUnitNumber?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UnitRangeAction {
|
||||||
|
unit: RackUnitItem
|
||||||
|
startUnit: number
|
||||||
|
endUnit: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(event: 'select', unit: RackUnitItem): void
|
||||||
|
(event: 'allocate', unit: RackUnitItem): void
|
||||||
|
(event: 'reserve', unit: RackUnitItem): void
|
||||||
|
(event: 'disable', unit: RackUnitItem): void
|
||||||
|
(event: 'enable', unit: RackUnitItem): void
|
||||||
|
(event: 'release', unit: RackUnitItem): void
|
||||||
|
(event: 'cancel-reservation', payload: UnitRangeAction): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const statusMeta: Record<RackUnitStatus, { text: string; tileText: string }> = {
|
||||||
|
available: { text: '可用', tileText: '可用' },
|
||||||
|
occupied: { text: '已占用', tileText: '占用' },
|
||||||
|
reserved: { text: '已预留', tileText: '预留' },
|
||||||
|
disabled: { text: '已禁用', tileText: '禁用' },
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedUnits = computed(() => [...props.units].sort((left, right) => right.unit_number - left.unit_number))
|
||||||
|
const selectedUnit = computed(() => props.units.find((unit) => unit.unit_number === props.selectedUnitNumber))
|
||||||
|
|
||||||
|
const getTileText = (unit: RackUnitItem) => {
|
||||||
|
if (unit.status === 'occupied') return unit.asset_code
|
||||||
|
if (unit.status === 'reserved') return unit.reserved_for
|
||||||
|
return statusMeta[unit.status].tileText
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSameReservation = (left: RackUnitItem, right: RackUnitItem) => {
|
||||||
|
return (
|
||||||
|
left.status === 'reserved' &&
|
||||||
|
right.status === 'reserved' &&
|
||||||
|
left.reserved_for === right.reserved_for &&
|
||||||
|
left.reserved_until === right.reserved_until
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getReservedGroup = (unit: RackUnitItem) => {
|
||||||
|
const unitMap = new Map(props.units.map((item) => [item.unit_number, item]))
|
||||||
|
const result = [unit]
|
||||||
|
|
||||||
|
for (const direction of [-1, 1]) {
|
||||||
|
let unitNumber = unit.unit_number + direction
|
||||||
|
let candidate = unitMap.get(unitNumber)
|
||||||
|
while (candidate && isSameReservation(unit, candidate)) {
|
||||||
|
result.push(candidate)
|
||||||
|
unitNumber += direction
|
||||||
|
candidate = unitMap.get(unitNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedGroup = computed(() => {
|
||||||
|
const unit = selectedUnit.value
|
||||||
|
if (!unit) return []
|
||||||
|
if (unit.status === 'occupied') {
|
||||||
|
return props.units.filter((item) => item.status === 'occupied' && item.asset_id === unit.asset_id)
|
||||||
|
}
|
||||||
|
if (unit.status === 'reserved') return getReservedGroup(unit)
|
||||||
|
return [unit]
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedUnitNumbers = computed(() => new Set(selectedGroup.value.map((unit) => unit.unit_number)))
|
||||||
|
const selectedStartUnit = computed(() => Math.min(...selectedGroup.value.map((unit) => unit.unit_number)))
|
||||||
|
const selectedEndUnit = computed(() => Math.max(...selectedGroup.value.map((unit) => unit.unit_number)))
|
||||||
|
|
||||||
|
const handleCancelReservation = () => {
|
||||||
|
const unit = selectedUnit.value
|
||||||
|
if (!unit) return
|
||||||
|
emit('cancel-reservation', {
|
||||||
|
unit,
|
||||||
|
startUnit: selectedStartUnit.value,
|
||||||
|
endUnit: selectedEndUnit.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.graphic-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 300px;
|
||||||
|
min-height: 520px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, minmax(84px, 1fr));
|
||||||
|
align-content: start;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-tile {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-width: 0;
|
||||||
|
height: 58px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
span {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-tile.available {
|
||||||
|
color: #006d22;
|
||||||
|
border-color: #7be188;
|
||||||
|
background: #e8ffea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-tile.occupied {
|
||||||
|
color: #0e42d2;
|
||||||
|
border-color: #94bfff;
|
||||||
|
background: #e8f3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-tile.reserved {
|
||||||
|
color: #b54708;
|
||||||
|
border-color: #ffb65d;
|
||||||
|
background: #fff7e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-tile.disabled {
|
||||||
|
color: #4e5969;
|
||||||
|
border-color: #c9cdd4;
|
||||||
|
background: repeating-linear-gradient(135deg, #f2f3f5 0, #f2f3f5 6px, #e5e6eb 6px, #e5e6eb 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-tile.selected {
|
||||||
|
outline: 3px solid rgba(114, 46, 209, 0.72);
|
||||||
|
outline-offset: 1px;
|
||||||
|
box-shadow: 0 0 0 5px rgba(114, 46, 209, 0.11);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-detail {
|
||||||
|
padding: 20px;
|
||||||
|
border-left: 1px solid #e5e6eb;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -46,6 +46,7 @@ interface Props {
|
|||||||
visible: boolean
|
visible: boolean
|
||||||
rackId: number
|
rackId: number
|
||||||
rackHeight: number
|
rackHeight: number
|
||||||
|
initialStartUnit: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
@@ -129,10 +130,12 @@ const resetForm = () => {
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(val) => {
|
(visible) => {
|
||||||
if (!val) {
|
if (visible) {
|
||||||
resetForm()
|
formData.start_unit = props.initialStartUnit
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
resetForm()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<div class="info-label">使用率</div>
|
<div class="info-label">使用率</div>
|
||||||
<div class="info-value">
|
<div class="info-value">
|
||||||
<a-progress
|
<a-progress
|
||||||
:percent="usagePercentage"
|
:percent="usagePercentage / 100"
|
||||||
:size="'small'"
|
:size="'small'"
|
||||||
:color="usagePercentage > 80 ? '#f53f3f' : usagePercentage > 50 ? '#ff7d00' : '#00b42a'"
|
:color="usagePercentage > 80 ? '#f53f3f' : usagePercentage > 50 ? '#ff7d00' : '#00b42a'"
|
||||||
/>
|
/>
|
||||||
@@ -55,11 +55,23 @@
|
|||||||
<div class="info-value">{{ availableUnits }}</div>
|
<div class="info-value">{{ availableUnits }}</div>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">预留U位</div>
|
||||||
|
<div class="info-value">{{ reservedUnits }}</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">禁用U位</div>
|
||||||
|
<div class="info-value">{{ disabledUnits }}</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<div class="info-label">空余率</div>
|
<div class="info-label">空余率</div>
|
||||||
<div class="info-value">
|
<div class="info-value">
|
||||||
<a-progress :percent="availablePercentage" :size="'small'" :color="'#00b42a'" />
|
<a-progress :percent="availablePercentage / 100" :size="'small'" :color="'#00b42a'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -135,12 +147,17 @@
|
|||||||
<a-table-column title="操作" :width="200" fixed="right">
|
<a-table-column title="操作" :width="200" fixed="right">
|
||||||
<template #cell="{ record }">
|
<template #cell="{ record }">
|
||||||
<a-space size="small">
|
<a-space size="small">
|
||||||
<!-- 禁用/启用按钮(所有状态都显示) -->
|
<a-button v-if="record.status === 'available'" type="text" size="small" @click="handleDisable(record)">禁用</a-button>
|
||||||
<a-button v-if="record.status !== 'disabled'" type="text" size="small" @click="handleDisable(record)">禁用</a-button>
|
<a-button v-else-if="record.status === 'disabled'" type="text" size="small" @click="handleEnable(record)">启用</a-button>
|
||||||
<a-button v-else type="text" size="small" @click="handleEnable(record)">启用</a-button>
|
|
||||||
|
|
||||||
<!-- 已占用状态:显示释放按钮 -->
|
<!-- 已占用状态:显示释放按钮 -->
|
||||||
<a-button v-if="record.status === 'occupied'" type="text" size="small" status="danger" @click="handleRelease(record)">
|
<a-button
|
||||||
|
v-if="record.status === 'occupied' && record.asset_id && record.unit_number === record.asset?.unit_start"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
status="danger"
|
||||||
|
@click="handleRelease(record)"
|
||||||
|
>
|
||||||
释放
|
释放
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
||||||
@@ -162,10 +179,23 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<!-- 分配U位对话框 -->
|
<!-- 分配U位对话框 -->
|
||||||
<allocate-unit-dialog v-model:visible="allocateVisible" :rack-id="rackId" :rack-height="rackInfo.height" @success="handleRefresh" />
|
<allocate-unit-dialog
|
||||||
|
v-model:visible="allocateVisible"
|
||||||
|
:rack-id="rackId"
|
||||||
|
:rack-height="rackInfo.height"
|
||||||
|
:room-id="rackInfo.room_id"
|
||||||
|
:initial-start-unit="1"
|
||||||
|
@success="handleRefresh"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 预留U位对话框 -->
|
<!-- 预留U位对话框 -->
|
||||||
<reserve-unit-dialog v-model:visible="reserveVisible" :rack-id="rackId" :rack-height="rackInfo.height" @success="handleRefresh" />
|
<reserve-unit-dialog
|
||||||
|
v-model:visible="reserveVisible"
|
||||||
|
:rack-id="rackId"
|
||||||
|
:rack-height="rackInfo.height"
|
||||||
|
:initial-start-unit="1"
|
||||||
|
@success="handleRefresh"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -174,8 +204,8 @@ import { ref, reactive, computed, onMounted } from 'vue'
|
|||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { Message, Modal } from '@arco-design/web-vue'
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
import { IconLeft, IconStorage, IconApps, IconPlus, IconLock, IconRefresh } from '@arco-design/web-vue/es/icon'
|
import { IconLeft, IconStorage, IconApps, IconPlus, IconLock, IconRefresh } from '@arco-design/web-vue/es/icon'
|
||||||
import { fetchUnitList, allocateUnit, reserveUnit, cancelReservation, releaseUnit, updateUnitStatus } from '@/api/ops/unit'
|
import { fetchUnitList, reserveUnit, cancelReservation, unplaceAsset, updateUnitStatus } from '@/api/ops/unit'
|
||||||
import { normalizeUnitList } from './utils/unitFallback'
|
import { fetchRoomDetail } from '@/api/ops/room'
|
||||||
import AllocateUnitDialog from './components/AllocateUnitDialog.vue'
|
import AllocateUnitDialog from './components/AllocateUnitDialog.vue'
|
||||||
import ReserveUnitDialog from './components/ReserveUnitDialog.vue'
|
import ReserveUnitDialog from './components/ReserveUnitDialog.vue'
|
||||||
|
|
||||||
@@ -200,12 +230,15 @@ const pageTitle = computed(() => {
|
|||||||
|
|
||||||
// 已使用U位
|
// 已使用U位
|
||||||
const usedUnits = computed(() => {
|
const usedUnits = computed(() => {
|
||||||
return unitList.value.filter((unit) => unit.status === 'occupied' || unit.status === 'reserved').length
|
return unitList.value.filter((unit) => unit.status === 'occupied').length
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const reservedUnits = computed(() => unitList.value.filter((unit) => unit.status === 'reserved').length)
|
||||||
|
const disabledUnits = computed(() => unitList.value.filter((unit) => unit.status === 'disabled').length)
|
||||||
|
|
||||||
// 空余U位
|
// 空余U位
|
||||||
const availableUnits = computed(() => {
|
const availableUnits = computed(() => {
|
||||||
return rackInfo.value.height - usedUnits.value
|
return unitList.value.filter((unit) => unit.status === 'available').length
|
||||||
})
|
})
|
||||||
|
|
||||||
// 使用率
|
// 使用率
|
||||||
@@ -222,9 +255,7 @@ const availablePercentage = computed(() => {
|
|||||||
|
|
||||||
// 总功耗
|
// 总功耗
|
||||||
const totalPower = computed(() => {
|
const totalPower = computed(() => {
|
||||||
return unitList.value.reduce((total, unit) => {
|
return Math.round((Number(rackInfo.value.used_power) || 0) * 1000)
|
||||||
return total + (unit.power_consumption || 0)
|
|
||||||
}, 0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取机柜状态颜色
|
// 获取机柜状态颜色
|
||||||
@@ -281,9 +312,8 @@ const fetchUnits = async () => {
|
|||||||
const res = await fetchUnitList(rackId.value)
|
const res = await fetchUnitList(rackId.value)
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const payload = res?.details ?? res?.data ?? {}
|
rackInfo.value = res.details.rack
|
||||||
rackInfo.value = payload?.rack || {}
|
unitList.value = res.details.units
|
||||||
unitList.value = normalizeUnitList(payload?.units, rackInfo.value?.height)
|
|
||||||
} else {
|
} else {
|
||||||
Message.error(res.message || '获取U位列表失败')
|
Message.error(res.message || '获取U位列表失败')
|
||||||
}
|
}
|
||||||
@@ -377,13 +407,18 @@ const handleRelease = async (record: any) => {
|
|||||||
title: '确认释放',
|
title: '确认释放',
|
||||||
content: `确认释放 U位 ${record.unit_number} 吗?`,
|
content: `确认释放 U位 ${record.unit_number} 吗?`,
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
const endUnit = record.occupied_units > 1 ? record.unit_number + record.occupied_units - 1 : record.unit_number
|
if (!rackInfo.value.room_id || !record.asset_id) {
|
||||||
|
Message.error('机房或资产信息不完整')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const res = await releaseUnit({
|
const roomRes = await fetchRoomDetail(rackInfo.value.room_id)
|
||||||
rack_id: rackId.value,
|
if (roomRes.code !== 0) {
|
||||||
start_unit: record.unit_number,
|
Message.error(roomRes.message || '获取机房布局版本失败')
|
||||||
end_unit: endUnit,
|
return
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const res = await unplaceAsset(rackInfo.value.room_id, record.asset_id, roomRes.details.layout_version)
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
Message.success('释放成功')
|
Message.success('释放成功')
|
||||||
|
|||||||
@@ -72,6 +72,8 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<a-tag v-if="rackInfo.height">总U位: {{ rackInfo.height }}</a-tag>
|
<a-tag v-if="rackInfo.height">总U位: {{ rackInfo.height }}</a-tag>
|
||||||
<a-tag v-if="usedUnits" color="blue">已使用: {{ usedUnits }}</a-tag>
|
<a-tag v-if="usedUnits" color="blue">已使用: {{ usedUnits }}</a-tag>
|
||||||
|
<a-tag v-if="reservedUnits" color="orange">已预留: {{ reservedUnits }}</a-tag>
|
||||||
|
<a-tag v-if="disabledUnits" color="gray">已禁用: {{ disabledUnits }}</a-tag>
|
||||||
<a-tag v-if="availableUnits" color="green">空余: {{ availableUnits }}</a-tag>
|
<a-tag v-if="availableUnits" color="green">空余: {{ availableUnits }}</a-tag>
|
||||||
<a-tag v-if="usagePercentage" :color="usagePercentage > 80 ? 'red' : 'orange'">使用率: {{ usagePercentage }}%</a-tag>
|
<a-tag v-if="usagePercentage" :color="usagePercentage > 80 ? 'red' : 'orange'">使用率: {{ usagePercentage }}%</a-tag>
|
||||||
</a-space>
|
</a-space>
|
||||||
@@ -100,6 +102,14 @@
|
|||||||
</template>
|
</template>
|
||||||
刷新
|
刷新
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<a-divider direction="vertical" />
|
||||||
|
<a-button type="outline" @click="handleToggleView">
|
||||||
|
<template #icon>
|
||||||
|
<icon-apps v-if="viewMode === 'list'" />
|
||||||
|
<icon-list v-else />
|
||||||
|
</template>
|
||||||
|
{{ viewMode === 'list' ? '图形视图' : '列表视图' }}
|
||||||
|
</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
@@ -107,10 +117,23 @@
|
|||||||
<a-card class="u-position-card" :loading="loading">
|
<a-card class="u-position-card" :loading="loading">
|
||||||
<template #title>
|
<template #title>
|
||||||
<icon-apps />
|
<icon-apps />
|
||||||
U位列表
|
{{ viewMode === 'list' ? 'U位列表' : 'U位图形视图' }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<a-table :data="unitList" :pagination="false" :bordered="{ cell: true }" :scroll="{ x: 1400 }" :loading="loading" size="small">
|
<rack-unit-graphic
|
||||||
|
v-if="viewMode === 'graphic'"
|
||||||
|
:units="unitList"
|
||||||
|
:selected-unit-number="selectedUnitNumber"
|
||||||
|
@select="handleGraphicSelect"
|
||||||
|
@allocate="openAllocateDialog"
|
||||||
|
@reserve="openReserveDialog"
|
||||||
|
@disable="handleDisable"
|
||||||
|
@enable="handleEnable"
|
||||||
|
@release="handleRelease"
|
||||||
|
@cancel-reservation="handleGraphicCancelReservation"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a-table v-else :data="unitList" :pagination="false" :bordered="{ cell: true }" :scroll="{ x: 1400 }" :loading="loading" size="small">
|
||||||
<template #columns>
|
<template #columns>
|
||||||
<!-- <a-table-column title="序号" :width="80">
|
<!-- <a-table-column title="序号" :width="80">
|
||||||
<template #cell="{ rowIndex }">
|
<template #cell="{ rowIndex }">
|
||||||
@@ -134,12 +157,17 @@
|
|||||||
<a-table-column title="操作" :width="200" fixed="right">
|
<a-table-column title="操作" :width="200" fixed="right">
|
||||||
<template #cell="{ record }">
|
<template #cell="{ record }">
|
||||||
<a-space size="small">
|
<a-space size="small">
|
||||||
<!-- 禁用/启用按钮(所有状态都显示) -->
|
<a-button v-if="record.status === 'available'" type="text" size="small" @click="handleDisable(record)">禁用</a-button>
|
||||||
<a-button v-if="record.status !== 'disabled'" type="text" size="small" @click="handleDisable(record)">禁用</a-button>
|
<a-button v-else-if="record.status === 'disabled'" type="text" size="small" @click="handleEnable(record)">启用</a-button>
|
||||||
<a-button v-else type="text" size="small" @click="handleEnable(record)">启用</a-button>
|
|
||||||
|
|
||||||
<!-- 已占用状态:显示释放按钮 -->
|
<!-- 已占用状态:显示释放按钮 -->
|
||||||
<a-button v-if="record.status === 'occupied'" type="text" size="small" status="danger" @click="handleRelease(record)">
|
<a-button
|
||||||
|
v-if="record.status === 'occupied' && record.asset_id && record.unit_number === record.asset?.unit_start"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
status="danger"
|
||||||
|
@click="handleRelease(record)"
|
||||||
|
>
|
||||||
释放
|
释放
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
||||||
@@ -162,17 +190,22 @@
|
|||||||
|
|
||||||
<!-- 分配U位对话框 -->
|
<!-- 分配U位对话框 -->
|
||||||
<allocate-unit-dialog
|
<allocate-unit-dialog
|
||||||
|
v-if="selectedRackId && rackInfo.height"
|
||||||
v-model:visible="allocateVisible"
|
v-model:visible="allocateVisible"
|
||||||
:rack-id="selectedRackId!"
|
:rack-id="selectedRackId"
|
||||||
:rack-height="rackInfo.height"
|
:rack-height="rackInfo.height"
|
||||||
|
:room-id="rackInfo.room_id"
|
||||||
|
:initial-start-unit="dialogStartUnit"
|
||||||
@success="handleRefresh"
|
@success="handleRefresh"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 预留U位对话框 -->
|
<!-- 预留U位对话框 -->
|
||||||
<reserve-unit-dialog
|
<reserve-unit-dialog
|
||||||
|
v-if="selectedRackId && rackInfo.height"
|
||||||
v-model:visible="reserveVisible"
|
v-model:visible="reserveVisible"
|
||||||
:rack-id="selectedRackId!"
|
:rack-id="selectedRackId"
|
||||||
:rack-height="rackInfo.height"
|
:rack-height="rackInfo.height"
|
||||||
|
:initial-start-unit="dialogStartUnit"
|
||||||
@success="handleRefresh"
|
@success="handleRefresh"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -181,16 +214,19 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
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, IconList, IconPlus, IconLock, IconRefresh } from '@arco-design/web-vue/es/icon'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { fetchUnitList, allocateUnit, reserveUnit, cancelReservation, releaseUnit, updateUnitStatus } from '@/api/ops/unit'
|
import { fetchUnitList, cancelReservation, unplaceAsset, updateUnitStatus } from '@/api/ops/unit'
|
||||||
|
import type { RackUnitItem, RackUnitRackInfo } 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'
|
||||||
import { fetchRoomListByFloor } from '@/api/ops/room'
|
import { fetchRoomDetail, fetchRoomListByFloor } from '@/api/ops/room'
|
||||||
import { normalizeUnitList } from './utils/unitFallback'
|
|
||||||
import AllocateUnitDialog from './components/AllocateUnitDialog.vue'
|
import AllocateUnitDialog from './components/AllocateUnitDialog.vue'
|
||||||
|
import RackUnitGraphic from './components/RackUnitGraphic.vue'
|
||||||
import ReserveUnitDialog from './components/ReserveUnitDialog.vue'
|
import ReserveUnitDialog from './components/ReserveUnitDialog.vue'
|
||||||
|
|
||||||
|
type ViewMode = 'list' | 'graphic'
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const rackListLoading = ref(false)
|
const rackListLoading = ref(false)
|
||||||
@@ -201,30 +237,42 @@ const selectedDatacenterId = ref<number | undefined>(undefined)
|
|||||||
const selectedFloorId = ref<number | undefined>(undefined)
|
const selectedFloorId = ref<number | undefined>(undefined)
|
||||||
const selectedRoomId = ref<number | undefined>(undefined)
|
const selectedRoomId = ref<number | undefined>(undefined)
|
||||||
const selectedRackId = ref<number | undefined>(undefined)
|
const selectedRackId = ref<number | undefined>(undefined)
|
||||||
const rackInfo = ref<any>({})
|
const viewMode = ref<ViewMode>('list')
|
||||||
|
const selectedUnitNumber = ref<number>()
|
||||||
|
const dialogStartUnit = ref(1)
|
||||||
|
const rackInfo = ref<Partial<RackUnitRackInfo>>({})
|
||||||
const datacenterList = ref<{ label: string; value: number }[]>([])
|
const datacenterList = ref<{ label: string; value: number }[]>([])
|
||||||
const floorList = ref<{ label: string; value: number }[]>([])
|
const floorList = ref<{ label: string; value: number }[]>([])
|
||||||
const roomList = ref<any[]>([])
|
const roomList = ref<any[]>([])
|
||||||
const rackList = ref<any[]>([])
|
const rackList = ref<any[]>([])
|
||||||
const unitList = ref<any[]>([])
|
const unitList = ref<RackUnitItem[]>([])
|
||||||
let datacenterSearchTimer: number | undefined
|
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
|
||||||
|
let unitRequestSequence = 0
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
const invalidateUnitRequest = () => {
|
||||||
|
unitRequestSequence += 1
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
// 对话框可见性
|
// 对话框可见性
|
||||||
const allocateVisible = ref(false)
|
const allocateVisible = ref(false)
|
||||||
const reserveVisible = ref(false)
|
const reserveVisible = ref(false)
|
||||||
|
|
||||||
// 已使用U位
|
// 已使用U位
|
||||||
const usedUnits = computed(() => {
|
const usedUnits = computed(() => {
|
||||||
return unitList.value.filter((unit) => unit.status === 'occupied' || unit.status === 'reserved').length
|
return unitList.value.filter((unit) => unit.status === 'occupied').length
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const reservedUnits = computed(() => unitList.value.filter((unit) => unit.status === 'reserved').length)
|
||||||
|
const disabledUnits = computed(() => unitList.value.filter((unit) => unit.status === 'disabled').length)
|
||||||
|
|
||||||
// 空余U位
|
// 空余U位
|
||||||
const availableUnits = computed(() => {
|
const availableUnits = computed(() => {
|
||||||
return rackInfo.value.height - usedUnits.value
|
return unitList.value.filter((unit) => unit.status === 'available').length
|
||||||
})
|
})
|
||||||
|
|
||||||
// 使用率
|
// 使用率
|
||||||
@@ -233,11 +281,6 @@ const usagePercentage = computed(() => {
|
|||||||
return Math.round((usedUnits.value / rackInfo.value.height) * 100)
|
return Math.round((usedUnits.value / rackInfo.value.height) * 100)
|
||||||
})
|
})
|
||||||
|
|
||||||
const extractList = (res: any): any[] => {
|
|
||||||
const candidate = res?.data?.data ?? res?.details?.data ?? res?.data ?? res?.details ?? []
|
|
||||||
return Array.isArray(candidate) ? candidate : []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取U位状态颜色
|
// 获取U位状态颜色
|
||||||
const getUnitStatusColor = (status?: string) => {
|
const getUnitStatusColor = (status?: string) => {
|
||||||
const colorMap: Record<string, string> = {
|
const colorMap: Record<string, string> = {
|
||||||
@@ -270,7 +313,7 @@ const fetchRacks = async (keyword?: string) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const res: any = await fetchRackListByRoom(selectedRoomId.value, { name: keyword })
|
const res: any = await fetchRackListByRoom(selectedRoomId.value, { name: keyword })
|
||||||
rackList.value = extractList(res)
|
rackList.value = res.details
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取机柜列表失败:', error)
|
console.error('获取机柜列表失败:', error)
|
||||||
Message.error('获取机柜列表失败')
|
Message.error('获取机柜列表失败')
|
||||||
@@ -284,7 +327,7 @@ const fetchDatacenters = async (keyword?: string) => {
|
|||||||
datacenterListLoading.value = true
|
datacenterListLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res: any = await fetchDatacenterList({ keyword })
|
const res: any = await fetchDatacenterList({ keyword })
|
||||||
const rows = extractList(res)
|
const rows = res.details
|
||||||
datacenterList.value = rows.map((d: any) => ({
|
datacenterList.value = rows.map((d: any) => ({
|
||||||
label: d.name || d.code || String(d.id),
|
label: d.name || d.code || String(d.id),
|
||||||
value: d.id,
|
value: d.id,
|
||||||
@@ -308,7 +351,7 @@ const fetchFloors = async (keyword?: string) => {
|
|||||||
const res: any = await fetchFloorListByDatacenter(selectedDatacenterId.value, {
|
const res: any = await fetchFloorListByDatacenter(selectedDatacenterId.value, {
|
||||||
name: keyword || undefined,
|
name: keyword || undefined,
|
||||||
})
|
})
|
||||||
const rows = extractList(res)
|
const rows = res.details
|
||||||
floorList.value = rows.map((floor: any) => ({
|
floorList.value = rows.map((floor: any) => ({
|
||||||
label: floor.name || floor.code || String(floor.id),
|
label: floor.name || floor.code || String(floor.id),
|
||||||
value: floor.id,
|
value: floor.id,
|
||||||
@@ -332,7 +375,7 @@ const fetchRooms = async (keyword?: string) => {
|
|||||||
const res: any = await fetchRoomListByFloor(selectedFloorId.value, {
|
const res: any = await fetchRoomListByFloor(selectedFloorId.value, {
|
||||||
name: keyword || undefined,
|
name: keyword || undefined,
|
||||||
})
|
})
|
||||||
roomList.value = extractList(res)
|
roomList.value = res.details
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取机房列表失败:', error)
|
console.error('获取机房列表失败:', error)
|
||||||
Message.error('获取机房列表失败')
|
Message.error('获取机房列表失败')
|
||||||
@@ -385,6 +428,7 @@ const handleDatacenterChange = async (datacenterId?: number | string) => {
|
|||||||
if (datacenterId !== undefined && datacenterId !== null && datacenterId !== '') {
|
if (datacenterId !== undefined && datacenterId !== null && datacenterId !== '') {
|
||||||
selectedDatacenterId.value = Number(datacenterId)
|
selectedDatacenterId.value = Number(datacenterId)
|
||||||
}
|
}
|
||||||
|
invalidateUnitRequest()
|
||||||
selectedFloorId.value = undefined
|
selectedFloorId.value = undefined
|
||||||
selectedRoomId.value = undefined
|
selectedRoomId.value = undefined
|
||||||
selectedRackId.value = undefined
|
selectedRackId.value = undefined
|
||||||
@@ -393,6 +437,7 @@ const handleDatacenterChange = async (datacenterId?: number | string) => {
|
|||||||
roomList.value = []
|
roomList.value = []
|
||||||
floorList.value = []
|
floorList.value = []
|
||||||
unitList.value = []
|
unitList.value = []
|
||||||
|
selectedUnitNumber.value = undefined
|
||||||
await fetchFloors()
|
await fetchFloors()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,12 +445,14 @@ const handleFloorChange = async (floorId?: number | string) => {
|
|||||||
if (floorId !== undefined && floorId !== null && floorId !== '') {
|
if (floorId !== undefined && floorId !== null && floorId !== '') {
|
||||||
selectedFloorId.value = Number(floorId)
|
selectedFloorId.value = Number(floorId)
|
||||||
}
|
}
|
||||||
|
invalidateUnitRequest()
|
||||||
selectedRoomId.value = undefined
|
selectedRoomId.value = undefined
|
||||||
selectedRackId.value = undefined
|
selectedRackId.value = undefined
|
||||||
rackInfo.value = {}
|
rackInfo.value = {}
|
||||||
rackList.value = []
|
rackList.value = []
|
||||||
roomList.value = []
|
roomList.value = []
|
||||||
unitList.value = []
|
unitList.value = []
|
||||||
|
selectedUnitNumber.value = undefined
|
||||||
await fetchRooms()
|
await fetchRooms()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,15 +460,19 @@ const handleRoomChange = async (roomId?: number | string) => {
|
|||||||
if (roomId !== undefined && roomId !== null && roomId !== '') {
|
if (roomId !== undefined && roomId !== null && roomId !== '') {
|
||||||
selectedRoomId.value = Number(roomId)
|
selectedRoomId.value = Number(roomId)
|
||||||
}
|
}
|
||||||
|
invalidateUnitRequest()
|
||||||
selectedRackId.value = undefined
|
selectedRackId.value = undefined
|
||||||
rackInfo.value = {}
|
rackInfo.value = {}
|
||||||
rackList.value = []
|
rackList.value = []
|
||||||
unitList.value = []
|
unitList.value = []
|
||||||
|
selectedUnitNumber.value = undefined
|
||||||
await fetchRacks()
|
await fetchRacks()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 机柜变化
|
// 机柜变化
|
||||||
const handleRackChange = (rackId: number) => {
|
const handleRackChange = (rackId: number) => {
|
||||||
|
invalidateUnitRequest()
|
||||||
|
selectedUnitNumber.value = undefined
|
||||||
if (rackId) {
|
if (rackId) {
|
||||||
fetchUnits(rackId)
|
fetchUnits(rackId)
|
||||||
} else {
|
} else {
|
||||||
@@ -436,25 +487,36 @@ const fetchUnits = async (rackId?: number) => {
|
|||||||
|
|
||||||
if (!targetRackId) return
|
if (!targetRackId) return
|
||||||
|
|
||||||
|
const requestSequence = ++unitRequestSequence
|
||||||
|
const isCurrentRequest = () => requestSequence === unitRequestSequence && targetRackId === selectedRackId.value
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetchUnitList(targetRackId)
|
const res = await fetchUnitList(targetRackId)
|
||||||
|
|
||||||
|
if (!isCurrentRequest()) return
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const payload = res?.details ?? res?.data ?? {}
|
rackInfo.value = res.details.rack
|
||||||
rackInfo.value = payload?.rack || {}
|
unitList.value = res.details.units
|
||||||
unitList.value = normalizeUnitList(payload?.units, rackInfo.value?.height)
|
|
||||||
|
if (selectedUnitNumber.value !== undefined && !unitList.value.some((unit) => unit.unit_number === selectedUnitNumber.value)) {
|
||||||
|
selectedUnitNumber.value = undefined
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Message.error(res.message || '获取U位列表失败')
|
Message.error(res.message || '获取U位列表失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (!isCurrentRequest()) return
|
||||||
console.error('获取U位列表失败:', error)
|
console.error('获取U位列表失败:', error)
|
||||||
Message.error('获取U位列表失败')
|
Message.error('获取U位列表失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
if (isCurrentRequest()) {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 刷新
|
// 刷新
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
@@ -464,12 +526,31 @@ const handleRefresh = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleToggleView = () => {
|
||||||
|
viewMode.value = viewMode.value === 'list' ? 'graphic' : 'list'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGraphicSelect = (unit: RackUnitItem) => {
|
||||||
|
selectedUnitNumber.value = unit.unit_number
|
||||||
|
}
|
||||||
|
|
||||||
|
const openAllocateDialog = (unit: RackUnitItem) => {
|
||||||
|
dialogStartUnit.value = unit.unit_number
|
||||||
|
allocateVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const openReserveDialog = (unit: RackUnitItem) => {
|
||||||
|
dialogStartUnit.value = unit.unit_number
|
||||||
|
reserveVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 分配U位
|
// 分配U位
|
||||||
const handleAllocate = () => {
|
const handleAllocate = () => {
|
||||||
if (!selectedRackId.value) {
|
if (!selectedRackId.value) {
|
||||||
Message.warning('请先选择机柜')
|
Message.warning('请先选择机柜')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
dialogStartUnit.value = 1
|
||||||
allocateVisible.value = true
|
allocateVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,6 +560,7 @@ const handleReserve = () => {
|
|||||||
Message.warning('请先选择机柜')
|
Message.warning('请先选择机柜')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
dialogStartUnit.value = 1
|
||||||
reserveVisible.value = true
|
reserveVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,12 +570,13 @@ const handleDisable = async (record: any) => {
|
|||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认禁用',
|
title: '确认禁用',
|
||||||
content: `确认禁用 U位 ${record.unit_number} 吗?`,
|
content: `确认禁用 U位 ${record.unit_number} 吗?`,
|
||||||
onOk: async () => {
|
onBeforeOk: async () => {
|
||||||
if (!selectedRackId.value) {
|
if (!selectedRackId.value) {
|
||||||
Message.warning('请先选择机柜')
|
Message.warning('请先选择机柜')
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const res = await updateUnitStatus({
|
const res = await updateUnitStatus({
|
||||||
rack_id: selectedRackId.value,
|
rack_id: selectedRackId.value,
|
||||||
start_unit: record.unit_number,
|
start_unit: record.unit_number,
|
||||||
@@ -503,9 +586,15 @@ const handleDisable = async (record: any) => {
|
|||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
Message.success('禁用成功')
|
Message.success('禁用成功')
|
||||||
fetchUnits()
|
await fetchUnits()
|
||||||
} else {
|
return true
|
||||||
|
}
|
||||||
Message.error(res.message || '禁用失败')
|
Message.error(res.message || '禁用失败')
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('禁用U位失败:', error)
|
||||||
|
Message.error('禁用失败')
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -520,12 +609,13 @@ const handleEnable = async (record: any) => {
|
|||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认启用',
|
title: '确认启用',
|
||||||
content: `确认启用 U位 ${record.unit_number} 吗?`,
|
content: `确认启用 U位 ${record.unit_number} 吗?`,
|
||||||
onOk: async () => {
|
onBeforeOk: async () => {
|
||||||
if (!selectedRackId.value) {
|
if (!selectedRackId.value) {
|
||||||
Message.warning('请先选择机柜')
|
Message.warning('请先选择机柜')
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const res = await updateUnitStatus({
|
const res = await updateUnitStatus({
|
||||||
rack_id: selectedRackId.value,
|
rack_id: selectedRackId.value,
|
||||||
start_unit: record.unit_number,
|
start_unit: record.unit_number,
|
||||||
@@ -535,9 +625,15 @@ const handleEnable = async (record: any) => {
|
|||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
Message.success('启用成功')
|
Message.success('启用成功')
|
||||||
fetchUnits()
|
await fetchUnits()
|
||||||
} else {
|
return true
|
||||||
|
}
|
||||||
Message.error(res.message || '启用失败')
|
Message.error(res.message || '启用失败')
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('启用U位失败:', error)
|
||||||
|
Message.error('启用失败')
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -548,29 +644,43 @@ const handleEnable = async (record: any) => {
|
|||||||
|
|
||||||
// 释放U位
|
// 释放U位
|
||||||
const handleRelease = async (record: any) => {
|
const handleRelease = async (record: any) => {
|
||||||
|
const occupiedUnitNumbers = unitList.value
|
||||||
|
.filter((unit) => unit.status === 'occupied' && unit.asset_id === record.asset_id)
|
||||||
|
.map((unit) => unit.unit_number)
|
||||||
|
const startUnit = Math.min(...occupiedUnitNumbers)
|
||||||
|
const endUnit = Math.max(...occupiedUnitNumbers)
|
||||||
|
const unitRange = startUnit === endUnit ? `U${startUnit}` : `U${startUnit}-U${endUnit}`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认释放',
|
title: '确认释放',
|
||||||
content: `确认释放 U位 ${record.unit_number} 吗?`,
|
content: `确认释放设备 ${record.asset_name}(${unitRange})吗?`,
|
||||||
onOk: async () => {
|
onBeforeOk: async () => {
|
||||||
const endUnit = record.occupied_units > 1 ? record.unit_number + record.occupied_units - 1 : record.unit_number
|
if (!rackInfo.value.room_id || !record.asset_id) {
|
||||||
|
Message.error('机房或资产信息不完整')
|
||||||
if (!selectedRackId.value) {
|
return false
|
||||||
Message.warning('请先选择机柜')
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await releaseUnit({
|
try {
|
||||||
rack_id: selectedRackId.value,
|
const roomRes = await fetchRoomDetail(rackInfo.value.room_id)
|
||||||
start_unit: record.unit_number,
|
if (roomRes.code !== 0) {
|
||||||
end_unit: endUnit,
|
Message.error(roomRes.message || '获取机房布局版本失败')
|
||||||
})
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await unplaceAsset(rackInfo.value.room_id, record.asset_id, roomRes.details.layout_version)
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
Message.success('释放成功')
|
Message.success('释放成功')
|
||||||
fetchUnits()
|
await fetchUnits()
|
||||||
} else {
|
return true
|
||||||
|
}
|
||||||
Message.error(res.message || '释放失败')
|
Message.error(res.message || '释放失败')
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('释放U位失败:', error)
|
||||||
|
Message.error('释放失败')
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -579,37 +689,47 @@ const handleRelease = async (record: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消预留
|
const cancelReservationRange = (startUnit: number, endUnit: number) => {
|
||||||
const handleCancelReservation = async (record: any) => {
|
|
||||||
try {
|
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认取消预留',
|
title: '确认取消预留',
|
||||||
content: `确认取消 U位 ${record.unit_number} 的预留吗?`,
|
content: `确认取消 U位 ${startUnit}${endUnit > startUnit ? ` 至 ${endUnit}` : ''} 的预留吗?`,
|
||||||
onOk: async () => {
|
onBeforeOk: async () => {
|
||||||
const endUnit = record.occupied_units > 1 ? record.unit_number + record.occupied_units - 1 : record.unit_number
|
|
||||||
|
|
||||||
if (!selectedRackId.value) {
|
if (!selectedRackId.value) {
|
||||||
Message.warning('请先选择机柜')
|
Message.warning('请先选择机柜')
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const res = await cancelReservation({
|
const res = await cancelReservation({
|
||||||
rack_id: selectedRackId.value,
|
rack_id: selectedRackId.value,
|
||||||
start_unit: record.unit_number,
|
start_unit: startUnit,
|
||||||
end_unit: endUnit,
|
end_unit: endUnit,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
Message.success('取消预留成功')
|
Message.success('取消预留成功')
|
||||||
fetchUnits()
|
await fetchUnits()
|
||||||
} else {
|
return true
|
||||||
|
}
|
||||||
Message.error(res.message || '取消预留失败')
|
Message.error(res.message || '取消预留失败')
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('取消预留失败:', error)
|
||||||
|
Message.error('取消预留失败')
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
|
||||||
console.error('取消预留失败:', error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 取消预留
|
||||||
|
const handleCancelReservation = (record: RackUnitItem) => {
|
||||||
|
const endUnit = record.unit_number + record.occupied_units - 1
|
||||||
|
cancelReservationRange(record.unit_number, endUnit)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGraphicCancelReservation = (payload: { unit: RackUnitItem; startUnit: number; endUnit: number }) => {
|
||||||
|
cancelReservationRange(payload.startUnit, payload.endUnit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
|
|||||||
1289
src/views/ops/pages/datacenter/u-position/prototype-preview.html
Normal file
1289
src/views/ops/pages/datacenter/u-position/prototype-preview.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,22 +0,0 @@
|
|||||||
export const normalizeUnitList = (units: any, rackHeight?: number) => {
|
|
||||||
if (Array.isArray(units) && units.length > 0) {
|
|
||||||
return units
|
|
||||||
}
|
|
||||||
|
|
||||||
const height = Number(rackHeight) || 0
|
|
||||||
if (height <= 0) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from({ length: height }, (_, idx) => ({
|
|
||||||
id: `virtual-${height - idx}`,
|
|
||||||
unit_number: height - idx,
|
|
||||||
status: 'available',
|
|
||||||
asset_name: '',
|
|
||||||
asset_code: '',
|
|
||||||
asset_type: '',
|
|
||||||
occupied_units: 1,
|
|
||||||
reserved_for: '',
|
|
||||||
power_consumption: 0,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
@@ -97,18 +97,12 @@ let datacenterSearchTimer: number | undefined
|
|||||||
let floorSearchTimer: number | undefined
|
let floorSearchTimer: number | undefined
|
||||||
let rackSearchTimer: number | undefined
|
let rackSearchTimer: number | undefined
|
||||||
|
|
||||||
// 提取列表数据
|
|
||||||
const extractList = (res: any): any[] => {
|
|
||||||
const candidate = res?.data?.data ?? res?.details?.data ?? res?.data ?? res?.details ?? []
|
|
||||||
return Array.isArray(candidate) ? candidate : []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取数据中心列表
|
// 获取数据中心列表
|
||||||
const fetchDatacenters = async (keyword?: string) => {
|
const fetchDatacenters = async (keyword?: string) => {
|
||||||
datacenterListLoading.value = true
|
datacenterListLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res: any = await fetchDatacenterList({ keyword })
|
const res: any = await fetchDatacenterList({ keyword })
|
||||||
const rows = extractList(res)
|
const rows = res.details
|
||||||
datacenterList.value = rows.map((d: any) => ({
|
datacenterList.value = rows.map((d: any) => ({
|
||||||
label: d.name || d.code || String(d.id),
|
label: d.name || d.code || String(d.id),
|
||||||
value: d.id,
|
value: d.id,
|
||||||
@@ -133,7 +127,7 @@ const fetchFloors = async (keyword?: string) => {
|
|||||||
const res: any = await fetchFloorListByDatacenter(selectedDatacenterId.value, {
|
const res: any = await fetchFloorListByDatacenter(selectedDatacenterId.value, {
|
||||||
name: keyword || undefined,
|
name: keyword || undefined,
|
||||||
})
|
})
|
||||||
const rows = extractList(res)
|
const rows = res.details
|
||||||
floorList.value = rows.map((floor: any) => ({
|
floorList.value = rows.map((floor: any) => ({
|
||||||
label: floor.name || floor.code || String(floor.id),
|
label: floor.name || floor.code || String(floor.id),
|
||||||
value: floor.id,
|
value: floor.id,
|
||||||
@@ -155,8 +149,8 @@ const fetchRacks = async (keyword?: string) => {
|
|||||||
}
|
}
|
||||||
rackListLoading.value = true
|
rackListLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res: any = await fetchRackListByFloor(selectedFloorId.value, { name: keyword })
|
const res: any = await fetchRackListByFloor(selectedFloorId.value, { keyword })
|
||||||
rackList.value = extractList(res)
|
rackList.value = res.details.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取机柜列表失败:', error)
|
console.error('获取机柜列表失败:', error)
|
||||||
Message.error('获取机柜列表失败')
|
Message.error('获取机柜列表失败')
|
||||||
|
|||||||
Reference in New Issue
Block a user