<template>
<div class="asset-overview-container container">
<div class="title">
{{ t('Overview') }}
</div>
<div class="asset-overview-body">
<div class="body-item">
<div class="body-item-top">
<div class="top-icon"></div>
<div class="top-body-comp">
<div class="asset-type-title">
{{ t('设备总数') }}
</div>
<div class="asset-number-title">
{{ handelNumber(props.assetOverviewData.equipmenttotalcount) }}
</div>
</div>
</div>
<div class="body-item-bottom">
<div class="body-item-bottom-online">
<div class="online-icon-title">
<div class="online-icon"></div>
<div class="online-title">
{{ t('在线') }}
</div>
</div>
<div class="online-number">
{{ handelNumber(props.assetOverviewData.onlineequipmenttotalcount) }}
</div>
</div>
<div class="body-item-bottom-online">
<div class="online-icon-title">
<div class="online-icon offline-icon"></div>
<div class="online-title">
{{ t('离线') }}
</div>
</div>
<div class="online-number underline" @click="showDialog('equipment')">
{{ handelNumber(props.assetOverviewData.offlineequipmenttotalcount) }}
</div>
</div>
</div>
</div>
<div class="body-item">
<div class="body-item-top">
<div class="top-icon fintting-icon"></div>
<div class="top-body-comp">
<div class="asset-type-title">
{{ t('配件总数') }}
</div>
<div class="asset-number-title">
{{ handelNumber(props.assetOverviewData.componenttotalcount) }}
</div>
</div>
</div>
<div class="body-item-bottom">
<div class="body-item-bottom-online">
<div class="online-icon-title">
<div class="online-icon"></div>
<div class="online-title">
{{ t('在线') }}
</div>
</div>
<div class="online-number">
{{ handelNumber(props.assetOverviewData.onlinecomponenttotalcount) }}
</div>
</div>
<div class="body-item-bottom-online">
<div class="online-icon-title">
<div class="online-icon offline-icon"></div>
<div class="online-title">
{{ t('离线') }}
</div>
</div>
<div class="online-number underline" @click="showDialog('accessory')">
{{ handelNumber(props.assetOverviewData.offlinecomponenttotalcount) }}
</div>
</div>
</div>
</div>
</div>
</div>
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="80%"
top="10vh"
:before-close="handleClose"
>
<!-- 添加按钮 -->
<div class="table-toolbar">
<img src="@/assets/images/setup.png" alt="" @click="openColumnSelection" />
</div>
<el-table v-loading="loading" :data="tableData" border :max-height="tableMaxHeight">
<el-table-column
:label="t('序号')"
type="index"
:index="indexMethod"
width="100"
fixed
></el-table-column>
<el-table-column
v-for="(column, index) in visibleColumns"
:key="index"
:label="t(column.title)"
:prop="column.field"
:width="column.width"
show-overflow-tooltip
>
<template v-if="column.title === t('入库时间')" #default="scope">
{{
scope?.row?.inventoryTime
? dayjs(scope?.row?.inventoryTime).format('YYYY-MM-DD HH:mm:ss')
: '--'
}}
</template>
<template v-else-if="column.title === t('盘点时间')" #default="scope">
{{
scope?.row?.countingTime
? dayjs(scope?.row?.countingTime).format('YYYY-MM-DD HH:mm:ss')
: '--'
}}
</template>
<template v-else #default="scope">
{{
[null, undefined, ''].includes(scope.row[column.field]) ? '--' : scope.row[column.field]
}}
</template>
</el-table-column>
</el-table>
<div class="table-page">
<Pagination
layout="total, sizes, prev, pager, next"
:pageSizes="[5, 10, 15, 20, 50]"
:total="page.total"
:currentPage="page.pageNo"
:pageSize="page.pageSize"
@changePage="tableChange"
></Pagination>
</div>
</el-dialog>
<!-- 列选择对话框 -->
<el-dialog
v-model="columnDialogVisible"
:title="t('列表字段筛选')"
width="38%"
@close="onColumnDialogClose"
>
<div style="max-height: 400px; overflow-y: auto">
<el-checkbox
v-model="checkedAll"
:indeterminate="isIndeterminate"
:label="t('全部')"
@change="checkAllFun"
/>
<!-- 复选框容器(使用 flex 布局) -->
<div class="checkbox-container">
<el-checkbox-group v-model="selectedColumns">
<el-checkbox
v-for="(column, index) in allColumns"
:key="index"
:label="column.title"
></el-checkbox>
</el-checkbox-group>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="columnDialogVisible = false">{{ t('取消') }}</el-button>
<el-button type="primary" @click="onColumnConfirm">{{ t('确定') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { computed, defineProps, onMounted, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import Pagination from '@/components/Pagination.vue';
import { queryOfflineAssetInfo } from '@/apis/dcPhysicalAPIS';
import { usePropertyMetricsManager } from '@/application/core/infra-layout/composables/usePropertyMetricsManager';
import dayjs from 'dayjs';
import { useMapLevelDataManager } from '@/application/core/infra-layout/composables/useMapLevelDataManager';
let tableMaxHeight = ref(450);
const mapLevelDataManagerStore = useMapLevelDataManager();
const mapLevelDataManager = computed(() => mapLevelDataManagerStore);
const mapLevelCode = computed(() => mapLevelDataManager.value.getMapLevelCode());
const currentSelectOption = computed(() => {
const { areaCode, cityCode, campusCode, dcCode, roomBuilding, floorCode } = mapLevelCode.value;
return [areaCode, cityCode, campusCode, dcCode, roomBuilding, floorCode].filter(Boolean);
});
const loading = ref(false);
const propertyMetricsManager = usePropertyMetricsManager();
const { t } = useI18n();
const props = defineProps({
assetOverviewData: {
type: Object,
default: () => ({}),
},
});
const handelNumber = (num, fixed) => {
if (num || num === 0) {
return fixed ? Number(num).toFixed(fixed) : num;
} else {
return '--';
}
};
// 弹窗
const dialogVisible = ref(false);
const dialogType = ref<string | null>(null);
const tableData = ref([]);
const page = reactive({
total: 0,
pageNo: 1,
pageSize: 10,
});
// 列相关状态
const columnDialogVisible = ref(false);
const selectedColumns = ref<string[]>([]); // 用户选择的列标题
const allColumns = ref<Array<{ title: string; field: string; width: number; visible: boolean }>>(
[],
);
const visibleColumns = ref<
Array<{ title: string; field: string; width: number; visible: boolean }>
>([]);
const checkedAll = computed({
get: () => {
const total = allColumns.value.length;
const selected = allColumns.value.filter(col => col.visible).length;
return selected === total;
},
set: (val: boolean) => {
checkAllFun(val);
},
});
// 半选状态
const isIndeterminate = computed(() => {
const total = allColumns.value.length;
const selected = allColumns.value.filter(col => col.visible).length;
return selected > 0 && selected < total;
});
// 列配置映射
const columnsMap = {
equipment: [
{ title: t('国家'), field: 'country', width: 80 },
{ title: t('大区'), field: 'area', width: 80 },
{ title: t('城市'), field: 'city', width: 120 },
{ title: t('园区名称'), field: 'campusName', width: 170 },
{ title: t('园区编码'), field: 'campusCode', width: 110 },
{ title: t('楼栋(DC)'), field: 'dcName', width: 270 },
{ title: t('DC编码'), field: 'dcCode', width: 130 },
{ title: t('楼层'), field: 'floor', width: 80 },
{ title: t('房间'), field: 'roomName', width: 260 },
{ title: t('房间编码'), field: 'roomCode', width: 110 },
{ title: t('机柜/货架'), field: 'rackId', width: 150 },
{ title: t('云业务'), field: 'cloudName', width: 140 },
{ title: t('云业务编码'), field: 'cloudId', width: 110 },
{ title: t('资产所有权类型'), field: 'assetOwnership', width: 150 },
{ title: t('资产所有权'), field: 'assetOwnershipName', width: 120 },
{ title: t('资产所有权类型编码'), field: 'assetOwnershipCode', width: 160 },
{ title: t('场地类型'), field: 'machineRoomTypeName', width: 100 },
{ title: t('场地类型编码'), field: 'machineRoomTypeCode', width: 130 },
{ title: t('资产类型'), field: 'assetTypeName', width: 120 },
{ title: t('资产类型编码'), field: 'assetTypeCode', width: 120 },
{ title: t('入库时间'), field: 'inventoryTime', width: 170 },
{ title: t('库存时长(天)'), field: 'inventoryDuration', width: 130 },
{ title: t('是否库存超期'), field: 'isOverdue', width: 110 },
{ title: t('盘点结果'), field: 'countingResult', width: 100 },
{ title: t('序列号'), field: 'sn', width: 200 },
{ title: t('日期'), field: 'date', width: 120 },
{ title: t('年月'), field: 'yearMonth', width: 80 },
{ title: t('盘点时间'), field: 'countingTime', width: 110 },
{ title: t('大类'), field: 'category', width: 150 },
{ title: t('小类'), field: 'subcategory', width: 150 },
{ title: t('发货SN'), field: 'shipmentSn', width: 200 },
{ title: t('业务标签'), field: 'label', width: 110 },
{ title: t('申购BOM'), field: 'purchaseBom', width: 110 },
{ title: t('BOM编码'), field: 'bomCode', width: 110 },
{ title: t('DC全称'), field: 'dcFullName', width: 290 },
{ title: t('资产状态'), field: 'status', width: 110 },
],
};
// 根据dialogType获取列配置
const getColumnsByType = (type: string | null) => columnsMap[type] || columnsMap.equipment;
// 初始化列数据
const initColumns = () => {
const columns = getColumnsByType(dialogType.value);
allColumns.value = columns.map(col => ({
title: col.title,
field: col.field,
width: col.width,
visible: true,
}));
// 初始化 selectedColumns
selectedColumns.value = allColumns.value.filter(col => col.visible).map(col => col.title);
};
const indexMethod = index => {
const indexNum = (index + 1 + (page.pageNo - 1) * page.pageSize).toString().padStart(2, '0');
return indexNum;
};
// 弹窗标题
const dialogTitle = computed(() =>
dialogType.value === 'equipment' ? t('离线设备明细') : t('离线配件明细'),
);
const showDialog = (type: string) => {
dialogType.value = type;
initColumns(); // 初始化列数据
getTableData(type);
dialogVisible.value = true;
};
// 处理多选
const handleCondition = condition => {
const multipleNames = [...propertyMetricsManager.multipleNames].map(i => i.value);
const cityMultipleNames = [...propertyMetricsManager.multipleNames].map(i => i.name);
if (!multipleNames.length) {
return;
}
if (['China', 'Abroad', 'global'].includes(mapLevelDataManager.value.level)) {
Reflect.set(condition.value, 'area', cityMultipleNames);
}
if (mapLevelDataManager.value.level === 'area') {
Reflect.set(condition.value, 'city', cityMultipleNames);
}
if (mapLevelDataManager.value.level === 'city') {
Reflect.set(condition.value, 'campusCode', multipleNames);
}
if (mapLevelDataManager.value.level === 'campus') {
Reflect.set(condition.value, 'buildingCode', multipleNames);
}
};
const getDetailCondition = condition => {
if (mapLevelCode.value.country === 'China') {
Reflect.set(condition.value, 'country', '中国');
} else if (mapLevelCode.value.country === 'Abroad') {
Reflect.set(condition.value, 'country', '海外');
} else {
Reflect.set(condition.value, 'country', '');
}
};
// 单选
const getDetailReflect = condition => {
if (currentSelectOption.value && currentSelectOption.value.length) {
if (mapLevelDataManager.value.level === 'area') {
Reflect.set(condition.value, 'area', [currentSelectOption.value[0]]);
} else if (mapLevelDataManager.value.level === 'city') {
let city = '';
for (let item of mapLevelDataManager.value.campusData) {
if (item.city_code === currentSelectOption.value[1]) {
city = item.city;
}
}
Reflect.set(condition.value, 'city', [city]);
} else if (mapLevelDataManager.value.level === 'campus') {
Reflect.set(condition.value, 'campusCode', [currentSelectOption.value[2]]);
} else if (mapLevelDataManager.value.level === 'dc') {
Reflect.set(condition.value, 'dcCode', [currentSelectOption.value[3]]);
Reflect.set(condition.value, 'building', currentSelectOption.value[4]);
} else {
}
} else {
Reflect.set(condition.value, 'area', []);
Reflect.set(condition.value, 'city', []);
Reflect.set(condition.value, 'campusCode', []);
Reflect.set(condition.value, 'dcCode', []);
Reflect.set(condition.value, 'building', '');
}
};
const getTableData = async type => {
let condition = ref({});
getDetailCondition(condition);
getDetailReflect(condition);
handleCondition(condition);
let params = {
condition: condition.value,
pageNo: page.pageNo,
pageSize: page.pageSize,
type: type === 'equipment' ? 'offline_equipment' : 'offline_component',
};
condition.value.date = propertyMetricsManager.timeValue;
condition.value.machineRoomTypeCode = propertyMetricsManager.propertyValue.machineroomTypeList;
condition.value.assetOwnershipTypeCode = propertyMetricsManager.propertyValue.assetownershipList;
condition.value.cloudId = propertyMetricsManager.propertyValue.cloudTypeList;
loading.value = true;
const res = await queryOfflineAssetInfo(params);
loading.value = false;
tableData.value = res?.data;
page.total = res?.total;
};
const handleClose = () => {
dialogVisible.value = false;
};
const tableChange = (pages: object) => {
page.pageNo = pages.currentPage;
page.pageSize = pages.pageSize;
getTableData(dialogType.value || 'equipment');
};
// 列选择功能
const openColumnSelection = () => {
columnDialogVisible.value = true;
};
const onColumnDialogClose = () => {
columnDialogVisible.value = false;
};
// 确认列选择
const onColumnConfirm = () => {
visibleColumns.value = allColumns.value.filter(col => col.visible);
columnDialogVisible.value = false;
};
// 全选功能处理
const checkAllFun = (checked: boolean) => {
allColumns.value.forEach(col => {
col.visible = checked;
});
selectedColumns.value = checked ? allColumns.value.map(col => col.title) : [];
};
watch(
() => selectedColumns.value,
newSelected => {
allColumns.value.forEach(col => {
col.visible = newSelected.includes(col.title);
});
// 更新全选状态
checkedAll.value = newSelected.length === allColumns.value.length;
isIndeterminate.value = newSelected.length > 0 && newSelected.length < allColumns.value.length;
},
{ deep: true },
);
onMounted(() => {
let offHeight = document.getElementsByClassName('aside-content')[0].offsetHeight;
tableMaxHeight.value = offHeight - 260;
});
</script>
<style lang="less" scoped>
.asset-overview-container {
width: 100%;
height: 200px;
overflow: hidden;
background-color: #fff;
border-radius: 10px;
border: 1px solid #f1f1f3;
.title {
margin: 20px;
font-weight: 600;
font-size: 14px;
}
.asset-overview-body {
width: 100%;
display: flex;
.body-item {
width: 50%;
.body-item-top {
display: flex;
margin-left: 16px;
.top-icon {
width: 50px;
height: 50px;
background-color: #f2f5fc;
background-image: url('../assets/equip.png');
background-repeat: no-repeat;
background-size: 100%;
border-radius: 50%;
}
.fintting-icon {
background-image: url('../assets/fitting.png');
}
.top-body-comp {
margin-left: 10px;
.asset-type-title {
font-size: 14px;
font-weight: 600;
}
.asset-number-title {
font-size: 16px;
font-weight: 600;
margin-top: 12px;
}
}
}
.body-item-bottom {
margin-top: 12px;
display: flex;
justify-content: space-around;
width: 100%;
.body-item-bottom-online {
width: calc(50% - 20px);
background-color: #f2f5fc;
padding: 12px 0;
.online-icon-title {
display: flex;
margin-left: 12px;
align-items: center;
.online-icon {
background-image: url('../assets/online.png');
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-size: 100%;
}
.online-icon.offline-icon {
background-image: url('../assets/offline.png');
}
.online-title {
font-size: 14px;
font-weight: 600;
margin-left: 4px;
}
}
.online-number {
font-weight: 600;
font-size: 14px;
margin-top: 8px;
margin-left: 12px;
}
.underline {
text-decoration: underline;
text-decoration-color: #0a0a0a; /* 下划线颜色 */
text-decoration-thickness: 1px; /* 下划线粗细 */
cursor: pointer;
}
}
}
}
}
}
.table-toolbar {
text-align: right;
margin-bottom: 10px;
}
.checkbox-container {
display: flex;
flex-wrap: wrap;
}
.checkbox-container .el-checkbox {
flex: 0 0 25%;
}
/* 可选:调整复选框的最小宽度 */
.checkbox-container .el-checkbox {
min-width: 130px;
}
:deep(.common-pagination) {
justify-content: flex-start;
}
.dark {
.asset-overview-container {
background: #282b33;
border-color: rgba(223, 225, 230, 0.1);
color: #dfe1e6;
.asset-overview-body {
.body-item {
.body-item-bottom {
.body-item-bottom-online {
background: #282b33;
}
}
}
}
}
}
.el-table {
--el-table-header-bg-color: #f2f5fc;
}
</style>
要正确显示半选状态,看看哪里有问题