commit
9fef2edaf3
@ -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: "资源概览",
|
||||
|
||||
260
f/web-kboss/src/utils/data-manager.js
Normal file
260
f/web-kboss/src/utils/data-manager.js
Normal 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 };
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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=供给概览(对应overview),1=需求概览(对应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 || []
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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: [
|
||||
// 底层:立体背景层(调宽barWidth,从10改为20)
|
||||
{
|
||||
name: '调用量',
|
||||
type: 'bar',
|
||||
@ -284,14 +318,13 @@ export default {
|
||||
},
|
||||
z: 1
|
||||
},
|
||||
// 上层:主体渐变层(调宽barWidth,从14改为24;关闭右侧数据展示)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -256,7 +256,7 @@ export default Vue.extend({
|
||||
break;
|
||||
}
|
||||
this.$router.push({
|
||||
path: '/orderManagement/orderManagement',
|
||||
path: '/orderManagement/baidu',
|
||||
query: query
|
||||
});
|
||||
}
|
||||
|
||||
@ -332,7 +332,7 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送验证码失败:', error);
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user