main #63
@ -436,7 +436,7 @@ export const asyncRoutes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
component: () => import('@/views/product/productHome/productIndex/index.vue'),
|
component: () => import('@/views/product/mainPage/index.vue'),
|
||||||
name: 'ResourceOverview',
|
name: 'ResourceOverview',
|
||||||
meta: {
|
meta: {
|
||||||
title: "资源概览",
|
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>
|
<i class="iconfont icon-kefu functions" @click="handleServiceClick"></i>
|
||||||
<!-- 控制台按钮(已登录) -->
|
<!-- 控制台按钮(已登录) -->
|
||||||
<a @click="goB" v-if="loginState" class="login-btn">控制台</a>
|
<a @click="goB" v-if="loginState" class="login-btn">控制台</a>
|
||||||
<!-- 消息 -->
|
<!-- 消息 -->
|
||||||
<i class="iconfont icon-xiaoxi functions" @click="handleMessageClick"></i>
|
<i class="iconfont icon-xiaoxi functions" @click="handleMessageClick"></i>
|
||||||
<!-- 登录按钮(未登录) -->
|
<!-- 登录按钮(未登录) -->
|
||||||
<a @click="$router.push({
|
<a @click="$router.push({
|
||||||
@ -77,7 +77,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<svg @click="copyBtn" class="copy-btn" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"
|
<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;">
|
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"
|
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>
|
p-id="1521"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@ -90,8 +90,7 @@
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<!-- 验证码输入框(仅在手机号登录模式下显示) -->
|
<!-- 验证码输入框(仅在手机号登录模式下显示) -->
|
||||||
<div v-else style="display:flex;margin-top:20px;"
|
<div v-else style="display:flex;margin-top:20px;">
|
||||||
v-show="loginForm.username !== 'admin' && loginForm.password !== 'admin'">
|
|
||||||
<el-form-item prop="" class="invitecode"
|
<el-form-item prop="" class="invitecode"
|
||||||
style="background-color: white; border: 1px solid #d9d9d9;">
|
style="background-color: white; border: 1px solid #d9d9d9;">
|
||||||
<div class="user-input">
|
<div class="user-input">
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
<br>
|
<br>
|
||||||
查看集群详细信息
|
查看集群详细信息
|
||||||
</span>
|
</span>
|
||||||
<div class="btn">进入地图</div>
|
<div class="btn" @click="enterCluster(cluster)">进入地图</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item" v-else>
|
<div class="item" v-else>
|
||||||
@ -31,22 +31,28 @@
|
|||||||
<br>
|
<br>
|
||||||
查看集群详细信息
|
查看集群详细信息
|
||||||
</span>
|
</span>
|
||||||
<div class="btn">进入地图</div>
|
<div class="btn" @click="enterCluster(cluster)">进入地图</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">{{ cluster.name }}</div>
|
<div class="right">{{ cluster.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import "echarts/map/js/china.js";
|
import "echarts/map/js/china.js";
|
||||||
import { areaTwo } from "@/views/product/bigScreen/asset/cityData";
|
import { areaTwo } from "@/views/product/bigScreen/asset/cityData";
|
||||||
|
import { dataManager } from "@/utils/data-manager.js";
|
||||||
|
|
||||||
const CHART_CONSTANTS = {
|
const CHART_CONSTANTS = {
|
||||||
MAP_ZOOM: 1.23,
|
MAP_ZOOM: 1.23,
|
||||||
@ -55,7 +61,7 @@ const CHART_CONSTANTS = {
|
|||||||
BORDER_COLOR: "#389dff",
|
BORDER_COLOR: "#389dff",
|
||||||
RIPPLE_PERIOD: 5,
|
RIPPLE_PERIOD: 5,
|
||||||
RESIZE_DEBOUNCE_TIME: 300,
|
RESIZE_DEBOUNCE_TIME: 300,
|
||||||
HORIZONTAL_LENGTH: 4, // 横线长度调至4(更短,满足需求)
|
HORIZONTAL_LENGTH: 4,
|
||||||
BG_IMAGE_PATHS: {
|
BG_IMAGE_PATHS: {
|
||||||
map: require("../../../../../assets/image/map.png"),
|
map: require("../../../../../assets/image/map.png"),
|
||||||
lbx: require("../../../../../assets/image/lbx.png"),
|
lbx: require("../../../../../assets/image/lbx.png"),
|
||||||
@ -74,49 +80,49 @@ export default {
|
|||||||
return {
|
return {
|
||||||
myChart: null,
|
myChart: null,
|
||||||
resizeTimer: null,
|
resizeTimer: null,
|
||||||
// 调整labelPosition与城市坐标的间距,强化斜向延伸的视觉效果
|
currentCluster: 'default',
|
||||||
clusterConfigs: [
|
clusterConfigs: [
|
||||||
{
|
{
|
||||||
name: '北京集群',
|
name: '北京集群',
|
||||||
cityName: '北京',
|
cityName: '北京',
|
||||||
direction: 'right-top',
|
direction: 'right-top',
|
||||||
labelPosition: [121.40, 50.90], // 远离城市坐标,斜向更明显
|
labelPosition: [121.40, 50.90],
|
||||||
position: { top: '18%', left: '73%' }
|
position: { top: '18%', left: '73%' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '内蒙集群',
|
name: '内蒙集群',
|
||||||
cityName: '内蒙古',
|
cityName: '内蒙古',
|
||||||
direction: 'left',
|
direction: 'left',
|
||||||
labelPosition: [105.73, 50.83], // 远离城市坐标
|
labelPosition: [105.73, 50.83],
|
||||||
position: { top: '19%', left: '35%' }
|
position: { top: '19%', left: '34%' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '新疆集群',
|
name: '新疆集群',
|
||||||
cityName: '新疆',
|
cityName: '新疆',
|
||||||
direction: 'left',
|
direction: 'left',
|
||||||
labelPosition: [88.68, 52.77], // 远离城市坐标
|
labelPosition: [88.68, 52.77],
|
||||||
position: { top: '15.4%', left: '8%' }
|
position: { top: '15.4%', left: '8%' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '长三角集群',
|
name: '长三角集群',
|
||||||
cityName: '上海',
|
cityName: '上海',
|
||||||
direction: 'right',
|
direction: 'right',
|
||||||
labelPosition: [125, 38.23], // 远离城市坐标
|
labelPosition: [125, 38.23],
|
||||||
position: { top: '40%', left: '80.5%' }
|
position: { top: '40%', left: '80.5%' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '珠三角集群',
|
name: '珠三角集群',
|
||||||
cityName: '广东',
|
cityName: '广东',
|
||||||
direction: 'right',
|
direction: 'right',
|
||||||
labelPosition: [125.5, 28.12], // 远离城市坐标
|
labelPosition: [125.5, 28.12],
|
||||||
position: { top: '57.3%', left: '80.9%' }
|
position: { top: '57.3%', left: '80.9%' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '川渝集群',
|
name: '川渝集群',
|
||||||
cityName: '四川',
|
cityName: '四川',
|
||||||
direction: 'left',
|
direction: 'left',
|
||||||
labelPosition: [99.06, 21.8], // 远离城市坐标
|
labelPosition: [99.06, 21.8],
|
||||||
position: { top: '69.2%', left: '26%' }
|
position: { top: '69.2%', left: '24%' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -135,42 +141,42 @@ export default {
|
|||||||
"上海": [121.47, 31.23],
|
"上海": [121.47, 31.23],
|
||||||
"四川": [104.06, 30.67],
|
"四川": [104.06, 30.67],
|
||||||
"重庆": [106.50, 29.53],
|
"重庆": [106.50, 29.53],
|
||||||
// "浙江": [120.15, 30.28],
|
|
||||||
"贵州": [106.63, 26.65],
|
"贵州": [106.63, 26.65],
|
||||||
"广东": [113.26, 23.12],
|
"广东": [113.26, 23.12],
|
||||||
"香港": [114.16, 22.28],
|
"香港": [114.16, 22.28],
|
||||||
"济南": [117.00, 36.65],
|
"济南": [117.00, 36.65],
|
||||||
// "无锡": [120.30, 31.57],
|
|
||||||
"广州": [113.23, 23.16],
|
"广州": [113.23, 23.16],
|
||||||
"青岛": [120.33, 36.07],
|
"青岛": [120.33, 36.07],
|
||||||
"长沙": [113.00, 28.21],
|
"长沙": [113.00, 28.21],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
showMap(newVal) {
|
|
||||||
newVal && this.$nextTick(() => this.initChart());
|
|
||||||
},
|
|
||||||
routePath: "refreshChart",
|
|
||||||
clickableCities: { deep: true, handler: "refreshChart" }
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initChart();
|
this.initChart();
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
|
|
||||||
|
// 监听集群变化
|
||||||
|
dataManager.eventBus.$on('cluster-changed', this.handleClusterChanged);
|
||||||
|
|
||||||
|
// 初始化当前集群状态
|
||||||
|
this.currentCluster = dataManager.getCurrentCluster();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.myChart?.dispose();
|
this.myChart?.dispose();
|
||||||
clearTimeout(this.resizeTimer);
|
clearTimeout(this.resizeTimer);
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
|
dataManager.eventBus.$off('cluster-changed', this.handleClusterChanged);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleResize() {
|
handleResize() {
|
||||||
clearTimeout(this.resizeTimer);
|
clearTimeout(this.resizeTimer);
|
||||||
this.resizeTimer = setTimeout(() => this.myChart?.resize(), CHART_CONSTANTS.RESIZE_DEBOUNCE_TIME);
|
this.resizeTimer = setTimeout(() => this.myChart?.resize(), CHART_CONSTANTS.RESIZE_DEBOUNCE_TIME);
|
||||||
},
|
},
|
||||||
|
|
||||||
getClusterStyle(position) {
|
getClusterStyle(position) {
|
||||||
return position ? { position: 'absolute', ...position, zIndex: 10 } : {};
|
return position ? { position: 'absolute', ...position, zIndex: 10 } : {};
|
||||||
},
|
},
|
||||||
|
|
||||||
initChart() {
|
initChart() {
|
||||||
const chartDom = this.$refs.myEchart;
|
const chartDom = this.$refs.myEchart;
|
||||||
if (!chartDom) return;
|
if (!chartDom) return;
|
||||||
@ -179,9 +185,7 @@ export default {
|
|||||||
this.myChart.setOption(this.buildChartOption(), true);
|
this.myChart.setOption(this.buildChartOption(), true);
|
||||||
this.myChart.on('click', this.handleAreaClick);
|
this.myChart.on('click', this.handleAreaClick);
|
||||||
},
|
},
|
||||||
refreshChart() {
|
|
||||||
this.$nextTick(() => this.initChart());
|
|
||||||
},
|
|
||||||
buildChartOption() {
|
buildChartOption() {
|
||||||
return {
|
return {
|
||||||
tooltip: { show: false },
|
tooltip: { show: false },
|
||||||
@ -189,6 +193,7 @@ export default {
|
|||||||
series: [this.buildLineSeries(), this.buildEffectScatterSeries(), this.buildLabelScatterSeries()]
|
series: [this.buildLineSeries(), this.buildEffectScatterSeries(), this.buildLabelScatterSeries()]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
buildGeoConfig() {
|
buildGeoConfig() {
|
||||||
return {
|
return {
|
||||||
map: "china",
|
map: "china",
|
||||||
@ -202,6 +207,7 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
buildLineSeries() {
|
buildLineSeries() {
|
||||||
return {
|
return {
|
||||||
type: 'lines',
|
type: 'lines',
|
||||||
@ -213,6 +219,7 @@ export default {
|
|||||||
effect: { show: true, trailLength: 0, symbol: 'circle', symbolSize: 3, color: CHART_CONSTANTS.LINE_COLOR }
|
effect: { show: true, trailLength: 0, symbol: 'circle', symbolSize: 3, color: CHART_CONSTANTS.LINE_COLOR }
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
buildEffectScatterSeries() {
|
buildEffectScatterSeries() {
|
||||||
return {
|
return {
|
||||||
type: "effectScatter",
|
type: "effectScatter",
|
||||||
@ -237,18 +244,28 @@ export default {
|
|||||||
effect: { show: true, scaleSize: 3, period: 4, color: CHART_CONSTANTS.LINE_COLOR, shadowBlur: 10 }
|
effect: { show: true, scaleSize: 3, period: 4, color: CHART_CONSTANTS.LINE_COLOR, shadowBlur: 10 }
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
buildLabelScatterSeries() {
|
buildLabelScatterSeries() {
|
||||||
return {
|
return {
|
||||||
type: 'scatter',
|
type: 'scatter',
|
||||||
coordinateSystem: 'geo',
|
coordinateSystem: 'geo',
|
||||||
data: this.clusterConfigs.map(c => ({
|
data: this.clusterConfigs.map(c => ({
|
||||||
value: c.labelPosition,
|
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'
|
symbol: 'none'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// 核心修改:强制实现 城市斜向→更短横线→标签 的折线形态
|
|
||||||
buildLineData() {
|
buildLineData() {
|
||||||
const shortLen = CHART_CONSTANTS.HORIZONTAL_LENGTH;
|
const shortLen = CHART_CONSTANTS.HORIZONTAL_LENGTH;
|
||||||
return this.clusterConfigs
|
return this.clusterConfigs
|
||||||
@ -260,34 +277,30 @@ export default {
|
|||||||
const [cityLon, cityLat] = cityCoord;
|
const [cityLon, cityLat] = cityCoord;
|
||||||
let lineStart, lineEnd;
|
let lineStart, lineEnd;
|
||||||
|
|
||||||
// 1. 计算极短水平横线的两端点(长度固定为shortLen)
|
|
||||||
switch (cluster.direction) {
|
switch (cluster.direction) {
|
||||||
case 'left':
|
case 'left':
|
||||||
// 横线在标签左侧,极短
|
lineEnd = [labelLon, labelLat];
|
||||||
lineEnd = [labelLon, labelLat]; // 横线终点 = 标签位置
|
lineStart = [labelLon + shortLen, labelLat];
|
||||||
lineStart = [labelLon + shortLen, labelLat]; // 横线起点 = 标签右移shortLen
|
|
||||||
break;
|
break;
|
||||||
case 'right':
|
case 'right':
|
||||||
default:
|
default:
|
||||||
// 横线在标签左侧,极短
|
lineStart = [labelLon - shortLen, labelLat];
|
||||||
lineStart = [labelLon - shortLen, labelLat]; // 横线起点 = 标签左移shortLen
|
lineEnd = [labelLon, labelLat];
|
||||||
lineEnd = [labelLon, labelLat]; // 横线终点 = 标签位置
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 折线顺序:城市(斜向) → 横线起点 → 横线终点(极短横线) → 标签
|
|
||||||
// 城市到横线起点的距离足够远,斜向效果明显
|
|
||||||
return {
|
return {
|
||||||
coords: [
|
coords: [
|
||||||
[cityLon, cityLat], // 起点:城市
|
[cityLon, cityLat],
|
||||||
lineStart, // 中点1:横线起点(城市到这一步是明显斜向)
|
lineStart,
|
||||||
lineEnd, // 中点2:横线终点(极短水平横线)
|
lineEnd,
|
||||||
[labelLon, labelLat]// 终点:标签
|
[labelLon, labelLat]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(item => item !== null);
|
.filter(item => item !== null);
|
||||||
},
|
},
|
||||||
|
|
||||||
buildScatterData() {
|
buildScatterData() {
|
||||||
return Object.keys(this.geoCoordMap).map(key => ({
|
return Object.keys(this.geoCoordMap).map(key => ({
|
||||||
name: key,
|
name: key,
|
||||||
@ -295,11 +308,77 @@ export default {
|
|||||||
symbolSize: key === '北京' ? 18 : 12
|
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) {
|
handleAreaClick(params) {
|
||||||
if (!this.clickableCities?.includes(params?.name)) return;
|
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;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0 8px;
|
|
||||||
background-color: rgba(147, 112, 219, 0.3);
|
background-color: rgba(147, 112, 219, 0.3);
|
||||||
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left .title {
|
.left .title {
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item .btn {
|
.item .btn {
|
||||||
color: #FDBD00;
|
color: #FDBD00;
|
||||||
cursor: pointer;
|
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 {
|
.item .right {
|
||||||
@ -407,6 +507,46 @@ export default {
|
|||||||
width: 20px;
|
width: 20px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background-color: #000;
|
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 {
|
.sortBox {
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<!-- 需求算力 -->
|
<!-- 需求算力 -->
|
||||||
<div class="top-tit">
|
<div class="top-tit">
|
||||||
<div class="top-text">
|
<div class="top-text">
|
||||||
1500P
|
{{ headerData.demandPower }}P
|
||||||
</div>
|
</div>
|
||||||
<div class="btm-text">
|
<div class="btm-text">
|
||||||
需求算力
|
需求算力
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<!-- 供给算力 -->
|
<!-- 供给算力 -->
|
||||||
<div class="top-tit">
|
<div class="top-tit">
|
||||||
<div class="top-text">
|
<div class="top-text">
|
||||||
35320P
|
{{ headerData.supplyPower }}P
|
||||||
</div>
|
</div>
|
||||||
<div class="btm-text">
|
<div class="btm-text">
|
||||||
供给算力
|
供给算力
|
||||||
@ -23,7 +23,7 @@
|
|||||||
<!-- 集群数 -->
|
<!-- 集群数 -->
|
||||||
<div class="top-tit">
|
<div class="top-tit">
|
||||||
<div class="top-text">
|
<div class="top-text">
|
||||||
7
|
{{ headerData.clusterCount }}
|
||||||
</div>
|
</div>
|
||||||
<div class="btm-text">
|
<div class="btm-text">
|
||||||
集群数量
|
集群数量
|
||||||
@ -32,7 +32,7 @@
|
|||||||
<!-- 客户数量 -->
|
<!-- 客户数量 -->
|
||||||
<div class="top-tit">
|
<div class="top-tit">
|
||||||
<div class="top-text">
|
<div class="top-text">
|
||||||
352
|
{{ headerData.customerCount }}
|
||||||
</div>
|
</div>
|
||||||
<div class="btm-text">
|
<div class="btm-text">
|
||||||
客户数量
|
客户数量
|
||||||
@ -41,7 +41,7 @@
|
|||||||
<!-- 芯片数量 -->
|
<!-- 芯片数量 -->
|
||||||
<div class="top-tit">
|
<div class="top-tit">
|
||||||
<div class="top-text">
|
<div class="top-text">
|
||||||
75368
|
{{ headerData.chipCount }}
|
||||||
</div>
|
</div>
|
||||||
<div class="btm-text">
|
<div class="btm-text">
|
||||||
芯片数量
|
芯片数量
|
||||||
@ -50,7 +50,7 @@
|
|||||||
<!-- 模型数量 -->
|
<!-- 模型数量 -->
|
||||||
<div class="top-tit">
|
<div class="top-tit">
|
||||||
<div class="top-text">
|
<div class="top-text">
|
||||||
75
|
{{ headerData.modelCount }}
|
||||||
</div>
|
</div>
|
||||||
<div class="btm-text">
|
<div class="btm-text">
|
||||||
模型数量
|
模型数量
|
||||||
@ -61,9 +61,14 @@
|
|||||||
|
|
||||||
<!-- 添加地图组件 -->
|
<!-- 添加地图组件 -->
|
||||||
<div class="map-container">
|
<div class="map-container">
|
||||||
<!-- 传递必需的props -->
|
<Map
|
||||||
<Map :route-path="currentRoutePath" :clickable-cities="clickableCities" :show-map.sync="showMap"
|
:route-path="currentRoutePath"
|
||||||
@area-click="handleAreaClick" />
|
:clickable-cities="clickableCities"
|
||||||
|
:show-map.sync="showMap"
|
||||||
|
@area-click="handleAreaClick"
|
||||||
|
@cluster-selected="handleClusterSelected"
|
||||||
|
@reset-view="handleResetView"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- 底部 -->
|
<!-- 底部 -->
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
@ -88,74 +93,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 概览 -->
|
<!-- 概览 -->
|
||||||
<div class="overview" v-show="activeTab === 0">
|
<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">
|
<div class="overview-title">
|
||||||
<span>2350</span>
|
<span>{{ item.value }}</span>
|
||||||
<span>CPU</span>
|
<span>{{ item.name }}</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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="overview-img">
|
<div class="overview-img">
|
||||||
<img src="../images/btm.png" alt="">
|
<img src="../images/btm.png" alt="">
|
||||||
@ -164,74 +110,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 概览two - 根据激活标签显示,不修改原有布局样式 -->
|
<!-- 概览two - 根据激活标签显示,不修改原有布局样式 -->
|
||||||
<div class="overview-two" v-show="activeTab === 1">
|
<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">
|
<div class="overview-title">
|
||||||
<span>0</span>
|
<span>{{ item.value }}</span>
|
||||||
<span>CPU</span>
|
<span>{{ item.name }}</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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="overview-img">
|
<div class="overview-img">
|
||||||
<img src="../images/btm.png" alt="">
|
<img src="../images/btm.png" alt="">
|
||||||
@ -246,6 +133,7 @@
|
|||||||
<script>
|
<script>
|
||||||
// 引入地图组件
|
// 引入地图组件
|
||||||
import Map from './Map.vue';
|
import Map from './Map.vue';
|
||||||
|
import { dataManager } from '@/utils/data-manager';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'VmCenter',
|
name: 'VmCenter',
|
||||||
@ -254,38 +142,80 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// 获取当前路由路径
|
|
||||||
currentRoutePath: this.$route ? this.$route.path : '/',
|
currentRoutePath: this.$route ? this.$route.path : '/',
|
||||||
// 定义可点击的城市列表 - 根据实际需求修改
|
|
||||||
clickableCities: ['北京', '上海', '广州', '深圳', '济南', '无锡', '青岛', '长沙', '天津', '南京', '杭州', '成都', '重庆', '武汉'],
|
clickableCities: ['北京', '上海', '广州', '深圳', '济南', '无锡', '青岛', '长沙', '天津', '南京', '杭州', '成都', '重庆', '武汉'],
|
||||||
// 控制地图显示
|
|
||||||
showMap: true,
|
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: {
|
created() {
|
||||||
// 监听路由变化,更新routePath
|
// 初始化数据
|
||||||
'$route.path'(newPath) {
|
this.currentData = dataManager.getData();
|
||||||
this.currentRoutePath = newPath;
|
this.updateDisplay();
|
||||||
}
|
|
||||||
|
// 监听数据变化
|
||||||
|
dataManager.eventBus.$on('cluster-changed', this.handleClusterChanged);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 如果当前没有路由对象,可以设置一个默认值
|
|
||||||
if (!this.$route) {
|
if (!this.$route) {
|
||||||
this.currentRoutePath = window.location.pathname;
|
this.currentRoutePath = window.location.pathname;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
dataManager.eventBus.$off('cluster-changed', this.handleClusterChanged);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleAreaClick(params) {
|
handleAreaClick(params) {
|
||||||
console.log('区域被点击:', params);
|
console.log('区域被点击:', params);
|
||||||
// 这里可以处理区域点击事件,比如跳转到详情页面等
|
|
||||||
},
|
},
|
||||||
// 新增:标签切换方法
|
|
||||||
|
handleClusterSelected(clusterName) {
|
||||||
|
console.log('接收到集群选择:', clusterName);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleResetView() {
|
||||||
|
this.showMap = true;
|
||||||
|
},
|
||||||
|
|
||||||
switchTab(tabIndex) {
|
switchTab(tabIndex) {
|
||||||
console.log('切换到标签:', tabIndex);
|
console.log('切换到标签:', tabIndex);
|
||||||
|
|
||||||
this.activeTab = 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="title">异构芯片规模</div>
|
||||||
<div class="conter">
|
<div class="conter">
|
||||||
<div class="conter-top">
|
<div class="conter-top">
|
||||||
<div class="top-tit">33000<span>P</span></div>
|
<div class="top-tit">{{ chipData.NVIDIA }}<span>P</span></div>
|
||||||
<div class="top-tit">5000<span>P</span></div>
|
<div class="top-tit">{{ chipData.ascend }}<span>P</span></div>
|
||||||
<div class="top-tit">8000<span>P</span></div>
|
<div class="top-tit">{{ chipData.supercomputer }}<span>P</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="conter-center">
|
<div class="conter-center">
|
||||||
<img src="../images/3D.png" alt="">
|
<img src="../images/3D.png" alt="">
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<div class="title">集群规模</div>
|
<div class="title">集群规模</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<dv-decoration-9 style="width:200px;height:200px;color: #fff;font-size: 20px; font-weight: 600;">
|
<dv-decoration-9 style="width:200px;height:200px;color: #fff;font-size: 20px; font-weight: 600;">
|
||||||
5100P
|
{{ chipData.total }}P
|
||||||
</dv-decoration-9>
|
</dv-decoration-9>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -54,44 +54,81 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
import { dataManager } from "@/utils/data-manager.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
name: 'LeftPanel',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
runningChartInstance: null,
|
runningChartInstance: null,
|
||||||
modelChartInstance: null,
|
modelChartInstance: null,
|
||||||
tokenChartInstance: 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() {
|
mounted() {
|
||||||
this.initRunningChart();
|
this.initRunningChart();
|
||||||
this.initModelChart();
|
this.initModelChart();
|
||||||
this.initTokenChart();
|
this.initTokenChart();
|
||||||
window.addEventListener('resize', this.handleResize);
|
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: {
|
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() {
|
initRunningChart() {
|
||||||
const chartDom = this.$refs.runningChart;
|
const chartDom = this.$refs.runningChart;
|
||||||
if (!chartDom) return;
|
if (!chartDom) return;
|
||||||
|
|
||||||
this.runningChartInstance = echarts.init(chartDom);
|
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 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 maxValue = Math.max(...yData);
|
||||||
const minValue = Math.min(...yData);
|
const minValue = Math.min(...yData);
|
||||||
|
|
||||||
// 计算合适的y轴最大最小值(留出一些空间)
|
|
||||||
const yMax = Math.ceil(maxValue / 1000) * 1000 + 1000;
|
const yMax = Math.ceil(maxValue / 1000) * 1000 + 1000;
|
||||||
const yMin = Math.floor(minValue / 1000) * 1000 - 1000;
|
const yMin = Math.floor(minValue / 1000) * 1000 - 1000;
|
||||||
|
|
||||||
// 找出最大值及其索引(用于标记点)
|
|
||||||
const maxIndex = yData.indexOf(maxValue);
|
const maxIndex = yData.indexOf(maxValue);
|
||||||
|
|
||||||
const markPointData = yData.map((value, index) => {
|
const markPointData = yData.map((value, index) => {
|
||||||
if (index === maxIndex) {
|
if (index === maxIndex) {
|
||||||
return {
|
return {
|
||||||
@ -101,16 +138,12 @@ export default {
|
|||||||
yAxis: value,
|
yAxis: value,
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
symbolSize: 10,
|
symbolSize: 10,
|
||||||
itemStyle: {
|
itemStyle: { color: '#fff' },
|
||||||
color: '#fff',
|
|
||||||
// borderColor: '#ff4d4f',
|
|
||||||
// borderWidth: 2
|
|
||||||
},
|
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
formatter: function (params) {
|
formatter: function (params) {
|
||||||
return (params.value / 10000).toFixed(1) + '万'; // 转换为"万"单位
|
return (params.value / 10000).toFixed(1) + '万';
|
||||||
},
|
},
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
@ -131,7 +164,7 @@ export default {
|
|||||||
const value = params[0].value;
|
const value = params[0].value;
|
||||||
return params[0].name + '<br/>' +
|
return params[0].name + '<br/>' +
|
||||||
params[0].marker + params[0].seriesName + ': ' +
|
params[0].marker + params[0].seriesName + ': ' +
|
||||||
value.toLocaleString() + '个'; // 添加千分位分隔符
|
value.toLocaleString() + '个';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
@ -154,8 +187,8 @@ export default {
|
|||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
min: yMin, // 使用动态计算的最小值
|
min: yMin,
|
||||||
max: yMax, // 使用动态计算的最大值
|
max: yMax,
|
||||||
splitNumber: 6,
|
splitNumber: 6,
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
@ -167,7 +200,6 @@ export default {
|
|||||||
color: 'rgba(255,255,255,0.7)',
|
color: 'rgba(255,255,255,0.7)',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
formatter: function (value) {
|
formatter: function (value) {
|
||||||
// 将数值转换为"万"单位显示,保留1位小数
|
|
||||||
return (value / 10000).toFixed(1) + '万';
|
return (value / 10000).toFixed(1) + '万';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -206,20 +238,23 @@ export default {
|
|||||||
backgroundColor: 'transparent'
|
backgroundColor: 'transparent'
|
||||||
};
|
};
|
||||||
|
|
||||||
this.runningChartInstance.setOption(option);
|
this.runningChartInstance.setOption(option, true);
|
||||||
},
|
},
|
||||||
// 模型调用量
|
|
||||||
initModelChart() {
|
initModelChart() {
|
||||||
const chartDom = this.$refs.modelChart;
|
const chartDom = this.$refs.modelChart;
|
||||||
if (!chartDom) return;
|
if (!chartDom) return;
|
||||||
|
|
||||||
this.modelChartInstance = echarts.init(chartDom);
|
this.modelChartInstance = echarts.init(chartDom);
|
||||||
|
this.updateModelChart();
|
||||||
|
},
|
||||||
|
|
||||||
// 使用提供的表格数据
|
updateModelChart() {
|
||||||
const yData = ['deepseek', '通义千问', 'kimi', '豆包', '文心一言', '元宝'];
|
if (!this.modelChartInstance || !this.currentData?.left) return;
|
||||||
const xData = [105, 78, 70, 120, 60, 85];
|
|
||||||
|
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 maxValue = Math.max(...xData);
|
||||||
const xMax = Math.ceil(maxValue / 50) * 50 + 10;
|
const xMax = Math.ceil(maxValue / 50) * 50 + 10;
|
||||||
|
|
||||||
@ -269,7 +304,6 @@ export default {
|
|||||||
axisTick: { show: false }
|
axisTick: { show: false }
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
// 底层:立体背景层(调宽barWidth,从10改为20)
|
|
||||||
{
|
{
|
||||||
name: '调用量',
|
name: '调用量',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
@ -284,14 +318,13 @@ export default {
|
|||||||
},
|
},
|
||||||
z: 1
|
z: 1
|
||||||
},
|
},
|
||||||
// 上层:主体渐变层(调宽barWidth,从14改为24;关闭右侧数据展示)
|
|
||||||
{
|
{
|
||||||
name: '调用量',
|
name: '调用量',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: xData,
|
data: xData,
|
||||||
barWidth: 14,
|
barWidth: 14,
|
||||||
label: {
|
label: {
|
||||||
show: false, // 关闭右侧数据标签展示
|
show: false,
|
||||||
position: 'right',
|
position: 'right',
|
||||||
color: '#50e3c2',
|
color: '#50e3c2',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
@ -317,21 +350,23 @@ export default {
|
|||||||
],
|
],
|
||||||
backgroundColor: 'transparent'
|
backgroundColor: 'transparent'
|
||||||
};
|
};
|
||||||
this.modelChartInstance.setOption(option);
|
this.modelChartInstance.setOption(option, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Token调用量
|
|
||||||
initTokenChart() {
|
initTokenChart() {
|
||||||
const chartDom = this.$refs.tokenChart;
|
const chartDom = this.$refs.tokenChart;
|
||||||
if (!chartDom) return;
|
if (!chartDom) return;
|
||||||
|
|
||||||
this.tokenChartInstance = echarts.init(chartDom);
|
this.tokenChartInstance = echarts.init(chartDom);
|
||||||
|
this.updateTokenChart();
|
||||||
|
},
|
||||||
|
|
||||||
// 使用提供的表格数据(单位是M,即百万)
|
updateTokenChart() {
|
||||||
const xData = ['千问3-max', 'kimi', '豆包', 'deepseek', '千帆'];
|
if (!this.tokenChartInstance || !this.currentData?.left) return;
|
||||||
const yData = [5000, 4300, 3500, 3200, 2800]; // 单位:百万
|
|
||||||
|
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 maxValue = Math.max(...yData);
|
||||||
const yMax = Math.ceil(maxValue / 1000) * 1000 + 500;
|
const yMax = Math.ceil(maxValue / 1000) * 1000 + 500;
|
||||||
|
|
||||||
@ -345,10 +380,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: '0', // 增大左侧间距,预留标签空间
|
left: '0',
|
||||||
right: '10%', // 平衡右侧间距
|
right: '10%',
|
||||||
top: '15%',
|
top: '15%',
|
||||||
bottom: '25%', // 增大底部间距,防止标签截断
|
bottom: '25%',
|
||||||
containLabel: true
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
@ -356,11 +391,10 @@ export default {
|
|||||||
data: xData,
|
data: xData,
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: 'rgba(255,255,255,0.7)',
|
color: 'rgba(255,255,255,0.7)',
|
||||||
fontSize: 11, // 减小字体,避免重叠
|
fontSize: 11,
|
||||||
rotate: 0, // 保持水平显示,如需防重叠可改为15
|
rotate: 0,
|
||||||
interval: 0, // 强制显示所有标签,不隐藏任何一个
|
interval: 0,
|
||||||
margin: 10, // 增大与轴线的间距
|
margin: 10,
|
||||||
// 长标签兜底处理
|
|
||||||
formatter: function (value) {
|
formatter: function (value) {
|
||||||
if (value.length > 8) {
|
if (value.length > 8) {
|
||||||
return value.substring(0, 8) + '...';
|
return value.substring(0, 8) + '...';
|
||||||
@ -373,7 +407,7 @@ export default {
|
|||||||
},
|
},
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
alignWithLabel: true // 刻度与标签对齐,优化布局
|
alignWithLabel: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
@ -396,12 +430,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
// 底层
|
|
||||||
{
|
{
|
||||||
name: 'Token',
|
name: 'Token',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: yData,
|
data: yData,
|
||||||
barWidth: 10, // 减小柱状图宽度,避免挤压标签
|
barWidth: 10,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
normal: {
|
normal: {
|
||||||
barBorderRadius: [6, 6, 0, 0],
|
barBorderRadius: [6, 6, 0, 0],
|
||||||
@ -411,7 +444,6 @@ export default {
|
|||||||
},
|
},
|
||||||
z: 1
|
z: 1
|
||||||
},
|
},
|
||||||
// 上层
|
|
||||||
{
|
{
|
||||||
name: 'Token',
|
name: 'Token',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
@ -444,8 +476,9 @@ export default {
|
|||||||
],
|
],
|
||||||
backgroundColor: 'transparent'
|
backgroundColor: 'transparent'
|
||||||
};
|
};
|
||||||
this.tokenChartInstance.setOption(option);
|
this.tokenChartInstance.setOption(option, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleResize() {
|
handleResize() {
|
||||||
clearTimeout(this.resizeTimer);
|
clearTimeout(this.resizeTimer);
|
||||||
this.resizeTimer = setTimeout(() => {
|
this.resizeTimer = setTimeout(() => {
|
||||||
@ -453,13 +486,15 @@ export default {
|
|||||||
this.modelChartInstance?.resize();
|
this.modelChartInstance?.resize();
|
||||||
this.tokenChartInstance?.resize();
|
this.tokenChartInstance?.resize();
|
||||||
}, 200);
|
}, 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>
|
</script>
|
||||||
@ -480,18 +515,19 @@ export default {
|
|||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.conter-top {
|
.conter-top {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
position: absolute;
|
padding: 0 18px;
|
||||||
top: 14px;
|
// left: 18px;
|
||||||
left: 18px;
|
|
||||||
|
|
||||||
.top-tit {
|
.top-tit {
|
||||||
padding-left: 10px;
|
// padding-left: 10px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
// font-size: 12px;
|
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -511,10 +547,9 @@ export default {
|
|||||||
.conter-bottom {
|
.conter-bottom {
|
||||||
margin-top: -16px;
|
margin-top: -16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-left:10px;
|
padding-left: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
// padding-left: 20px;
|
|
||||||
|
|
||||||
.bottom-tit {
|
.bottom-tit {
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
@ -591,7 +626,6 @@ export default {
|
|||||||
|
|
||||||
// 模型调用
|
// 模型调用
|
||||||
.model-chart {
|
.model-chart {
|
||||||
// height: 100%;
|
|
||||||
height: calc(100% - 46px);
|
height: calc(100% - 46px);
|
||||||
width: 240px;
|
width: 240px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,24 +10,24 @@
|
|||||||
<!-- 中心TOP1 -->
|
<!-- 中心TOP1 -->
|
||||||
<div class="circle-center">
|
<div class="circle-center">
|
||||||
<div class="rank">TOP1</div>
|
<div class="rank">TOP1</div>
|
||||||
<div class="name">智能推理</div>
|
<div class="name">{{ appTypes[0] || '智能推理' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 周围TOP项 -->
|
<!-- 周围TOP项 -->
|
||||||
<div class="circle-item item-top2">
|
<div class="circle-item item-top2">
|
||||||
<div class="rank">TOP2</div>
|
<div class="rank">TOP2</div>
|
||||||
<div class="name">智能训练</div>
|
<div class="name">{{ appTypes[1] || '智能训练' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="circle-item item-top3">
|
<div class="circle-item item-top3">
|
||||||
<div class="rank">TOP3</div>
|
<div class="rank">TOP3</div>
|
||||||
<div class="name">图形渲染</div>
|
<div class="name">{{ appTypes[2] || '图形渲染' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="circle-item item-top4">
|
<div class="circle-item item-top4">
|
||||||
<div class="rank">TOP4</div>
|
<div class="rank">TOP4</div>
|
||||||
<div class="name">蛋白质分析</div>
|
<div class="name">{{ appTypes[3] || '蛋白质分析' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="circle-item item-top5">
|
<div class="circle-item item-top5">
|
||||||
<div class="rank">TOP5</div>
|
<div class="rank">TOP5</div>
|
||||||
<div class="name">其他</div>
|
<div class="name">{{ appTypes[4] || '其他' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
<!-- 第 2 行 -->
|
<!-- 第 2 行 -->
|
||||||
<div class="row row-2">
|
<div class="row row-2">
|
||||||
<!-- 用户数量(无背景色) -->
|
<!-- 用户数量 -->
|
||||||
<div class="user-num">
|
<div class="user-num">
|
||||||
<div class="title">用户数量</div>
|
<div class="title">用户数量</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@ -60,7 +60,7 @@
|
|||||||
<div class="user-consume">
|
<div class="user-consume">
|
||||||
<div class="title">用户消费排行</div>
|
<div class="title">用户消费排行</div>
|
||||||
<div class="content">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -84,21 +84,14 @@ import img2 from '../images/2.png'
|
|||||||
import img3 from '../images/3.png'
|
import img3 from '../images/3.png'
|
||||||
import img4 from '../images/4.png'
|
import img4 from '../images/4.png'
|
||||||
import img5 from '../images/5.png'
|
import img5 from '../images/5.png'
|
||||||
|
import { dataManager } from "@/utils/data-manager.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
name: 'RightPanel',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
scrollBoardConfig: {
|
scrollBoardConfig: {
|
||||||
data: [
|
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'],
|
|
||||||
],
|
|
||||||
index: false,
|
index: false,
|
||||||
columnWidth: [50, 100, 90],
|
columnWidth: [50, 100, 90],
|
||||||
rowHeight: 28,
|
rowHeight: 28,
|
||||||
@ -106,39 +99,139 @@ export default {
|
|||||||
rowNum: 5,
|
rowNum: 5,
|
||||||
oddRowBGC: '#040c45',
|
oddRowBGC: '#040c45',
|
||||||
evenRowBGC: '#17264f',
|
evenRowBGC: '#17264f',
|
||||||
waitTime: 2000,
|
waitTime: 1500, // 缩短等待时间,加快滚动
|
||||||
carousel: 'single',
|
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() {
|
mounted() {
|
||||||
this.initPowerUseChart();
|
this.initPowerUseChart();
|
||||||
window.addEventListener('resize', this.handleResize);
|
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: {
|
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() {
|
initPowerUseChart() {
|
||||||
const chartDom = this.$refs.powerUseChart;
|
const chartDom = this.$refs.powerUseChart;
|
||||||
if (!chartDom) return;
|
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 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(...powerData);
|
||||||
const maxValue = Math.max(...powerUsage);
|
const minValue = Math.min(...powerData);
|
||||||
const minValue = Math.min(...powerUsage);
|
|
||||||
|
|
||||||
// 计算合适的y轴最大最小值(留出一些空间)
|
|
||||||
const yMax = Math.ceil(maxValue / 1000) * 1000 + 1000;
|
const yMax = Math.ceil(maxValue / 1000) * 1000 + 1000;
|
||||||
const yMin = Math.floor(minValue / 1000) * 1000 - 1000;
|
const yMin = Math.floor(minValue / 1000) * 1000 - 1000;
|
||||||
|
|
||||||
// 找出最大值及其索引(用于标记点)
|
const maxIndex = powerData.indexOf(maxValue);
|
||||||
const maxIndex = powerUsage.indexOf(maxValue);
|
const markPointData = powerData.map((value, index) => {
|
||||||
|
|
||||||
const markPointData = powerUsage.map((value, index) => {
|
|
||||||
if (index === maxIndex) {
|
if (index === maxIndex) {
|
||||||
return {
|
return {
|
||||||
name: '最高',
|
name: '最高',
|
||||||
@ -147,16 +240,12 @@ export default {
|
|||||||
yAxis: value,
|
yAxis: value,
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
symbolSize: 10,
|
symbolSize: 10,
|
||||||
itemStyle: {
|
itemStyle: { color: '#fff' },
|
||||||
color: '#fff',
|
|
||||||
// borderColor: '#ff4d4f',
|
|
||||||
// borderWidth: 2
|
|
||||||
},
|
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
formatter: function(params) {
|
formatter: function(params) {
|
||||||
return (params.value / 10000).toFixed(1) + '万'; // 转换为"万"单位
|
return (params.value / 10000).toFixed(1) + '万';
|
||||||
},
|
},
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
@ -178,7 +267,7 @@ export default {
|
|||||||
const value = params[0].value;
|
const value = params[0].value;
|
||||||
return params[0].name + '<br/>' +
|
return params[0].name + '<br/>' +
|
||||||
params[0].marker + params[0].seriesName + ': ' +
|
params[0].marker + params[0].seriesName + ': ' +
|
||||||
value.toLocaleString() + '算力单位'; // 添加千分位分隔符
|
value.toLocaleString() + '算力单位';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
@ -201,8 +290,8 @@ export default {
|
|||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
min: yMin, // 使用动态计算的最小值
|
min: yMin,
|
||||||
max: yMax, // 使用动态计算的最大值
|
max: yMax,
|
||||||
splitNumber: 6,
|
splitNumber: 6,
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
@ -214,7 +303,6 @@ export default {
|
|||||||
color: 'rgba(255,255,255,0.7)',
|
color: 'rgba(255,255,255,0.7)',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
formatter: function(value) {
|
formatter: function(value) {
|
||||||
// 将数值转换为"万"单位显示,保留1位小数
|
|
||||||
return (value / 10000).toFixed(1) + '万';
|
return (value / 10000).toFixed(1) + '万';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -225,7 +313,7 @@ export default {
|
|||||||
{
|
{
|
||||||
name: '算力使用',
|
name: '算力使用',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: powerUsage,
|
data: powerData,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
symbolSize: 8,
|
symbolSize: 8,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
@ -252,7 +340,7 @@ export default {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
myChart.setOption(option);
|
this.powerChart.setOption(option, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleResize() {
|
handleResize() {
|
||||||
@ -264,13 +352,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
if (this.$refs.powerUseChart) {
|
|
||||||
const chart = echarts.getInstanceByDom(this.$refs.powerUseChart);
|
|
||||||
if (chart) chart.dispose();
|
|
||||||
}
|
|
||||||
window.removeEventListener('resize', this.handleResize);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -256,7 +256,7 @@ export default Vue.extend({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/orderManagement/orderManagement',
|
path: '/orderManagement/baidu',
|
||||||
query: query
|
query: query
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -332,7 +332,7 @@ export default {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('发送验证码失败:', error);
|
console.error('发送验证码失败:', error);
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user