main #63

Merged
charles merged 3 commits from main into prod 2026-01-21 17:50:01 +08:00
10 changed files with 771 additions and 327 deletions

View File

@ -436,7 +436,7 @@ export const asyncRoutes = [
children: [
{
path: "",
component: () => import('@/views/product/productHome/productIndex/index.vue'),
component: () => import('@/views/product/mainPage/index.vue'),
name: 'ResourceOverview',
meta: {
title: "资源概览",

View File

@ -0,0 +1,260 @@
// src/data-manager.js
const clusterData = {
// 默认数据(全国数据)
default: {
// 头部数据
header: {
demandPower: 1500,
supplyPower: 35320,
clusterCount: 7,
customerCount: 352,
chipCount: 75368,
modelCount: 75
},
// 左侧数据
left: {
// 异构芯片规模
chipScale: {
NVIDIA: 33000,
ascend: 5000,
supercomputer: 8000,
total: 5100
},
// 运行中芯片数量
runningChips: [16909, 15050, 16240, 21470, 18940, 18950, 17450],
// 模型调用量
modelUsage: [
{ name: 'deepseek', value: 105 },
{ name: '通义千问', value: 78 },
{ name: 'kimi', value: 70 },
{ name: '豆包', value: 120 },
{ name: '文心一言', value: 60 },
{ name: '元宝', value: 85 }
],
// token调用量
tokenUsage: [
{ name: '千问3-max', value: 5000 },
{ name: 'kimi', value: 4300 },
{ name: '豆包', value: 3500 },
{ name: 'deepseek', value: 3200 },
{ name: '千帆', value: 2800 }
]
},
// 右侧数据
right: {
// 应用类型
appTypes: ['智能推理', '智能训练', '图形渲染', '蛋白质分析', '其他'],
// 用户消费排行
userConsumption: [
{ rank: 1, name: '用户A', amount: 520000 },
{ rank: 2, name: '用户B', amount: 450000 },
{ rank: 3, name: '用户B', amount: 186000 },
{ rank: 4, name: '用户D', amount: 120000 },
{ rank: 5, name: '用户E', amount: 56000 },
{ rank: 6, name: '用户F', amount: 43000 },
{ rank: 7, name: '用户G', amount: 35000 },
{ rank: 8, name: '用户H', amount: 28000 }
],
// 算力使用情况
powerUsage: [12300, 11500, 11500, 13400, 12900, 13500, 13200]
},
// 底部概览数据
overview: {
supply: [
{ name: 'CPU', value: 2350 },
{ name: 'GPU', value: 5500 },
{ name: '进程数量', value: 0 },
{ name: '资源统计', value: 0 },
{ name: '运行中', value: '30%' },
{ name: '关机中', value: '70%' },
{ name: '故障数量', value: 0 },
{ name: '在线数量', value: 0 }
],
demand: [
{ name: 'CPU', value: 0 },
{ name: 'GPU', value: 0 },
{ name: '进程数量', value: 0 },
{ name: '资源统计', value: 0 },
{ name: '运行中', value: 0 },
{ name: '关机中', value: 0 },
{ name: '故障数量', value: 0 },
{ name: '在线数量', value: 0 }
]
}
},
// 北京集群数据
beijing: {
header: {
demandPower: 190,
supplyPower: 4485,
clusterCount: 1,
customerCount: 44,
chipCount: 9571,
modelCount: 9
},
left: {
chipScale: {
NVIDIA: 4191,
ascend: 635,
supercomputer: 1016,
total: 647
},
runningChips: [2147, 1911.35, 2062, 2726, 2405, 2406, 2216],
modelUsage: [
{ name: 'DEEPSEEK', value: 13 },
{ name: '通义千问', value: 9 },
{ name: 'kimi', value: 8 },
{ name: '豆包', value: 15 },
{ name: '文心一言', value: 7 },
{ name: '元宝', value: 10 }
],
tokenUsage: [
{ name: '千问3-max', value: 635 },
{ name: 'kimi 2', value: 546 },
{ name: 'doubao 1.5', value: 444 },
{ name: 'deepseek', value: 406 },
{ name: '千帆5.0', value: 355 }
]
},
right: {
appTypes: ['智能推理', '智能训练', '图形渲染', '蛋白质分析', '其他'],
userConsumption: [
{ rank: 1, name: '用户A', amount: 66040 },
{ rank: 2, name: '用户B', amount: 57150 },
{ rank: 3, name: '用户C', amount: 23622 },
{ rank: 4, name: '用户D', amount: 15240 },
{ rank: 5, name: '用户E', amount: 7112 },
{ rank: 6, name: '用户F', amount: 5461 },
{ rank: 7, name: '用户G', amount: 4445 },
{ rank: 8, name: '用户H', amount: 3556 }
],
powerUsage: [1562, 1460, 1460, 1701, 1638, 1714, 1676]
},
overview: {
supply: [
{ name: 'CPU', value: 298 },
{ name: 'GPU', value: 698 },
{ name: '进程数量', value: 0 },
{ name: '资源统计', value: 0 },
{ name: '运行中', value: '30%' },
{ name: '关机中', value: '70%' },
{ name: '故障数量', value: 0 },
{ name: '在线数量', value: 0 }
],
demand: [
{ name: 'CPU', value: 0 },
{ name: 'GPU', value: 0 },
{ name: '进程数量', value: 0 },
{ name: '资源统计', value: 0 },
{ name: '运行中', value: 0 },
{ name: '关机中', value: 0 },
{ name: '故障数量', value: 0 },
{ name: '在线数量', value: 0 }
]
}
}
};
// 全局事件总线
class EventBus {
constructor() {
this.events = {};
}
$on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
$off(event, callback) {
if (!this.events[event]) return;
if (!callback) {
delete this.events[event];
} else {
const index = this.events[event].indexOf(callback);
if (index > -1) {
this.events[event].splice(index, 1);
}
}
}
$emit(event, data) {
if (!this.events[event]) return;
this.events[event].forEach(callback => {
callback(data);
});
}
}
// 数据管理器
class DataManager {
constructor() {
this.currentCluster = 'default';
this.listeners = [];
this.eventBus = new EventBus();
this.data = clusterData[this.currentCluster];
}
setCluster(clusterName) {
this.currentCluster = clusterName;
this.data = clusterData[clusterName] || clusterData.default;
this.notifyListeners();
}
getData() {
return this.data;
}
getCurrentCluster() {
return this.currentCluster;
}
addListener(listener) {
this.listeners.push(listener);
}
removeListener(listener) {
const index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
}
notifyListeners() {
this.listeners.forEach(listener => {
if (typeof listener === 'function') {
listener(this.data);
}
});
this.eventBus.$emit('cluster-changed', {
cluster: this.currentCluster,
data: this.data
});
}
}
// 创建全局数据管理器实例
const dataManager = new DataManager();
// 导出
export { clusterData, dataManager };

View File

@ -41,7 +41,7 @@
<i class="iconfont icon-kefu functions" @click="handleServiceClick"></i>
<!-- 控制台按钮已登录 -->
<a @click="goB" v-if="loginState" class="login-btn">控制台</a>
<!-- 消息 -->
<!-- 消息 -->
<i class="iconfont icon-xiaoxi functions" @click="handleMessageClick"></i>
<!-- 登录按钮未登录 -->
<a @click="$router.push({
@ -77,7 +77,7 @@
</span>
<svg @click="copyBtn" class="copy-btn" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"
width="12" height="12" style="fill: #1019ab;">
<path
<path
d="M394.666667 106.666667h448a74.666667 74.666667 0 0 1 74.666666 74.666666v448a74.666667 74.666667 0 0 1-74.666666 74.666667H394.666667a74.666667 74.666667 0 0 1-74.666667-74.666667V181.333333a74.666667 74.666667 0 0 1 74.666667-74.666666z m0 64a10.666667 10.666667 0 0 0-10.666667 10.666666v448a10.666667 10.666667 0 0 0 10.666667 10.666667h448a10.666667 10.666667 0 0 0 10.666666-10.666667V181.333333a10.666667 10.666667 0 0 0-10.666666-10.666666H394.666667z m245.333333 597.333333a32 32 0 0 1 64 0v74.666667a74.666667 74.666667 0 0 1-74.666667 74.666666H181.333333a74.666667 74.666667 0 0 1-74.666666-74.666666V394.666667a74.666667 74.666667 0 0 1 74.666666-74.666667h74.666667a32 32 0 0 1 0 64h-74.666667a10.666667 10.666667 0 0 0-10.666666 10.666667v448a10.666667 10.666667 0 0 0 10.666666 10.666666h448a10.666667 10.666667 0 0 0 10.666667-10.666666v-74.666667z"
p-id="1521"></path>
</svg>

View File

@ -90,8 +90,7 @@
</el-tooltip>
<!-- 验证码输入框仅在手机号登录模式下显示 -->
<div v-else style="display:flex;margin-top:20px;"
v-show="loginForm.username !== 'admin' && loginForm.password !== 'admin'">
<div v-else style="display:flex;margin-top:20px;">
<el-form-item prop="" class="invitecode"
style="background-color: white; border: 1px solid #d9d9d9;">
<div class="user-input">

View File

@ -21,7 +21,7 @@
<br>
查看集群详细信息
</span>
<div class="btn">进入地图</div>
<div class="btn" @click="enterCluster(cluster)">进入地图</div>
</div>
</div>
<div class="item" v-else>
@ -31,22 +31,28 @@
<br>
查看集群详细信息
</span>
<div class="btn">进入地图</div>
<div class="btn" @click="enterCluster(cluster)">进入地图</div>
</div>
<div class="right">{{ cluster.name }}</div>
</div>
</div>
</div>
</div>
</div>
<!-- 添加返回按钮 -->
<div v-if="currentCluster !== 'default'" class="reset-container">
<div class="reset-btn" @click="resetToDefault">
<span>返回全国视图</span>
</div>
</div>
</div>
</template>
<script>
import * as echarts from "echarts";
import "echarts/map/js/china.js";
import { areaTwo } from "@/views/product/bigScreen/asset/cityData";
import { dataManager } from "@/utils/data-manager.js";
const CHART_CONSTANTS = {
MAP_ZOOM: 1.23,
@ -55,7 +61,7 @@ const CHART_CONSTANTS = {
BORDER_COLOR: "#389dff",
RIPPLE_PERIOD: 5,
RESIZE_DEBOUNCE_TIME: 300,
HORIZONTAL_LENGTH: 4, // 线4
HORIZONTAL_LENGTH: 4,
BG_IMAGE_PATHS: {
map: require("../../../../../assets/image/map.png"),
lbx: require("../../../../../assets/image/lbx.png"),
@ -74,49 +80,49 @@ export default {
return {
myChart: null,
resizeTimer: null,
// labelPosition
currentCluster: 'default',
clusterConfigs: [
{
name: '北京集群',
cityName: '北京',
direction: 'right-top',
labelPosition: [121.40, 50.90], //
labelPosition: [121.40, 50.90],
position: { top: '18%', left: '73%' }
},
{
name: '内蒙集群',
cityName: '内蒙古',
direction: 'left',
labelPosition: [105.73, 50.83], //
position: { top: '19%', left: '35%' }
labelPosition: [105.73, 50.83],
position: { top: '19%', left: '34%' }
},
{
name: '新疆集群',
cityName: '新疆',
direction: 'left',
labelPosition: [88.68, 52.77], //
labelPosition: [88.68, 52.77],
position: { top: '15.4%', left: '8%' }
},
{
name: '长三角集群',
cityName: '上海',
direction: 'right',
labelPosition: [125, 38.23], //
labelPosition: [125, 38.23],
position: { top: '40%', left: '80.5%' }
},
{
name: '珠三角集群',
cityName: '广东',
direction: 'right',
labelPosition: [125.5, 28.12], //
labelPosition: [125.5, 28.12],
position: { top: '57.3%', left: '80.9%' }
},
{
name: '川渝集群',
cityName: '四川',
direction: 'left',
labelPosition: [99.06, 21.8], //
position: { top: '69.2%', left: '26%' }
labelPosition: [99.06, 21.8],
position: { top: '69.2%', left: '24%' }
}
]
};
@ -135,42 +141,42 @@ export default {
"上海": [121.47, 31.23],
"四川": [104.06, 30.67],
"重庆": [106.50, 29.53],
// "": [120.15, 30.28],
"贵州": [106.63, 26.65],
"广东": [113.26, 23.12],
"香港": [114.16, 22.28],
"济南": [117.00, 36.65],
// "": [120.30, 31.57],
"广州": [113.23, 23.16],
"青岛": [120.33, 36.07],
"长沙": [113.00, 28.21],
};
}
},
watch: {
showMap(newVal) {
newVal && this.$nextTick(() => this.initChart());
},
routePath: "refreshChart",
clickableCities: { deep: true, handler: "refreshChart" }
},
mounted() {
this.initChart();
window.addEventListener('resize', this.handleResize);
//
dataManager.eventBus.$on('cluster-changed', this.handleClusterChanged);
//
this.currentCluster = dataManager.getCurrentCluster();
},
beforeDestroy() {
this.myChart?.dispose();
clearTimeout(this.resizeTimer);
window.removeEventListener('resize', this.handleResize);
dataManager.eventBus.$off('cluster-changed', this.handleClusterChanged);
},
methods: {
handleResize() {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => this.myChart?.resize(), CHART_CONSTANTS.RESIZE_DEBOUNCE_TIME);
},
getClusterStyle(position) {
return position ? { position: 'absolute', ...position, zIndex: 10 } : {};
},
initChart() {
const chartDom = this.$refs.myEchart;
if (!chartDom) return;
@ -179,9 +185,7 @@ export default {
this.myChart.setOption(this.buildChartOption(), true);
this.myChart.on('click', this.handleAreaClick);
},
refreshChart() {
this.$nextTick(() => this.initChart());
},
buildChartOption() {
return {
tooltip: { show: false },
@ -189,6 +193,7 @@ export default {
series: [this.buildLineSeries(), this.buildEffectScatterSeries(), this.buildLabelScatterSeries()]
};
},
buildGeoConfig() {
return {
map: "china",
@ -202,6 +207,7 @@ export default {
}
};
},
buildLineSeries() {
return {
type: 'lines',
@ -213,6 +219,7 @@ export default {
effect: { show: true, trailLength: 0, symbol: 'circle', symbolSize: 3, color: CHART_CONSTANTS.LINE_COLOR }
};
},
buildEffectScatterSeries() {
return {
type: "effectScatter",
@ -237,18 +244,28 @@ export default {
effect: { show: true, scaleSize: 3, period: 4, color: CHART_CONSTANTS.LINE_COLOR, shadowBlur: 10 }
};
},
buildLabelScatterSeries() {
return {
type: 'scatter',
coordinateSystem: 'geo',
data: this.clusterConfigs.map(c => ({
value: c.labelPosition,
label: { show: true, formatter: c.name, backgroundColor: '#000', color: '#fff', fontSize: 14, fontWeight: 'bold', padding: [8,12], borderRadius: 2 }
label: {
show: true,
formatter: c.name,
backgroundColor: '#000',
color: '#fff',
fontSize: 14,
fontWeight: 'bold',
padding: [8,12],
borderRadius: 2
}
})),
symbol: 'none'
};
},
// 线 线
buildLineData() {
const shortLen = CHART_CONSTANTS.HORIZONTAL_LENGTH;
return this.clusterConfigs
@ -260,34 +277,30 @@ export default {
const [cityLon, cityLat] = cityCoord;
let lineStart, lineEnd;
// 1. 线shortLen
switch (cluster.direction) {
case 'left':
// 线
lineEnd = [labelLon, labelLat]; // 线 =
lineStart = [labelLon + shortLen, labelLat]; // 线 = shortLen
lineEnd = [labelLon, labelLat];
lineStart = [labelLon + shortLen, labelLat];
break;
case 'right':
default:
// 线
lineStart = [labelLon - shortLen, labelLat]; // 线 = shortLen
lineEnd = [labelLon, labelLat]; // 线 =
lineStart = [labelLon - shortLen, labelLat];
lineEnd = [labelLon, labelLat];
break;
}
// 2. 线() 线 线(线)
// 线
return {
coords: [
[cityLon, cityLat], //
lineStart, // 1线
lineEnd, // 2线线
[labelLon, labelLat]//
[cityLon, cityLat],
lineStart,
lineEnd,
[labelLon, labelLat]
]
};
})
.filter(item => item !== null);
},
buildScatterData() {
return Object.keys(this.geoCoordMap).map(key => ({
name: key,
@ -295,11 +308,77 @@ export default {
symbolSize: key === '北京' ? 18 : 12
}));
},
//
enterCluster(cluster) {
console.log('点击集群:', cluster.name);
//
if (cluster.name === '北京集群') {
console.log('进入集群:', cluster.name);
//
this.$emit('cluster-selected', cluster.name);
//
let clusterKey = 'default';
switch (cluster.name) {
case '北京集群':
clusterKey = 'beijing';
break;
default:
clusterKey = 'default';
}
//
dataManager.setCluster(clusterKey);
this.currentCluster = clusterKey;
//
this.$emit('update:showMap', false);
this.$emit('area-click', {
name: cluster.cityName,
cluster: cluster.name
});
} else {
//
console.log(`当前只支持北京集群,${cluster.name}功能暂未开放`);
//
// this.$message.warning(`${cluster.name}`);
}
},
// handleAreaClick
handleAreaClick(params) {
if (!this.clickableCities?.includes(params?.name)) return;
this.currentArea = areaTwo[params.name] || [];
this.$emit('update:showMap', false);
this.$emit('area-click', params);
//
const cluster = this.clusterConfigs.find(c => c.cityName === params.name);
if (cluster) {
this.enterCluster(cluster);
} else {
this.currentArea = areaTwo[params.name] || [];
this.$emit('update:showMap', false);
this.$emit('area-click', params);
}
},
//
handleClusterChanged(event) {
this.currentCluster = event.cluster;
},
//
resetToDefault() {
dataManager.setCluster('default');
this.currentCluster = 'default';
this.$emit('reset-view');
},
// updateCharts
updateCharts() {
console.log('updateCharts called in Map.vue');
//
}
}
};
@ -389,17 +468,38 @@ export default {
display: flex;
justify-content: center;
flex-direction: column;
padding: 0 8px;
background-color: rgba(147, 112, 219, 0.3);
padding: 8px;
}
.left .title {
padding-bottom: 16px;
font-size: 12px;
line-height: 1.3;
}
.item .btn {
color: #FDBD00;
cursor: pointer;
padding: 2px 8px;
border: 1px solid #FDBD00;
border-radius: 4px;
text-align: center;
transition: all 0.3s;
font-size: 12px;
font-weight: bold;
}
.item .btn:hover {
background: rgba(253, 189, 0, 0.1);
transform: scale(1.05);
box-shadow: 0 0 10px rgba(253, 189, 0, 0.5);
}
/* 非北京集群的按钮样式,可以添加一个特殊样式表示不可用 */
.item .btn.disabled {
cursor: not-allowed;
opacity: 0.5;
}
.item .right {
@ -407,6 +507,46 @@ export default {
width: 20px;
font-size: 12px;
background-color: #000;
writing-mode: vertical-lr;
display: flex;
align-items: center;
justify-content: center;
letter-spacing: 0.5em;
padding: 5px 0;
}
.reset-container {
position: absolute;
top: 10%;
left: 50%;
transform: translateX(-50%);
z-index: 100;
}
.reset-btn {
/* 渐变色 */
background: #0A2463 ;
color: white;
padding: 10px 20px;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.reset-btn:hover {
background: #0F3460;
transform: scale(1.05);
box-shadow: 0 0 15px rgba(24, 144, 255, 0.7);
}
.reset-btn span {
margin-right: 5px;
}
.sortBox {

View File

@ -5,7 +5,7 @@
<!-- 需求算力 -->
<div class="top-tit">
<div class="top-text">
1500P
{{ headerData.demandPower }}P
</div>
<div class="btm-text">
需求算力
@ -14,7 +14,7 @@
<!-- 供给算力 -->
<div class="top-tit">
<div class="top-text">
35320P
{{ headerData.supplyPower }}P
</div>
<div class="btm-text">
供给算力
@ -23,7 +23,7 @@
<!-- 集群数 -->
<div class="top-tit">
<div class="top-text">
7
{{ headerData.clusterCount }}
</div>
<div class="btm-text">
集群数量
@ -32,7 +32,7 @@
<!-- 客户数量 -->
<div class="top-tit">
<div class="top-text">
352
{{ headerData.customerCount }}
</div>
<div class="btm-text">
客户数量
@ -41,7 +41,7 @@
<!-- 芯片数量 -->
<div class="top-tit">
<div class="top-text">
75368
{{ headerData.chipCount }}
</div>
<div class="btm-text">
芯片数量
@ -50,7 +50,7 @@
<!-- 模型数量 -->
<div class="top-tit">
<div class="top-text">
75
{{ headerData.modelCount }}
</div>
<div class="btm-text">
模型数量
@ -61,9 +61,14 @@
<!-- 添加地图组件 -->
<div class="map-container">
<!-- 传递必需的props -->
<Map :route-path="currentRoutePath" :clickable-cities="clickableCities" :show-map.sync="showMap"
@area-click="handleAreaClick" />
<Map
:route-path="currentRoutePath"
:clickable-cities="clickableCities"
:show-map.sync="showMap"
@area-click="handleAreaClick"
@cluster-selected="handleClusterSelected"
@reset-view="handleResetView"
/>
</div>
<!-- 底部 -->
<div class="bottom">
@ -88,74 +93,15 @@
</div>
<!-- 概览 -->
<div class="overview" v-show="activeTab === 0">
<!-- 概览项原有内容不变 -->
<div class="overview-item">
<!-- 概览项动态数据 -->
<div
v-for="(item, index) in overviewData.supply"
:key="'supply-' + index"
class="overview-item"
>
<div class="overview-title">
<span>2350</span>
<span>CPU</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>5500</span>
<span>GPU</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>进程数量</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>资源统计</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>30%</span>
<span>运行中</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>70%</span>
<span>关机中</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>故障数量</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>在线数量</span>
<span>{{ item.value }}</span>
<span>{{ item.name }}</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
@ -164,74 +110,15 @@
</div>
<!-- 概览two - 根据激活标签显示不修改原有布局样式 -->
<div class="overview-two" v-show="activeTab === 1">
<!-- 概览项原有内容不变 -->
<div class="overview-item">
<!-- 概览项动态数据 -->
<div
v-for="(item, index) in overviewData.demand"
:key="'demand-' + index"
class="overview-item"
>
<div class="overview-title">
<span>0</span>
<span>CPU</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>GPU</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>进程数量</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>资源统计</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>运行中</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>关机中</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>故障数量</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
<div class="overview-item">
<div class="overview-title">
<span>0</span>
<span>在线数量</span>
<span>{{ item.value }}</span>
<span>{{ item.name }}</span>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
@ -246,6 +133,7 @@
<script>
//
import Map from './Map.vue';
import { dataManager } from '@/utils/data-manager';
export default {
name: 'VmCenter',
@ -254,38 +142,80 @@ export default {
},
data() {
return {
//
currentRoutePath: this.$route ? this.$route.path : '/',
// -
clickableCities: ['北京', '上海', '广州', '深圳', '济南', '无锡', '青岛', '长沙', '天津', '南京', '杭州', '成都', '重庆', '武汉'],
//
showMap: true,
// 0=overview1=overview-two
activeTab: 0
activeTab: 0,
currentData: null,
headerData: {
demandPower: 0,
supplyPower: 0,
clusterCount: 0,
customerCount: 0,
chipCount: 0,
modelCount: 0
},
overviewData: {
supply: [],
demand: []
}
};
},
watch: {
// routePath
'$route.path'(newPath) {
this.currentRoutePath = newPath;
}
created() {
//
this.currentData = dataManager.getData();
this.updateDisplay();
//
dataManager.eventBus.$on('cluster-changed', this.handleClusterChanged);
},
mounted() {
//
if (!this.$route) {
this.currentRoutePath = window.location.pathname;
}
},
beforeDestroy() {
dataManager.eventBus.$off('cluster-changed', this.handleClusterChanged);
},
methods: {
handleAreaClick(params) {
console.log('区域被点击:', params);
//
},
//
handleClusterSelected(clusterName) {
console.log('接收到集群选择:', clusterName);
},
handleResetView() {
this.showMap = true;
},
switchTab(tabIndex) {
console.log('切换到标签:', tabIndex);
this.activeTab = tabIndex;
},
handleClusterChanged(event) {
this.currentData = event.data;
this.updateDisplay();
},
updateDisplay() {
if (!this.currentData) return;
this.headerData = {
demandPower: this.currentData.header?.demandPower || 0,
supplyPower: this.currentData.header?.supplyPower || 0,
clusterCount: this.currentData.header?.clusterCount || 0,
customerCount: this.currentData.header?.customerCount || 0,
chipCount: this.currentData.header?.chipCount || 0,
modelCount: this.currentData.header?.modelCount || 0
};
this.overviewData = {
supply: this.currentData.overview?.supply || [],
demand: this.currentData.overview?.demand || []
};
}
}
}

View File

@ -7,9 +7,9 @@
<div class="title">异构芯片规模</div>
<div class="conter">
<div class="conter-top">
<div class="top-tit">33000<span>P</span></div>
<div class="top-tit">5000<span>P</span></div>
<div class="top-tit">8000<span>P</span></div>
<div class="top-tit">{{ chipData.NVIDIA }}<span>P</span></div>
<div class="top-tit">{{ chipData.ascend }}<span>P</span></div>
<div class="top-tit">{{ chipData.supercomputer }}<span>P</span></div>
</div>
<div class="conter-center">
<img src="../images/3D.png" alt="">
@ -25,7 +25,7 @@
<div class="title">集群规模</div>
<div class="content">
<dv-decoration-9 style="width:200px;height:200px;color: #fff;font-size: 20px; font-weight: 600;">
5100P
{{ chipData.total }}P
</dv-decoration-9>
</div>
</div>
@ -54,44 +54,81 @@
<script>
import * as echarts from 'echarts';
import { dataManager } from "@/utils/data-manager.js";
export default {
name: 'LeftPanel',
data() {
return {
runningChartInstance: null,
modelChartInstance: null,
tokenChartInstance: null,
resizeTimer: null
resizeTimer: null,
currentData: null,
chipData: {
NVIDIA: 0,
ascend: 0,
supercomputer: 0,
total: 0
}
};
},
created() {
this.currentData = dataManager.getData();
this.updateChipData();
dataManager.eventBus.$on('cluster-changed', this.handleClusterChanged);
},
mounted() {
this.initRunningChart();
this.initModelChart();
this.initTokenChart();
window.addEventListener('resize', this.handleResize);
},
beforeDestroy() {
this.runningChartInstance?.dispose();
this.modelChartInstance?.dispose();
this.tokenChartInstance?.dispose();
window.removeEventListener('resize', this.handleResize);
dataManager.eventBus.$off('cluster-changed', this.handleClusterChanged);
},
methods: {
//
handleClusterChanged(event) {
this.currentData = event.data;
this.updateChipData();
this.updateCharts();
},
updateChipData() {
if (this.currentData?.left?.chipScale) {
this.chipData = {
NVIDIA: this.currentData.left.chipScale.NVIDIA || 0,
ascend: this.currentData.left.chipScale.ascend || 0,
supercomputer: this.currentData.left.chipScale.supercomputer || 0,
total: this.currentData.left.chipScale.total || 0
};
}
},
initRunningChart() {
const chartDom = this.$refs.runningChart;
if (!chartDom) return;
this.runningChartInstance = echarts.init(chartDom);
this.updateRunningChart();
},
updateRunningChart() {
if (!this.runningChartInstance || !this.currentData?.left) return;
const yData = this.currentData.left.runningChips || [];
const xData = ['12/01', '12/02', '12/03', '12/04', '12/05', '12/06', '12/07'];
const yData = [16909, 15050, 16240, 21470, 18940, 18950, 17450];
// y
const maxValue = Math.max(...yData);
const minValue = Math.min(...yData);
// y
const yMax = Math.ceil(maxValue / 1000) * 1000 + 1000;
const yMin = Math.floor(minValue / 1000) * 1000 - 1000;
//
const maxIndex = yData.indexOf(maxValue);
const markPointData = yData.map((value, index) => {
if (index === maxIndex) {
return {
@ -101,16 +138,12 @@ export default {
yAxis: value,
symbol: 'circle',
symbolSize: 10,
itemStyle: {
color: '#fff',
// borderColor: '#ff4d4f',
// borderWidth: 2
},
itemStyle: { color: '#fff' },
label: {
show: true,
position: 'top',
formatter: function (params) {
return (params.value / 10000).toFixed(1) + '万'; // ""
return (params.value / 10000).toFixed(1) + '万';
},
color: '#fff',
fontWeight: 'bold',
@ -131,7 +164,7 @@ export default {
const value = params[0].value;
return params[0].name + '<br/>' +
params[0].marker + params[0].seriesName + ': ' +
value.toLocaleString() + '个'; //
value.toLocaleString() + '个';
}
},
grid: {
@ -154,8 +187,8 @@ export default {
},
yAxis: {
type: 'value',
min: yMin, // 使
max: yMax, // 使
min: yMin,
max: yMax,
splitNumber: 6,
splitLine: {
lineStyle: {
@ -167,7 +200,6 @@ export default {
color: 'rgba(255,255,255,0.7)',
fontSize: 12,
formatter: function (value) {
// ""1
return (value / 10000).toFixed(1) + '万';
}
},
@ -206,20 +238,23 @@ export default {
backgroundColor: 'transparent'
};
this.runningChartInstance.setOption(option);
this.runningChartInstance.setOption(option, true);
},
//
initModelChart() {
const chartDom = this.$refs.modelChart;
if (!chartDom) return;
this.modelChartInstance = echarts.init(chartDom);
this.updateModelChart();
},
// 使
const yData = ['deepseek', '通义千问', 'kimi', '豆包', '文心一言', '元宝'];
const xData = [105, 78, 70, 120, 60, 85];
updateModelChart() {
if (!this.modelChartInstance || !this.currentData?.left) return;
const modelData = this.currentData.left.modelUsage || [];
const yData = modelData.map(item => item.name);
const xData = modelData.map(item => item.value);
// X
const maxValue = Math.max(...xData);
const xMax = Math.ceil(maxValue / 50) * 50 + 10;
@ -269,7 +304,6 @@ export default {
axisTick: { show: false }
},
series: [
// barWidth1020
{
name: '调用量',
type: 'bar',
@ -284,14 +318,13 @@ export default {
},
z: 1
},
// barWidth1424
{
name: '调用量',
type: 'bar',
data: xData,
barWidth: 14,
label: {
show: false, //
show: false,
position: 'right',
color: '#50e3c2',
fontWeight: 'bold',
@ -317,21 +350,23 @@ export default {
],
backgroundColor: 'transparent'
};
this.modelChartInstance.setOption(option);
this.modelChartInstance.setOption(option, true);
},
// Token
initTokenChart() {
const chartDom = this.$refs.tokenChart;
if (!chartDom) return;
this.tokenChartInstance = echarts.init(chartDom);
this.updateTokenChart();
},
// 使M
const xData = ['千问3-max', 'kimi', '豆包', 'deepseek', '千帆'];
const yData = [5000, 4300, 3500, 3200, 2800]; //
updateTokenChart() {
if (!this.tokenChartInstance || !this.currentData?.left) return;
const tokenData = this.currentData.left.tokenUsage || [];
const xData = tokenData.map(item => item.name);
const yData = tokenData.map(item => item.value);
// Y
const maxValue = Math.max(...yData);
const yMax = Math.ceil(maxValue / 1000) * 1000 + 500;
@ -345,10 +380,10 @@ export default {
}
},
grid: {
left: '0', //
right: '10%', //
left: '0',
right: '10%',
top: '15%',
bottom: '25%', //
bottom: '25%',
containLabel: true
},
xAxis: {
@ -356,11 +391,10 @@ export default {
data: xData,
axisLabel: {
color: 'rgba(255,255,255,0.7)',
fontSize: 11, //
rotate: 0, // 15
interval: 0, //
margin: 10, // 线
//
fontSize: 11,
rotate: 0,
interval: 0,
margin: 10,
formatter: function (value) {
if (value.length > 8) {
return value.substring(0, 8) + '...';
@ -373,7 +407,7 @@ export default {
},
axisTick: {
show: false,
alignWithLabel: true //
alignWithLabel: true
}
},
yAxis: {
@ -396,12 +430,11 @@ export default {
}
},
series: [
//
{
name: 'Token',
type: 'bar',
data: yData,
barWidth: 10, //
barWidth: 10,
itemStyle: {
normal: {
barBorderRadius: [6, 6, 0, 0],
@ -411,7 +444,6 @@ export default {
},
z: 1
},
//
{
name: 'Token',
type: 'bar',
@ -444,8 +476,9 @@ export default {
],
backgroundColor: 'transparent'
};
this.tokenChartInstance.setOption(option);
this.tokenChartInstance.setOption(option, true);
},
handleResize() {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
@ -453,13 +486,15 @@ export default {
this.modelChartInstance?.resize();
this.tokenChartInstance?.resize();
}, 200);
},
// updateCharts
updateCharts() {
console.log('updateCharts called in LeftPanel');
this.updateRunningChart();
this.updateModelChart();
this.updateTokenChart();
}
},
beforeDestroy() {
this.runningChartInstance?.dispose();
this.modelChartInstance?.dispose();
this.tokenChartInstance?.dispose();
window.removeEventListener('resize', this.handleResize);
}
};
</script>
@ -480,18 +515,19 @@ export default {
position: relative;
.conter-top {
width: 100%;
position: absolute;
top: 14px;
display: flex;
align-items: center;
justify-content: space-around;
position: absolute;
top: 14px;
left: 18px;
padding: 0 18px;
// left: 18px;
.top-tit {
padding-left: 10px;
// padding-left: 10px;
span {
// font-size: 12px;
margin-left: 2px;
}
}
@ -511,10 +547,9 @@ export default {
.conter-bottom {
margin-top: -16px;
display: flex;
padding-left:10px;
padding-left: 10px;
align-items: center;
justify-content: space-around;
// padding-left: 20px;
.bottom-tit {
padding-right: 4px;
@ -591,7 +626,6 @@ export default {
//
.model-chart {
// height: 100%;
height: calc(100% - 46px);
width: 240px;
}

View File

@ -10,24 +10,24 @@
<!-- 中心TOP1 -->
<div class="circle-center">
<div class="rank">TOP1</div>
<div class="name">智能推理</div>
<div class="name">{{ appTypes[0] || '智能推理' }}</div>
</div>
<!-- 周围TOP项 -->
<div class="circle-item item-top2">
<div class="rank">TOP2</div>
<div class="name">智能训练</div>
<div class="name">{{ appTypes[1] || '智能训练' }}</div>
</div>
<div class="circle-item item-top3">
<div class="rank">TOP3</div>
<div class="name">图形渲染</div>
<div class="name">{{ appTypes[2] || '图形渲染' }}</div>
</div>
<div class="circle-item item-top4">
<div class="rank">TOP4</div>
<div class="name">蛋白质分析</div>
<div class="name">{{ appTypes[3] || '蛋白质分析' }}</div>
</div>
<div class="circle-item item-top5">
<div class="rank">TOP5</div>
<div class="name">其他</div>
<div class="name">{{ appTypes[4] || '其他' }}</div>
</div>
</div>
</div>
@ -49,7 +49,7 @@
<!-- 2 -->
<div class="row row-2">
<!-- 用户数量无背景色 -->
<!-- 用户数量 -->
<div class="user-num">
<div class="title">用户数量</div>
<div class="content">
@ -60,7 +60,7 @@
<div class="user-consume">
<div class="title">用户消费排行</div>
<div class="content">
<dv-scroll-board :config="scrollBoardConfig" style="width:95%;height:95%" />
<dv-scroll-board :config="scrollBoardConfig" :key="scrollBoardKey" ref="scrollBoardRef" style="width:95%;height:95%" />
</div>
</div>
</div>
@ -84,21 +84,14 @@ import img2 from '../images/2.png'
import img3 from '../images/3.png'
import img4 from '../images/4.png'
import img5 from '../images/5.png'
import { dataManager } from "@/utils/data-manager.js";
export default {
name: 'RightPanel',
data() {
return {
scrollBoardConfig: {
data: [
[`<div class="rank-img"><img src="${img1}" /></div>`, '用户A', '¥520000'],
[`<div class="rank-img"><img src="${img2}" /></div>`, '用户B', '¥450000'],
[`<div class="rank-img"><img src="${img3}" /></div>`, '用户B', '¥186000'],
[`<div class="rank-img"><img src="${img4}" /></div>`, '用户D', '¥120000'],
[`<div class="rank-img"><img src="${img5}" /></div>`, '用户E', '¥56000'],
[`<div class="rank-img"><img src="${img5}" /></div>`, '用户F', '¥43000'],
[`<div class="rank-img"><img src="${img5}" /></div>`, '用户G', '¥35000'],
[`<div class="rank-img"><img src="${img5}" /></div>`, '用户H', '¥28000'],
],
data: [],
index: false,
columnWidth: [50, 100, 90],
rowHeight: 28,
@ -106,39 +99,139 @@ export default {
rowNum: 5,
oddRowBGC: '#040c45',
evenRowBGC: '#17264f',
waitTime: 2000,
waitTime: 1500, //
carousel: 'single',
hoverPause: true
hoverPause: false, //
autoPlay: true //
},
resizeTimer: null
}
scrollBoardKey: 0,
resizeTimer: null,
powerChart: null,
currentData: null,
appTypes: ['智能推理', '智能训练', '图形渲染', '蛋白质分析', '其他']
};
},
created() {
this.currentData = dataManager.getData();
this.updateDisplay();
dataManager.eventBus.$on('cluster-changed', this.handleClusterChanged);
},
mounted() {
this.initPowerUseChart();
window.addEventListener('resize', this.handleResize);
//
this.$nextTick(() => {
this.startScrollBoard();
});
},
beforeDestroy() {
if (this.$refs.powerUseChart) {
const chart = echarts.getInstanceByDom(this.$refs.powerUseChart);
if (chart) chart.dispose();
}
window.removeEventListener('resize', this.handleResize);
dataManager.eventBus.$off('cluster-changed', this.handleClusterChanged);
},
methods: {
handleClusterChanged(event) {
this.currentData = event.data;
this.updateDisplay();
},
updateDisplay() {
if (!this.currentData) return;
this.appTypes = this.currentData.right?.appTypes || this.appTypes;
this.updateScrollBoard();
this.updatePowerChart();
},
updateScrollBoard() {
if (!this.currentData?.right) return;
const userData = this.currentData.right.userConsumption || [];
const imgMap = {
1: img1,
2: img2,
3: img3,
4: img4,
5: img5
};
// 5
const tableData = [];
for (let i = 0; i < Math.max(5, userData.length); i++) {
if (userData[i]) {
tableData.push([
`<div class="rank-img"><img src="${imgMap[i + 1] || img5}" /></div>`,
userData[i].name,
`¥${userData[i].amount.toLocaleString()}`
]);
} else {
tableData.push([
`<div class="rank-img"><img src="${imgMap[i + 1] || img5}" /></div>`,
`用户${i + 1}`,
'¥0'
]);
}
}
//
const newConfig = {
...this.scrollBoardConfig,
data: tableData
};
//
this.scrollBoardConfig = newConfig;
//
this.scrollBoardKey += 1;
//
this.$nextTick(() => {
this.startScrollBoard();
});
},
//
startScrollBoard() {
if (this.$refs.scrollBoardRef && this.$refs.scrollBoardRef.startRoll) {
// startRoll
this.$refs.scrollBoardRef.startRoll();
}
// startRoll访
if (this.$refs.scrollBoardRef && this.$refs.scrollBoardRef.$children[0]) {
const child = this.$refs.scrollBoardRef.$children[0];
if (child.startAnimation) {
child.startAnimation();
}
}
},
initPowerUseChart() {
const chartDom = this.$refs.powerUseChart;
if (!chartDom) return;
const myChart = echarts.init(chartDom);
this.powerChart = echarts.init(chartDom);
this.updatePowerChart();
},
//
updatePowerChart() {
if (!this.powerChart || !this.currentData?.right) return;
const powerData = this.currentData.right.powerUsage || [];
const dates = ['12/01', '12/02', '12/03', '12/04', '12/05', '12/06', '12/07'];
const powerUsage = [12300, 11500, 11500, 13400, 12900, 13500, 13200];
// y
const maxValue = Math.max(...powerUsage);
const minValue = Math.min(...powerUsage);
// y
const maxValue = Math.max(...powerData);
const minValue = Math.min(...powerData);
const yMax = Math.ceil(maxValue / 1000) * 1000 + 1000;
const yMin = Math.floor(minValue / 1000) * 1000 - 1000;
//
const maxIndex = powerUsage.indexOf(maxValue);
const markPointData = powerUsage.map((value, index) => {
const maxIndex = powerData.indexOf(maxValue);
const markPointData = powerData.map((value, index) => {
if (index === maxIndex) {
return {
name: '最高',
@ -147,16 +240,12 @@ export default {
yAxis: value,
symbol: 'circle',
symbolSize: 10,
itemStyle: {
color: '#fff',
// borderColor: '#ff4d4f',
// borderWidth: 2
},
itemStyle: { color: '#fff' },
label: {
show: true,
position: 'top',
formatter: function(params) {
return (params.value / 10000).toFixed(1) + '万'; // ""
return (params.value / 10000).toFixed(1) + '万';
},
color: '#fff',
fontWeight: 'bold',
@ -178,7 +267,7 @@ export default {
const value = params[0].value;
return params[0].name + '<br/>' +
params[0].marker + params[0].seriesName + ': ' +
value.toLocaleString() + '算力单位'; //
value.toLocaleString() + '算力单位';
}
},
grid: {
@ -201,8 +290,8 @@ export default {
},
yAxis: {
type: 'value',
min: yMin, // 使
max: yMax, // 使
min: yMin,
max: yMax,
splitNumber: 6,
splitLine: {
lineStyle: {
@ -214,7 +303,6 @@ export default {
color: 'rgba(255,255,255,0.7)',
fontSize: 12,
formatter: function(value) {
// ""1
return (value / 10000).toFixed(1) + '万';
}
},
@ -225,7 +313,7 @@ export default {
{
name: '算力使用',
type: 'line',
data: powerUsage,
data: powerData,
symbol: 'none',
symbolSize: 8,
itemStyle: {
@ -252,7 +340,7 @@ export default {
]
};
myChart.setOption(option);
this.powerChart.setOption(option, true);
},
handleResize() {
@ -264,13 +352,6 @@ export default {
}
}, 200);
}
},
beforeDestroy() {
if (this.$refs.powerUseChart) {
const chart = echarts.getInstanceByDom(this.$refs.powerUseChart);
if (chart) chart.dispose();
}
window.removeEventListener('resize', this.handleResize);
}
};
</script>

View File

@ -256,7 +256,7 @@ export default Vue.extend({
break;
}
this.$router.push({
path: '/orderManagement/orderManagement',
path: '/orderManagement/baidu',
query: query
});
}

View File

@ -332,7 +332,7 @@ export default {
}
} catch (error) {
console.error('发送验证码失败:', error);
}
},