main #58

Merged
charles merged 2 commits from main into prod 2026-01-13 11:13:37 +08:00
34 changed files with 2409 additions and 75 deletions

View File

@ -18,6 +18,7 @@
},
"dependencies": {
"@form-create/element-ui": "^2.5.30",
"@jiaminghi/data-view": "^2.10.0",
"@xterm/xterm": "^5.5.0",
"amfe-flexible": "^2.2.1",
"axios": "0.18.1",
@ -29,7 +30,8 @@
"decimal.js": "^10.4.3",
"driver.js": "0.9.5",
"dropzone": "5.5.1",
"echarts": "4.2.1",
"echarts": "^4.9.0",
"echarts-gl": "^1.1.2",
"element-ui": "^2.15.14",
"file-saver": "^2.0.1",
"fuse.js": "3.4.4",
@ -75,7 +77,7 @@
"autoprefixer": "9.5.1",
"babel-eslint": "10.1.0",
"babel-jest": "23.6.0",
"babel-loader": "^9.1.3",
"babel-loader": "^8.2.5",
"babel-plugin-dynamic-import-node": "2.3.3",
"bpmn-js": "^13.2.2",
"chalk": "2.4.2",

View File

@ -509,7 +509,7 @@ export default {
this.searchDialogVisible = true
},
goScreen() {
window.open('https://www.kaiyuancloud.cn/dev/#/screen/k8sRescource', '_blank');
window.open('https://www.opencomputing.cn/#/screen/k8sRescource', '_blank');
},
channelFirstBtn() {
reqApplyChannel({ user_id: this.userId }).then(res => {

View File

@ -64,9 +64,14 @@ sessionStorage.setItem('client_uuid', uuidv4())
// 引入form-create 表单生成器
import formCreate from "@form-create/element-ui"
Vue.use(formCreate);
// ============ 引入 DataV 数据可视化库 (Vue 2.x 版本) ============
import dataV from '@jiaminghi/data-view'
// 使用 DataV
Vue.use(dataV)
// ============ DataV 引入完成 ============
import * as filters from './filters' // global filters
import HappyScroll from 'vue-happy-scroll'

View File

@ -181,7 +181,7 @@ export const constantRoutes = [
{
hidden: true, path: '/screen', name: 'screen', title: '可视化大屏', meta: {
title: "可视化大屏", fullPath: "/operation/analyze/screen",
}, component: () => import('@/views/product/bigScreen/index.vue'), children: [{
}, component: () => import('@/views/product/bigScreen/Newscreen/index.vue'), children: [{
path: "index",
title: '可视化首页',
component: () => import('@/views/product/bigScreen/mainPage/index.vue'),

View File

@ -264,7 +264,7 @@ const actions = {
// 如果权限列表包含空路径,认为用户有所有权限
accessedRoutes = asyncRoutes || [];
} else {
// 使用修复后的过滤函数,传入用户角色和设备类型
// 传入用户角色和设备类型
accessedRoutes = filterAsyncRoutes(asyncRoutes, auths, userRoles, deviceType);
}
} else {
@ -272,7 +272,7 @@ const actions = {
accessedRoutes = [];
}
// 新增:为普通用户添加订单管理和资源管理路由以及新的五个客户菜单
// 为普通用户添加订单管理和资源管理路由以及新的五个客户菜单
console.log("为用户添加特定路由");
const userSpecificRoutes = addUserRoutes(asyncRoutes, userType, orgType, userRoles, deviceType);

View File

@ -0,0 +1,15 @@
<template>
<div>
历史订单
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
</style>

View File

@ -77,7 +77,7 @@
</span>
<svg @click="copyBtn" class="copy-btn" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"
width="12" height="12" style="fill: #1019ab;">
<path
<path
d="M394.666667 106.666667h448a74.666667 74.666667 0 0 1 74.666666 74.666666v448a74.666667 74.666667 0 0 1-74.666666 74.666667H394.666667a74.666667 74.666667 0 0 1-74.666667-74.666667V181.333333a74.666667 74.666667 0 0 1 74.666667-74.666666z m0 64a10.666667 10.666667 0 0 0-10.666667 10.666666v448a10.666667 10.666667 0 0 0 10.666667 10.666667h448a10.666667 10.666667 0 0 0 10.666666-10.666667V181.333333a10.666667 10.666667 0 0 0-10.666666-10.666666H394.666667z m245.333333 597.333333a32 32 0 0 1 64 0v74.666667a74.666667 74.666667 0 0 1-74.666667 74.666666H181.333333a74.666667 74.666667 0 0 1-74.666666-74.666666V394.666667a74.666667 74.666667 0 0 1 74.666666-74.666667h74.666667a32 32 0 0 1 0 64h-74.666667a10.666667 10.666667 0 0 0-10.666666 10.666667v448a10.666667 10.666667 0 0 0 10.666666 10.666666h448a10.666667 10.666667 0 0 0 10.666667-10.666666v-74.666667z"
p-id="1521"></path>
</svg>

View File

@ -7,7 +7,6 @@
<!-- 页面顶部的横幅图片 -->
<!-- <img src="" alt=""> -->
<div class="conter">
<div class="left">
<p class="title">
@ -104,8 +103,8 @@
</div>
</el-form-item>
<!-- 获取验证码按钮 - 添加防抖功能 -->
<span :disabled="isDisabled || isGettingCode" class="getCodeStyleNew" style="height:40px;margin-left:10px"
@click="debouncedGetCode">
<span :disabled="isDisabled || isGettingCode" class="getCodeStyleNew"
style="height:40px;margin-left:10px" @click="debouncedGetCode">
{{ SendCode_text }}
</span>
</div>
@ -373,7 +372,7 @@ export default {
methods: {
//
debounce(func, wait) {
return function() {
return function () {
const context = this;
const args = arguments;
clearTimeout(this.debounceTimer);
@ -384,7 +383,7 @@ export default {
},
//
debouncedGetCode: function() {
debouncedGetCode: function () {
if (this.isDisabled || this.isGettingCode) return;
this.isGettingCode = true;
@ -400,7 +399,7 @@ export default {
},
//
debouncedGetCode1: function() {
debouncedGetCode1: function () {
if (this.isDisabled1 || this.isGettingCode1) return;
this.isGettingCode1 = true;
@ -459,8 +458,7 @@ export default {
//
pollWxCode() {
this.getCodeTimer = setInterval(async () => {
try {
//
try {x // x
const res = await reqGetCodeAPI({ state: this.wxState });
if (!res.status) return;
@ -915,7 +913,7 @@ export default {
}
} else {
this.$router.push(getHomePath());
}``
} ``
} else if (res.roles.includes('运营')) {
this.$router.push('/operation/supplierManagement');
} else if (res.roles.includes('销售')) {

View File

@ -0,0 +1,472 @@
<template>
<div id="vmcentercontainer">
<div class="bg1"></div>
<div class="bg2"></div>
<div class="bg3"></div>
<div class="echart-map-container">
<div class="worldmap">
<div class="wrapper">
<div class="map-container" ref="myEchart"></div>
<div
v-for="(cluster, index) in clusterConfigs"
:key="index"
class="cluster-item-container"
:style="getClusterStyle(cluster.position)"
>
<div class="item" v-if="['北京集群', '长三角集群', '珠三角集群'].includes(cluster.name)">
<div class="right">{{ cluster.name }}</div>
<div class="left">
<span class="title">
点击下方进入地图
<br>
查看集群详细信息
</span>
<div class="btn">进入地图</div>
</div>
</div>
<div class="item" v-else>
<div class="left">
<span class="title">
点击下方进入地图
<br>
查看集群详细信息
</span>
<div class="btn">进入地图</div>
</div>
<div class="right">{{ cluster.name }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import * as echarts from "echarts";
import "echarts/map/js/china.js";
import { areaTwo } from "@/views/product/bigScreen/asset/cityData";
const CHART_CONSTANTS = {
MAP_ZOOM: 1.23,
MAP_CENTER: [105, 36],
LINE_COLOR: "#ffcc00",
BORDER_COLOR: "#389dff",
RIPPLE_PERIOD: 5,
RESIZE_DEBOUNCE_TIME: 300,
HORIZONTAL_LENGTH: 4, // 线4
BG_IMAGE_PATHS: {
map: require("../../../../../assets/image/map.png"),
lbx: require("../../../../../assets/image/lbx.png"),
jt: require("../../../../../assets/image/jt.png")
}
};
export default {
name: 'EchartMap',
props: {
routePath: { type: String, default: '/' },
clickableCities: { type: Array, default: () => [] },
showMap: { type: Boolean, default: true }
},
data() {
return {
myChart: null,
resizeTimer: null,
// labelPosition
clusterConfigs: [
{
name: '北京集群',
cityName: '北京',
direction: 'right-top',
labelPosition: [121.40, 50.90], //
position: { top: '18%', left: '73%' }
},
{
name: '内蒙集群',
cityName: '内蒙古',
direction: 'left',
labelPosition: [105.73, 50.83], //
position: { top: '19%', left: '35%' }
},
{
name: '新疆集群',
cityName: '新疆',
direction: 'left',
labelPosition: [88.68, 52.77], //
position: { top: '15.4%', left: '8%' }
},
{
name: '长三角集群',
cityName: '上海',
direction: 'right',
labelPosition: [128, 38.23], //
position: { top: '40%', left: '80.5%' }
},
{
name: '珠三角集群',
cityName: '广东',
direction: 'right',
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%' }
}
]
};
},
computed: {
geoCoordMap() {
return {
"新疆": [87.68, 43.77],
"内蒙古": [111.73, 40.83],
"北京": [116.40, 39.90],
"天津": [117.20, 39.12],
"宁夏": [106.27, 38.47],
"河北": [114.48, 38.03],
"甘肃": [103.82, 36.06],
"江苏": [118.78, 32.04],
"上海": [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);
},
beforeDestroy() {
this.myChart?.dispose();
clearTimeout(this.resizeTimer);
window.removeEventListener('resize', this.handleResize);
},
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;
this.myChart?.dispose();
this.myChart = echarts.init(chartDom);
this.myChart.setOption(this.buildChartOption(), true);
this.myChart.on('click', this.handleAreaClick);
},
refreshChart() {
this.$nextTick(() => this.initChart());
},
buildChartOption() {
return {
tooltip: { show: false },
geo: this.buildGeoConfig(),
series: [this.buildLineSeries(), this.buildEffectScatterSeries(), this.buildLabelScatterSeries()]
};
},
buildGeoConfig() {
return {
map: "china",
roam: false,
zoom: CHART_CONSTANTS.MAP_ZOOM,
center: CHART_CONSTANTS.MAP_CENTER,
label: { normal: { show: false }, emphasis: { show: false } },
itemStyle: {
normal: { areaColor: "rgba(0,0,255,0)", borderColor: CHART_CONSTANTS.BORDER_COLOR, borderWidth: 1 },
emphasis: { areaColor: "#0033cc", borderWidth: 0 }
}
};
},
buildLineSeries() {
return {
type: 'lines',
coordinateSystem: 'geo',
data: this.buildLineData(),
lineStyle: { type: 'solid', width: 2, color: CHART_CONSTANTS.LINE_COLOR },
symbol: ['none', 'none'],
polyline: true,
effect: { show: true, trailLength: 0, symbol: 'circle', symbolSize: 3, color: CHART_CONSTANTS.LINE_COLOR }
};
},
buildEffectScatterSeries() {
return {
type: "effectScatter",
coordinateSystem: "geo",
data: this.buildScatterData(),
symbol: "circle",
symbolSize: (_, p) => p.name === '北京' ? 18 : 12,
hoverSymbolSize: 15,
tooltip: {
show: true,
formatter: ({name}) => name,
backgroundColor: '#f4f6f8',
textStyle: { color: "#333", fontSize: 12 },
borderColor: '#333',
padding: 7
},
label: { formatter: "{b}", position: "right", show: true, fontWeight: 'bolder' },
itemStyle: { color: "#fdfdfd" },
emphasis: { label: { show: false } },
effectType: 'ripple',
rippleEffect: { brushType: 'stroke', scale: 2.5, period: CHART_CONSTANTS.RIPPLE_PERIOD },
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 }
})),
symbol: 'none'
};
},
// 线 线
buildLineData() {
const shortLen = CHART_CONSTANTS.HORIZONTAL_LENGTH;
return this.clusterConfigs
.map(cluster => {
const cityCoord = this.geoCoordMap[cluster.cityName];
const [labelLon, labelLat] = cluster.labelPosition;
if (!cityCoord) return null;
const [cityLon, cityLat] = cityCoord;
let lineStart, lineEnd;
// 1. 线shortLen
switch (cluster.direction) {
case 'left':
// 线
lineEnd = [labelLon, labelLat]; // 线 =
lineStart = [labelLon + shortLen, labelLat]; // 线 = shortLen
break;
case 'right':
default:
// 线
lineStart = [labelLon - shortLen, labelLat]; // 线 = shortLen
lineEnd = [labelLon, labelLat]; // 线 =
break;
}
// 2. 线() 线 线(线)
// 线
return {
coords: [
[cityLon, cityLat], //
lineStart, // 1线
lineEnd, // 2线线
[labelLon, labelLat]//
]
};
})
.filter(item => item !== null);
},
buildScatterData() {
return Object.keys(this.geoCoordMap).map(key => ({
name: key,
value: this.geoCoordMap[key],
symbolSize: key === '北京' ? 18 : 12
}));
},
handleAreaClick(params) {
if (!this.clickableCities?.includes(params?.name)) return;
this.currentArea = areaTwo[params.name] || [];
this.$emit('update:showMap', false);
this.$emit('area-click', params);
}
}
};
</script>
<style scoped>
#vmcentercontainer {
position: relative;
height: 100%;
overflow: hidden;
}
.bg1, .bg2, .bg3 {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-size: 100% 100%;
}
.bg1 {
height: 518px;
width: 518px;
background: url(~@/assets/image/map.png);
opacity: 0.3;
}
.bg2 {
height: 643px;
width: 643px;
background: url(~@/assets/image/lbx.png);
opacity: 0.6;
animation: rotate1 15s linear infinite;
}
.bg3 {
height: 566px;
width: 566px;
background: url(~@/assets/image/jt.png);
opacity: 0.8;
animation: rotate2 15s linear infinite;
}
.echart-map-container {
width: 100%;
height: 100%;
position: relative;
}
.worldmap {
position: absolute;
top: 0;
left: 0;
z-index: 5;
height: 100%;
width: 100%;
}
.wrapper {
padding: 15px;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
position: relative;
}
.map-container {
width: 100%;
height: 100%;
}
.cluster-item-container {
position: absolute;
z-index: 10;
}
.item {
display: flex;
width: 200px;
color: #fff;
font-size: 10px;
}
.item .left {
display: flex;
justify-content: center;
flex-direction: column;
padding: 0 8px;
background-color: rgba(147, 112, 219, 0.3);
}
.left .title {
padding-bottom: 16px;
}
.item .btn {
color: #FDBD00;
cursor: pointer;
}
.item .right {
text-align: center;
width: 20px;
font-size: 12px;
background-color: #000;
}
.sortBox {
width: 100%;
height: 100%;
color: #bbd6f1;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
padding: 20px;
}
.sortBox ul {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 20px;
padding: 0;
margin: 20px 0 0 0;
}
.sortBox li {
list-style: none;
border: 1px solid #2086ee;
border-radius: 5px;
width: 250px;
height: 35px;
display: flex;
justify-content: flex-start;
align-items: center;
color: #d3d5ea;
padding-left: 10px;
font-weight: 700;
background-color: rgba(32, 134, 238, 0.1);
}
.go-back {
font-weight: bold;
border: 1px solid white;
border-radius: 5px;
padding: 8px 16px;
margin-bottom: 30px;
cursor: pointer;
background-color: rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.go-back:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
@keyframes rotate1 {
from { transform: translate(-50%, -50%) rotate(0deg); }
to { transform: translate(-50%, -50%) rotate(360deg); }
}
@keyframes rotate2 {
from { transform: translate(-50%, -50%) rotate(0deg); }
to { transform: translate(-50%, -50%) rotate(-360deg); }
}
</style>

View File

@ -0,0 +1,424 @@
<template>
<div>
<div class="center">
<div class="top">
<!-- 需求算力 -->
<div class="top-tit">
<div class="top-text">
1500P
</div>
<div class="btm-text">
需求算力
</div>
</div>
<!-- 供给算力 -->
<div class="top-tit">
<div class="top-text">
35320P
</div>
<div class="btm-text">
供给算力
</div>
</div>
<!-- 集群数 -->
<div class="top-tit">
<div class="top-text">
7
</div>
<div class="btm-text">
集群数量
</div>
</div>
<!-- 客户数量 -->
<div class="top-tit">
<div class="top-text">
352
</div>
<div class="btm-text">
客户数量
</div>
</div>
<!-- 芯片数量 -->
<div class="top-tit">
<div class="top-text">
75368
</div>
<div class="btm-text">
芯片数量
</div>
</div>
<!-- 模型数量 -->
<div class="top-tit">
<div class="top-text">
75
</div>
<div class="btm-text">
模型数量
</div>
</div>
<dv-decoration-5 style="width:660px;height:40px;" :color="['#379BE8', '#2765B4']" :dur="8" />
</div>
<!-- 添加地图组件 -->
<div class="map-container">
<!-- 传递必需的props -->
<Map :route-path="currentRoutePath" :clickable-cities="clickableCities" :show-map.sync="showMap"
@area-click="handleAreaClick" />
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 切换栏 -->
<div class="btm-tab">
<!-- 标签项 - 绑定点击事件和激活状态 -->
<div
class="tab-item"
:class="{ active: activeTab === 0 }"
@click="switchTab(0)"
>
供给概览
</div>
<!-- 标签项 - 绑定点击事件和激活状态 -->
<div
class="tab-item"
:class="{ active: activeTab === 1 }"
@click="switchTab(1)"
>
需求概览
</div>
</div>
<!-- 概览 -->
<div class="overview" v-show="activeTab === 0">
<!-- 概览项原有内容不变 -->
<div 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>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
</div>
<!-- 概览two - 根据激活标签显示不修改原有布局样式 -->
<div class="overview-two" v-show="activeTab === 1">
<!-- 概览项原有内容不变 -->
<div 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>
</div>
<div class="overview-img">
<img src="../images/btm.png" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//
import Map from './Map.vue';
export default {
name: 'VmCenter',
components: {
Map
},
data() {
return {
//
currentRoutePath: this.$route ? this.$route.path : '/',
// -
clickableCities: ['北京', '上海', '广州', '深圳', '济南', '无锡', '青岛', '长沙', '天津', '南京', '杭州', '成都', '重庆', '武汉'],
//
showMap: true,
// 0=overview1=overview-two
activeTab: 0
};
},
watch: {
// routePath
'$route.path'(newPath) {
this.currentRoutePath = newPath;
}
},
mounted() {
//
if (!this.$route) {
this.currentRoutePath = window.location.pathname;
}
},
methods: {
handleAreaClick(params) {
console.log('区域被点击:', params);
//
},
//
switchTab(tabIndex) {
console.log('切换到标签:', tabIndex);
this.activeTab = tabIndex;
}
}
}
</script>
<style lang="less" scoped>
.center {
width: 100%;
height: calc(100vh - 160px);
display: flex;
flex-direction: column;
}
.top {
overflow: hidden;
width: 100%;
display: flex;
justify-content: center;
flex-wrap: wrap;
padding: 10px 0;
padding-top: 40px;
flex-shrink: 0;
/* 防止被压缩 */
.top-tit {
padding-left: 4%;
display: flex;
flex-direction: column;
align-items: center;
color: #fff;
font-family: "Microsoft Yahei", "PingFang SC", sans-serif;
font-weight: 600;
color: #ffffff;
transform: skew(-8deg);
-webkit-transform: skew(-8deg);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4);
letter-spacing: 1px;
.top-text {
font-size: 26px;
}
.btm-text {
font-size: 16px;
}
}
}
/* 地图容器样式 */
.map-container {
flex: 1;
/* 占据剩余空间 */
width: 100%;
min-height: 0;
/* 防止flex item溢出 */
margin-top: -50px;
position: relative;
overflow: hidden;
}
.bottom {
z-index: 99;
width: 100%;
position: fixed;
bottom: 140px;
left:0;
width: 100%;
height: 40px !important;
.btm-tab {
width: 100%;
display: flex;
color: #fff;
font-size: 16px;
display: flex;
justify-content: center;
}
.tab-item {
margin-right: 40px;
width: 140px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
background: url(../images/btmTab.png) no-repeat;
background-size: 100% 100%;
cursor: pointer; /* 新增:添加鼠标手型,提升交互体验 */
transition: all 0.3s ease; /* 新增:过渡动画,切换更平滑 */
}
/* 新增:标签激活状态样式 */
.tab-item.active {
/* 可根据需求调整激活样式,示例:加深背景/改变文字颜色 */
filter: brightness(1.2); /* 背景提亮,不修改原有背景图 */
color: #409eff; /* 激活态文字高亮 */
font-weight: 600;
width: 150px;
height: 50px;
}
.overview,.overview-two{
display: flex;
justify-content: center;
margin-top: 50px;
.overview-item{
display: flex;
flex-direction: column;
align-items: center;
position: relative;
margin-left: 50px;
.overview-title{
display: flex;
flex-direction: column;
width: 100%;
text-align: center;
font-size: 16px;
color: #fff;
position: absolute;
top: 0%;
left: 50%;
transform: translate(-50%, -60%);
}
.overview-img{
width: 70px;
height: 50px;
img{
width: 100%;
height: 100%;
}
}
}
}
}
</style>

View File

@ -0,0 +1,642 @@
<template>
<div>
<div class="left">
<!-- 第1行2个横向模块 -->
<div class="row row-1">
<div class="cluster">
<div class="title">异构芯片规模</div>
<div class="conter">
<div class="conter-top">
<div class="top-tit">33000 /<span></span></div>
<div class="top-tit">5000 /<span></span></div>
<div class="top-tit">8000 /<span></span></div>
</div>
<div class="conter-center">
<img src="../images/3D.png" alt="">
</div>
<div class="conter-bottom">
<div class="bottom-tit">NVIDIA</div>
<div class="bottom-tit">晟腾</div>
<div class="bottom-tit">传统超算</div>
</div>
</div>
</div>
<div class="heterogeneous">
<div class="title">集群规模</div>
<div class="content">
<dv-decoration-9 style="width:200px;height:200px;color: #fff;font-size: 20px; font-weight: 600;">
5100P
</dv-decoration-9>
</div>
</div>
</div>
<!-- 第2行通栏模块运行中芯片数量 -->
<div class="row row-2">
<div class="running">
<div class="title">运行中芯片数量</div>
<div ref="runningChart" class="chart-container "></div>
</div>
</div>
<!-- 第3行2个横向模块 -->
<div class="row row-3">
<div class="model">
<div class="title">模型调用量</div>
<div ref="modelChart" class="chart-container model-chart"></div>
</div>
<div class="token">
<div class="title">token调用量</div>
<div ref="tokenChart" class="chart-container token-chart"></div>
</div>
</div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
data() {
return {
runningChartInstance: null,
modelChartInstance: null,
tokenChartInstance: null,
resizeTimer: null
};
},
mounted() {
this.initRunningChart();
this.initModelChart();
this.initTokenChart();
window.addEventListener('resize', this.handleResize);
},
methods: {
//
initRunningChart() {
const chartDom = this.$refs.runningChart;
if (!chartDom) return;
this.runningChartInstance = echarts.init(chartDom);
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 {
name: '最高',
value: value,
xAxis: index,
yAxis: value,
symbol: 'circle',
symbolSize: 10,
itemStyle: {
color: '#fff',
// borderColor: '#ff4d4f',
// borderWidth: 2
},
label: {
show: true,
position: 'top',
formatter: function (params) {
return (params.value / 10000).toFixed(1) + '万'; // ""
},
color: '#fff',
fontWeight: 'bold',
fontSize: 12
}
};
}
return null;
}).filter(item => item !== null);
const option = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.85)',
borderColor: '#1890ff',
textStyle: { color: '#fff' },
formatter: function (params) {
const value = params[0].value;
return params[0].name + '<br/>' +
params[0].marker + params[0].seriesName + ': ' +
value.toLocaleString() + '个'; //
}
},
grid: {
left: '3%',
right: '4%',
top: '12%',
bottom: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
color: 'rgba(255,255,255,0.7)',
fontSize: 12,
interval: 0
},
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
axisTick: { show: false }
},
yAxis: {
type: 'value',
min: yMin, // 使
max: yMax, // 使
splitNumber: 6,
splitLine: {
lineStyle: {
color: 'rgba(255,255,255,0.1)',
type: 'dashed'
}
},
axisLabel: {
color: 'rgba(255,255,255,0.7)',
fontSize: 12,
formatter: function (value) {
// ""1
return (value / 10000).toFixed(1) + '万';
}
},
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
axisTick: { show: false }
},
series: [
{
name: '芯片数',
type: 'line',
data: yData,
symbol: 'none',
symbolSize: 8,
itemStyle: {
color: '#1890ff',
borderColor: '#fff',
borderWidth: 2
},
lineStyle: {
color: '#1890ff',
width: 3
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(24,144,255,0.4)' },
{ offset: 1, color: 'rgba(24,144,255,0.05)' }
])
},
markPoint: {
symbol: 'circle',
symbolSize: 10,
data: markPointData
}
}
],
backgroundColor: 'transparent'
};
this.runningChartInstance.setOption(option);
},
//
initModelChart() {
const chartDom = this.$refs.modelChart;
if (!chartDom) return;
this.modelChartInstance = echarts.init(chartDom);
// 使
const yData = ['deepseek', '通义千问', 'kimi', '豆包', '文心一言', '元宝'];
const xData = [105, 78, 70, 120, 60, 85];
// X
const maxValue = Math.max(...xData);
const xMax = Math.ceil(maxValue / 50) * 50 + 10;
const option = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.85)',
borderColor: '#50e3c2',
formatter: function (params) {
return params[0].name + ': ' + params[0].value;
}
},
grid: {
left: '2%',
right: '5%',
top: '10%',
bottom: '10%',
containLabel: true
},
xAxis: {
type: 'value',
min: 0,
max: xMax,
splitNumber: 6,
axisLabel: {
color: 'rgba(255,255,255,0.7)',
fontSize: 10
},
axisLine: {
lineStyle: { color: 'rgba(255,255,255,0.3)' }
},
splitLine: {
lineStyle: {
color: 'rgba(255,255,255,0.1)',
type: 'dashed'
}
}
},
yAxis: {
type: 'category',
data: yData,
axisLabel: {
color: '#fff',
fontSize: 10
},
axisLine: { show: false },
axisTick: { show: false }
},
series: [
// barWidth1020
{
name: '调用量',
type: 'bar',
data: xData,
barWidth: 20,
itemStyle: {
normal: {
barBorderRadius: [0, 8, 8, 0],
color: '#1a9678',
opacity: 0.6
}
},
z: 1
},
// barWidth1424
{
name: '调用量',
type: 'bar',
data: xData,
barWidth: 14,
label: {
show: false, //
position: 'right',
color: '#50e3c2',
fontWeight: 'bold',
fontSize: 12,
formatter: '{c}'
},
itemStyle: {
normal: {
barBorderRadius: [0, 8, 8, 0],
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#76f7d8' },
{ offset: 0.5, color: '#50e3c2' },
{ offset: 1, color: '#27c8a7' }
]),
shadowColor: 'rgba(80, 227, 194, 0.5)',
shadowBlur: 8,
shadowOffsetX: 2,
shadowOffsetY: 2
}
},
z: 2
}
],
backgroundColor: 'transparent'
};
this.modelChartInstance.setOption(option);
},
// Token
initTokenChart() {
const chartDom = this.$refs.tokenChart;
if (!chartDom) return;
this.tokenChartInstance = echarts.init(chartDom);
// 使M
const xData = ['千问3-max', 'kimi', '豆包', 'deepseek', '千帆'];
const yData = [5000, 4300, 3500, 3200, 2800]; //
// Y
const maxValue = Math.max(...yData);
const yMax = Math.ceil(maxValue / 1000) * 1000 + 500;
const option = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.85)',
borderColor: '#1890ff',
formatter: function (params) {
return params[0].name + '<br/>' + params[0].marker + params[0].seriesName + ': ' + params[0].value + 'M';
}
},
grid: {
left: '0', //
right: '10%', //
top: '15%',
bottom: '25%', //
containLabel: true
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
color: 'rgba(255,255,255,0.7)',
fontSize: 11, //
rotate: 0, // 15
interval: 0, //
margin: 10, // 线
//
formatter: function (value) {
if (value.length > 8) {
return value.substring(0, 8) + '...';
}
return value;
}
},
axisLine: {
lineStyle: { color: 'rgba(255,255,255,0.3)' }
},
axisTick: {
show: false,
alignWithLabel: true //
}
},
yAxis: {
type: 'value',
min: 0,
max: yMax,
splitNumber: 6,
splitLine: {
lineStyle: {
color: 'rgba(255,255,255,0.1)',
type: 'dashed'
}
},
axisLabel: {
color: 'rgba(255,255,255,0.7)',
fontSize: 12
},
axisLine: {
lineStyle: { color: 'rgba(255,255,255,0.3)' }
}
},
series: [
//
{
name: 'Token',
type: 'bar',
data: yData,
barWidth: 10, //
itemStyle: {
normal: {
barBorderRadius: [6, 6, 0, 0],
color: '#0a4a86',
opacity: 0.6
}
},
z: 1
},
//
{
name: 'Token',
type: 'bar',
data: yData,
barWidth: 12,
label: {
show: false,
position: 'top',
color: '#1890ff',
fontWeight: 'bold',
fontSize: 12,
formatter: '{c}M'
},
itemStyle: {
normal: {
barBorderRadius: [6, 6, 0, 0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#40a9ff' },
{ offset: 0.5, color: '#1890ff' },
{ offset: 1, color: '#0f58a8' }
]),
shadowColor: 'rgba(24, 144, 255, 0.5)',
shadowBlur: 8,
shadowOffsetX: 2,
shadowOffsetY: 2
}
},
z: 2
}
],
backgroundColor: 'transparent'
};
this.tokenChartInstance.setOption(option);
},
handleResize() {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
this.runningChartInstance?.resize();
this.modelChartInstance?.resize();
this.tokenChartInstance?.resize();
}, 200);
}
},
beforeDestroy() {
this.runningChartInstance?.dispose();
this.modelChartInstance?.dispose();
this.tokenChartInstance?.dispose();
window.removeEventListener('resize', this.handleResize);
}
};
</script>
<style lang="less" scoped>
.left {
width: 100%;
height: calc(100vh - 160px);
display: flex;
flex-direction: column;
gap: 15px;
padding: 10px 0;
box-sizing: border-box;
}
.conter {
color: #fff;
position: relative;
.conter-top {
display: flex;
align-items: center;
justify-content: space-around;
position: absolute;
top: 14px;
left: 10px;
.top-tit {
padding-left: 6px;
span {
font-size: 11px;
}
}
}
.conter-center {
display: flex;
align-items: center;
justify-content: center;
img {
width: 80%;
height: 80%;
}
}
.conter-bottom {
margin-top: -16px;
display: flex;
padding-left:10px;
align-items: center;
justify-content: space-around;
// padding-left: 20px;
.bottom-tit {
padding-right: 4px;
span {
font-size: 11px;
}
}
}
}
.row {
width: 100%;
height: calc(100% / 3);
display: flex;
justify-content: space-around;
align-items: stretch;
box-sizing: border-box;
}
/* 模块宽度适配 */
.cluster,
.heterogeneous,
.model,
.token {
width: 46%;
height: 100%;
box-sizing: border-box;
}
.cluster,
.heterogeneous {
.title {
margin-bottom: 20px;
}
}
.running {
width: 95%;
height: 100%;
box-sizing: border-box;
.title {
padding-left: 12%;
height: 36px;
}
}
/* 标题样式 */
.title {
background: url(../images/titleBg.png) no-repeat;
background-size: cover;
height: 36px;
color: #fff;
display: flex;
align-items: center;
padding-left: 20%;
font-family: "Microsoft Yahei", "PingFang SC", sans-serif;
font-size: 14px;
font-weight: 600;
transform: skew(-12deg);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4);
letter-spacing: 1px;
margin-bottom: 10px;
}
/* 图表容器 */
.chart-container {
width: 100%;
height: calc(100% - 46px);
position: relative;
box-sizing: border-box;
}
//
.model-chart {
// height: 100%;
height: calc(100% - 46px);
width: 240px;
}
.token-chart {
width: 420px;
}
.content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
}
/* 调整第3行的高度比例 */
.row-3 {
.model,
.token {
.chart-container {
height: calc(100% - 46px);
}
}
}
/* 响应式适配 */
@media (min-width: 1920px) {
.title {
font-size: 18px;
height: 45px;
}
.chart-container {
height: calc(100% - 55px);
}
}
@media (max-width: 1366px) {
.title {
font-size: 14px;
height: 38px;
}
.chart-container {
height: calc(100% - 48px);
}
}
</style>

View File

@ -0,0 +1,640 @@
<template>
<div>
<div class="right">
<!-- 1 -->
<div class="row row-1">
<!-- 应用类型 -->
<div class="app-class">
<div class="title">应用类型</div>
<div class="content">
<!-- 中心TOP1 -->
<div class="circle-center">
<div class="rank">TOP1</div>
<div class="name">智能推理</div>
</div>
<!-- 周围TOP项 -->
<div class="circle-item item-top2">
<div class="rank">TOP2</div>
<div class="name">智能训练</div>
</div>
<div class="circle-item item-top3">
<div class="rank">TOP3</div>
<div class="name">图形渲染</div>
</div>
<div class="circle-item item-top4">
<div class="rank">TOP4</div>
<div class="name">蛋白质分析</div>
</div>
<div class="circle-item item-top5">
<div class="rank">TOP5</div>
<div class="name">其他</div>
</div>
</div>
</div>
<!-- 用户种类 -->
<div class="user-class">
<div class="title">用户种类</div>
<div class="content">
<img src="../images/userClass.png" alt="用户种类示意图">
<div class="user">
<span class="finance">金融</span>
<span class="medical">医疗</span>
<span class="creative">文创</span>
<span class="education">教育</span>
<span class="intelligence">智能制造</span>
</div>
</div>
</div>
</div>
<!-- 2 -->
<div class="row row-2">
<!-- 用户数量无背景色 -->
<div class="user-num">
<div class="title">用户数量</div>
<div class="content">
<img src="../images/use-num.png" alt="">
</div>
</div>
<!-- 用户消费排行 -->
<div class="user-consume">
<div class="title">用户消费排行</div>
<div class="content">
<dv-scroll-board :config="scrollBoardConfig" style="width:95%;height:95%" />
</div>
</div>
</div>
<!-- 3 -->
<div class="row row-3">
<!-- 算力使用情况双折线图无背景色 -->
<div class="power-use">
<div class="title">算力使用情况</div>
<div ref="powerUseChart" class="power-use-chart"></div>
</div>
</div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts';
import img1 from '../images/1.png'
import img2 from '../images/2.png'
import img3 from '../images/3.png'
import img4 from '../images/4.png'
import img5 from '../images/5.png'
export default {
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'],
],
index: false,
columnWidth: [50, 100, 90],
rowHeight: 28,
align: ['center', 'center', 'center'],
rowNum: 5,
oddRowBGC: '#040c45',
evenRowBGC: '#17264f',
waitTime: 2000,
carousel: 'single',
hoverPause: true
},
resizeTimer: null
}
},
mounted() {
this.initPowerUseChart();
window.addEventListener('resize', this.handleResize);
},
methods: {
initPowerUseChart() {
const chartDom = this.$refs.powerUseChart;
if (!chartDom) return;
const myChart = echarts.init(chartDom);
//
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 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) => {
if (index === maxIndex) {
return {
name: '最高',
value: value,
xAxis: index,
yAxis: value,
symbol: 'circle',
symbolSize: 10,
itemStyle: {
color: '#fff',
// borderColor: '#ff4d4f',
// borderWidth: 2
},
label: {
show: true,
position: 'top',
formatter: function(params) {
return (params.value / 10000).toFixed(1) + '万'; // ""
},
color: '#fff',
fontWeight: 'bold',
fontSize: 12
}
};
}
return null;
}).filter(item => item !== null);
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.85)',
borderColor: '#1890ff',
textStyle: { color: '#fff' },
formatter: function(params) {
const value = params[0].value;
return params[0].name + '<br/>' +
params[0].marker + params[0].seriesName + ': ' +
value.toLocaleString() + '算力单位'; //
}
},
grid: {
left: '3%',
right: '4%',
top: '12%',
bottom: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: dates,
axisLabel: {
color: 'rgba(255,255,255,0.7)',
fontSize: 12,
interval: 0
},
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
axisTick: { show: false }
},
yAxis: {
type: 'value',
min: yMin, // 使
max: yMax, // 使
splitNumber: 6,
splitLine: {
lineStyle: {
color: 'rgba(255,255,255,0.1)',
type: 'dashed'
}
},
axisLabel: {
color: 'rgba(255,255,255,0.7)',
fontSize: 12,
formatter: function(value) {
// ""1
return (value / 10000).toFixed(1) + '万';
}
},
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
axisTick: { show: false }
},
series: [
{
name: '算力使用',
type: 'line',
data: powerUsage,
symbol: 'none',
symbolSize: 8,
itemStyle: {
color: '#1890ff',
borderColor: '#fff',
borderWidth: 2
},
lineStyle: {
color: '#1890ff',
width: 3
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(24,144,255,0.4)' },
{ offset: 1, color: 'rgba(24,144,255,0.05)' }
])
},
markPoint: {
symbol: 'circle',
symbolSize: 10,
data: markPointData
}
}
]
};
myChart.setOption(option);
},
handleResize() {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
if (this.$refs.powerUseChart) {
const chart = echarts.getInstanceByDom(this.$refs.powerUseChart);
if (chart) chart.resize();
}
}, 200);
}
},
beforeDestroy() {
if (this.$refs.powerUseChart) {
const chart = echarts.getInstanceByDom(this.$refs.powerUseChart);
if (chart) chart.dispose();
}
window.removeEventListener('resize', this.handleResize);
}
};
</script>
<style lang="less" scoped>
.user-consume {
/deep/ .rank-img {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
img {
width: 28px;
height: 28px;
object-fit: contain;
}
}
}
/* 整体容器:纵向排列 3 行,每行高度平分 */
.right {
width: 100%;
height: calc(100vh - 160px);
display: flex;
flex-direction: column;
gap: 15px;
padding: 10px 0;
box-sizing: border-box;
}
.row {
width: 100%;
height: calc(100% / 3);
display: flex;
justify-content: space-around;
align-items: stretch;
box-sizing: border-box;
}
.app-class, .user-class, .user-num, .user-consume {
width: 46%;
height: 100%;
box-sizing: border-box;
}
.user-class img {
width: 100%;
height: 100%;
}
/* 用户数量模块样式调整 */
.user-num .content {
width: 100%;
height: calc(100% - 50px);
display: flex;
justify-content: center;
align-items: center; /* 垂直居中 */
overflow: hidden;
padding: 5px; /* 添加内边距 */
margin-top: -40px;
}
.user-num .content img {
width: 100%;
height: 100%;
object-fit: contain; /* 保持图片比例 */
object-position: center center; /* 图片中心对齐 */
}
.power-use {
width: 95%;
height: 100%;
box-sizing: border-box;
}
.title {
background: url(../images/titleBg.png) no-repeat;
background-size: 100% 100%;
height: 40px;
color: #fff;
display: flex;
align-items: center;
padding-left: 12%;
font-family: "Microsoft Yahei", "PingFang SC", sans-serif;
font-size: 16px;
font-weight: 600;
color: #ffffff;
transform: skew(-12deg);
-webkit-transform: skew(-12deg);
margin-bottom: 10px;
}
.user-class .content {
width: 100%;
height: calc(100% - 50px);
font-size: 14px;
font-weight: bold;
color: #fff;
position: relative;
overflow: hidden;
.user {
position: absolute;
top: 56%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.finance, .medical, .creative, .education, .intelligence {
padding-top: 12px;
}
}
.user-consume .content {
width: 100%;
height: calc(100% - 50px);
display: flex;
justify-content: center;
align-items: center;
padding: 0 5px;
}
.power-use-chart {
width: 100%;
height: calc(100% - 50px);
}
.power-use .title {
padding-left: 12%;
}
/* 应用类型模块内容容器 */
.app-class .content {
width: 100%;
height: calc(100% - 50px);
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
/* 中心TOP1样式 */
.circle-center {
width: 120px;
height: 120px;
border-radius: 50%;
background: rgba(0, 92, 207, 0.3);
border: 2px solid rgba(72, 176, 255, 0.8);
box-shadow: 0 0 15px rgba(72, 176, 255, 0.6);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #fff;
font-size: 14px;
position: relative;
z-index: 2;
.rank {
font-weight: bold;
margin-bottom: 5px;
}
.name {
font-size: 16px;
}
}
/* 周围TOP项基础样式 */
.circle-item {
width: 74px;
height: 74px;
border-radius: 50%;
background: rgba(0, 92, 207, 0.2);
border: 1px solid rgba(72, 176, 255, 0.6);
box-shadow: 0 0 10px rgba(72, 176, 255, 0.4);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #fff;
font-size: 12px;
position: absolute;
z-index: 1;
transform-origin: center center;
.rank {
font-weight: bold;
margin-bottom: 3px;
}
.name {
font-size: 13px;
}
}
/* 各TOP项定位 */
.item-top2 {
width: 70px;
height: 70px;
top: 2px;
right: 2px;
}
.item-top3 {
bottom: 2px;
left: 2px;
}
.item-top4 {
top: 4px;
left: -4px;
}
.item-top5 {
bottom: 2px;
right: -2px;
}
/* 增强动画效果 - 增加旋转速度 */
@keyframes rotate2 {
0%, 100% {
transform: translate(0, 0) rotate(0deg);
}
25% {
transform: translate(-5px, -5px) rotate(15deg);
}
50% {
transform: translate(0, 0) rotate(0deg);
}
75% {
transform: translate(5px, 5px) rotate(-15deg);
}
}
@keyframes rotate3 {
0%, 100% {
transform: translate(0, 0) rotate(0deg);
}
25% {
transform: translate(5px, 5px) rotate(-15deg);
}
50% {
transform: translate(0, 0) rotate(0deg);
}
75% {
transform: translate(-5px, -5px) rotate(15deg);
}
}
@keyframes rotate4 {
0%, 100% {
transform: translate(0, 0) rotate(0deg);
}
25% {
transform: translate(5px, -5px) rotate(15deg);
}
50% {
transform: translate(0, 0) rotate(0deg);
}
75% {
transform: translate(-5px, 5px) rotate(-15deg);
}
}
@keyframes rotate5 {
0%, 100% {
transform: translate(0, 0) rotate(0deg);
}
25% {
transform: translate(-5px, 5px) rotate(-15deg);
}
50% {
transform: translate(0, 0) rotate(0deg);
}
75% {
transform: translate(5px, -5px) rotate(15deg);
}
}
/* 为每个小圆添加明显的轨道运动 */
.item-top2 {
animation: rotate2 4s ease-in-out infinite;
box-shadow: 0 0 12px rgba(72, 176, 255, 0.6);
border: 2px solid rgba(72, 176, 255, 0.8);
}
.item-top3 {
animation: rotate3 5s ease-in-out infinite;
animation-delay: 0.5s;
box-shadow: 0 0 12px rgba(72, 176, 255, 0.6);
border: 2px solid rgba(72, 176, 255, 0.8);
}
.item-top4 {
animation: rotate4 6s ease-in-out infinite;
animation-delay: 1s;
box-shadow: 0 0 12px rgba(72, 176, 255, 0.6);
border: 2px solid rgba(72, 176, 255, 0.8);
}
.item-top5 {
animation: rotate5 7s ease-in-out infinite;
animation-delay: 1.5s;
box-shadow: 0 0 12px rgba(72, 176, 255, 0.6);
border: 2px solid rgba(72, 176, 255, 0.8);
}
/* 增强中心圆的光效 */
@keyframes centerGlow {
0%, 100% {
box-shadow:
0 0 20px rgba(72, 176, 255, 0.8),
inset 0 0 25px rgba(72, 176, 255, 0.3);
transform: scale(1);
}
50% {
box-shadow:
0 0 30px rgba(72, 176, 255, 1),
inset 0 0 30px rgba(72, 176, 255, 0.5);
transform: scale(1.02);
}
}
.circle-center {
animation: centerGlow 3s ease-in-out infinite;
}
/* 增强小圆的发光效果 */
.circle-item {
background: rgba(0, 92, 207, 0.25);
border: 2px solid rgba(72, 176, 255, 0.7);
.rank {
color: #66c2ff;
text-shadow: 0 0 3px rgba(102, 194, 255, 0.8);
}
}
/* 增强悬停效果 */
.circle-item:hover {
animation-play-state: paused;
transform: scale(1.15) !important;
z-index: 10;
box-shadow: 0 0 20px rgba(72, 176, 255, 1);
border: 2px solid rgba(72, 176, 255, 1);
transition: all 0.3s ease;
}
/* 为整个内容区域添加一个微妙的背景光晕(无脉动效果) */
.app-class .content::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 280px;
height: 280px;
background: radial-gradient(
circle at center,
rgba(72, 176, 255, 0.12) 0%,
rgba(0, 92, 207, 0.06) 40%,
rgba(0, 92, 207, 0.02) 70%,
transparent 90%
);
transform: translate(-50%, -50%);
z-index: 0;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,92 @@
<template>
<div id="app">
<div class="top">
<ScreenHeader></ScreenHeader>
</div>
<div class="center">
<!-- 左边 -->
<div class="left">
<ScreenLeft></ScreenLeft>
</div>
<!-- 中间 -->
<div class="content">
<ScreenCenter></ScreenCenter>
</div>
<!-- 右边 -->
<div class="right">
<ScreenRight></ScreenRight>
</div>
</div>
<!-- <div class="bottom">
<ScreenFooter></ScreenFooter>
</div> -->
</div>
</template>
<script>
import ScreenHeader from './screenHeader/index.vue';
import ScreenLeft from './ScreenLeft/index.vue';
import ScreenCenter from './ScreenCenter/index.vue';
import ScreenRight from './ScreenRight/index.vue';
export default {
components: {
ScreenHeader,
ScreenLeft,
ScreenCenter,
ScreenRight
},
data() {
return {
}
},
methods: {
}
}
</script>
<style lang="less" scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
box-sizing: border-box;
height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
background: url("../../../../assets/image/bg.jpg") no-repeat center ;
background-size: cover; //
overflow: hidden;
}
.top {
width: 100%;
height: 85px;
}
img {
height: 300px;
width: 300px;
position: absolute;
right: 20px;
}
.center {
display: flex;
.left{
width: 30%;
}
.content{
width: 40%;
}
.right{
width: 30%;
}
}
.center {
height: calc(100% - 80px);
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<div id="vmheader">
<h1 style="cursor: pointer" @click="fullScreen">NCmatch算力供需对接平台</h1>
<ZpTime class="time"></ZpTime>
</div>
</template>
<script>
import ZpTime from '../../components/common/ZpTime.vue'
export default {
name: 'vmheader',
components: {
ZpTime,
},
data() {
return {
}
},
methods: {
async fullScreen() {
//
let full = document.fullscreenElement
//
if (!full) {
await document.documentElement.requestFullscreen()
// this.$store.commit('setShowScreen', false)
// this.$store.commit('setShowScreen', true)
} else {
//退
await document.exitFullscreen()
}
},
goPage(item, index) {
this.currentIndex = index
this.$router.push(item.path)
}
}
}
</script>
<style scoped>
#vmheader {
height: 80px;
position: relative;
background: url("../../../../../assets/image/head_bg.png") no-repeat top center;
background-size: 100% 100%;
display: flex;
justify-content: center;
align-items: center;
/* 制定背景图像大小 */
}
#vmheader h1 {
color: #cfd1ee;
font-size: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.time {
position: absolute;
top: 0;
right: 30px
}
</style>

View File

@ -96,7 +96,7 @@
</div>
</template>
<script>1
<script>
import countTo from 'vue-count-to';
import VmCenterTop from './common/VmCenterTop.vue'
import VmCenterContainer from './common/VmCenterContainer.vue'
@ -483,9 +483,9 @@ export default {
background-color: rgba(101, 132, 226, 0.1);
padding: 15px;
height: 85px;
//display: flex;
/* //display: flex;
//justify-content: center;
//align-items: center;
//align-items: center; */
}
.tophd {

View File

@ -362,3 +362,4 @@ export default {
height: calc(100vh - 180px);
}
</style>

View File

@ -23,9 +23,9 @@ export default {
background-color: rgba(101, 132, 226, 0.1);
padding: 15px;
height: 85px;
//display: flex;
//justify-content: center;
//align-items: center;
/* //display: flex; */
/* //justify-content: center; */
/* //align-items: center; */
}
.tophd {

View File

@ -61,7 +61,7 @@ li {
width: 100vw;
background: url("../../../assets/image/bg.jpg") top center;
height: 100vh;
//overflow-y: auto;
/* overflow-y: auto; */
}
#content {

View File

@ -4,7 +4,7 @@
<div class="section">
<div class="bigTitle">快捷导航</div>
<ul class="recUl">
<li v-for="(item,index) in navList" :key="index" @click="goBaidu(item)">
<li v-for="(item, index) in navList" :key="index" @click="goBaidu(item)">
<img :src="getNavIcon(index)" class="nav-icon" alt="icon" />
{{ item.name }}
</li>
@ -25,12 +25,7 @@
<div class="section">
<div class="bigTitle">到期预警</div>
<div class="table-container">
<el-table
height="250px"
border
:data="tableData"
style="width: 100%"
header-cell-class-name="table-header"
<el-table height="250px" border :data="tableData" style="width: 100%" header-cell-class-name="table-header"
cell-class-name="table-cell">
<el-table-column prop="name" label="名称" min-width="120"></el-table-column>
<el-table-column prop="instanceid" label="资源id" min-width="120"></el-table-column>
@ -91,12 +86,7 @@
<div class="todos card">
<div class="title">待办事项</div>
<ul class="todo-list">
<li
v-for="(item, index) in todoList"
:key="index"
class="todo-item"
@click="handleTodoClick(item.name)"
>
<li v-for="(item, index) in todoList" :key="index" class="todo-item" @click="handleTodoClick(item.name)">
<span class="todo-name">{{ item.name }}</span>
<span class="todo-count">{{ item.count }}</span>
</li>
@ -104,11 +94,8 @@
</div>
</div>
<MessageCenter
ref="messageCenter"
:visible.sync="messageCenterVisible"
@unread-count-update="handleUnreadCountUpdate"
/>
<MessageCenter ref="messageCenter" :visible.sync="messageCenterVisible"
@unread-count-update="handleUnreadCountUpdate" />
</div>
</template>
@ -146,6 +133,7 @@ export default Vue.extend({
{ name: '处理中', count: 0 },
{ name: '站内信', count: 0 }
],
mybalance: 0,
messageCenterVisible: false,
navIcons: [icon1, icon2, icon3, icon4]
}
@ -205,9 +193,9 @@ export default Vue.extend({
async initMybalance() {
const res = await editReachargelogAPI()
if (res.status==true) {
if (res.status == true) {
this.mybalance = res.data
}else{
} else {
this.mybalance = res.data
}
},
@ -232,7 +220,7 @@ export default Vue.extend({
const res = await todoCount();
if (res.status && res.data) {
this.todoList = this.todoList.map(item => {
switch(item.name) {
switch (item.name) {
case '待支付':
return { ...item, count: res.data.pending_payment_orders || 0 };
case '待续费':
@ -256,7 +244,7 @@ export default Vue.extend({
this.openMessageCenter();
} else {
let query = {};
switch(todoName) {
switch (todoName) {
case '待支付':
query = { filterType: 'processing' };
break;
@ -389,6 +377,7 @@ export default Vue.extend({
li {
cursor: default;
&:hover {
transform: none;
box-shadow: none;
@ -501,6 +490,7 @@ export default Vue.extend({
.title {
margin-bottom: 15px;
}
.todo-list {
list-style: none;
padding: 0;
@ -567,7 +557,8 @@ export default Vue.extend({
overflow-x: hidden;
}
.leftBox, .rightBox {
.leftBox,
.rightBox {
width: 100%;
flex: none;
}

View File

@ -31,7 +31,7 @@
prop="address"
min-width="180"
label="基础网络">
<template scope="scope">
<template slot-scope="scope">
(){{ scope.row.IPSet[0].IP }}<br/>
(){{ scope.row.IPSet[1].IP }}
</template>

View File

@ -33,7 +33,6 @@ module.exports = {
// lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,
devServer: {
// public:'www.kaiyuancloud.cn',
port: port,
open: true,
overlay: {
@ -41,32 +40,8 @@ module.exports = {
errors: true
},
proxy: {
// '/api': {
// // 需要访问的地址
// target: 'http://47.93.12.75:8891', //true
// // target: 'hhttps://www.kaiyuancloud.cn/dev', //false
//
// // target: 'socks5://127.0.0.1:1086',
// // target: 'http://192.168.0.227:8891',
//
// // target: 'http://59.110.20.133:8891',
// // target: 'http://123.56.76.139:8891',
// // target: 'https://dev.kaiyuancloud.cn',
// // 开启websocket 代理
// ws: true,
// // 开启代理
// changeOrigin: true,
// // 路径重写
// // 也可以根据实际开发场景,改成其他值 '^/api' : '/其他值' 进行接口请求
// pathRewrite: {
// '^/api': ''
// }
// },
'/api': {
// target: 'http://47.93.12.75:8891',
target: 'https://dev.opencomputing.cn',
// target: 'https://www.kaiyuancloud.cn',
ws: true,
changeOrigin: true,
pathRewrite: {
@ -93,7 +68,11 @@ module.exports = {
name: name,
resolve: {
alias: {
'@': resolve('src')
'@': resolve('src'),
// 解决 echarts 地图路径问题 - 添加这3个别名
'echarts/map/js/china.js': resolve('node_modules/echarts/map/json/china.json'),
'echarts/china.json': resolve('node_modules/echarts/map/json/china.json'),
'echarts/map/json/china.json': resolve('node_modules/echarts/map/json/china.json')
}
},
// 生产环境去除console和debugger
@ -124,7 +103,6 @@ module.exports = {
}
},
chainWebpack(config) {
// it can improve the speed of the first screen, it is recommended to turn on preload
// it can improve the speed of the first screen, it is recommended to turn on preload
config.plugin('preload').tap(() => [
{
@ -156,6 +134,11 @@ module.exports = {
})
.end()
// 添加别名配置
config.resolve.alias
.set('echarts/map/js/china.js', resolve('node_modules/echarts/map/json/china.json'))
.set('echarts/china.json', resolve('node_modules/echarts/map/json/china.json'))
// 生产环境配置
config
.when(process.env.NODE_ENV !== 'development',