Commit f1a15c83 by renyizhao

大屏修改

parent c03775b8
...@@ -8,6 +8,7 @@ export interface ResourceConfig { ...@@ -8,6 +8,7 @@ export interface ResourceConfig {
sort: number; // 排序 sort: number; // 排序
status?: number; // 状态:0=启用,1=禁用 status?: number; // 状态:0=启用,1=禁用
detailRemark: string; // 详情备注(如:i7-13700K 16核24线程) detailRemark: string; // 详情备注(如:i7-13700K 16核24线程)
compute?: number; // 算力值(TOPS),默认 0,可由运营手动配置
} }
// 算力资源配置 API // 算力资源配置 API
......
<template> <template>
<div ref="mapRef" class="map-main"></div> <div ref="mapRef" class="map-main"></div>
</template> </template>
<script setup> <script setup>
import { onBeforeUnmount, onMounted, ref, nextTick, inject, watch } from 'vue' import { onBeforeUnmount, onMounted, ref, nextTick, inject, watch } from 'vue'
import * as echarts from 'echarts/core' import * as echarts from 'echarts/core'
import { TooltipComponent, VisualMapComponent, GeoComponent, GridComponent, LegendComponent } from 'echarts/components' import {
TooltipComponent,
VisualMapComponent,
GeoComponent,
GridComponent,
LegendComponent
} from 'echarts/components'
import { import {
MapChart, MapChart,
ScatterChart ScatterChart
...@@ -40,7 +45,7 @@ const geoCoordMap = ref({ ...@@ -40,7 +45,7 @@ const geoCoordMap = ref({
天津市: [117.190182, 39.125523], 天津市: [117.190182, 39.125523],
河北省: [114.502462, 38.045494], 河北省: [114.502462, 38.045494],
山西省: [112.549248, 37.857014], 山西省: [112.549248, 37.857014],
内蒙古自治区: [111.751990, 40.841470], 内蒙古自治区: [111.75199, 40.84147],
辽宁省: [123.429096, 41.796767], 辽宁省: [123.429096, 41.796767],
吉林省: [125.324498, 43.886845], 吉林省: [125.324498, 43.886845],
黑龙江省: [126.642464, 45.802755], 黑龙江省: [126.642464, 45.802755],
...@@ -51,24 +56,24 @@ const geoCoordMap = ref({ ...@@ -51,24 +56,24 @@ const geoCoordMap = ref({
福建省: [119.306239, 26.075302], 福建省: [119.306239, 26.075302],
江西省: [115.892151, 28.676461], 江西省: [115.892151, 28.676461],
山东省: [117.000923, 36.675807], 山东省: [117.000923, 36.675807],
河南省: [113.625367, 34.746570], 河南省: [113.625367, 34.74657],
湖北省: [114.305539, 30.593098], 湖北省: [114.305539, 30.593098],
湖南省: [112.982279, 28.194090], 湖南省: [112.982279, 28.19409],
广东省: [113.264385, 23.129110], 广东省: [113.264385, 23.12911],
广西壮族自治区: [108.320004, 22.824018], 广西壮族自治区: [108.320004, 22.824018],
海南省: [110.198289, 20.044008], 海南省: [110.198289, 20.044008],
重庆市: [106.551559, 29.563010], 重庆市: [106.551559, 29.56301],
四川省: [104.065735, 30.659462], 四川省: [104.065735, 30.659462],
贵州省: [106.707221, 26.598278], 贵州省: [106.707221, 26.598278],
云南省: [102.712251, 25.040609], 云南省: [102.712251, 25.040609],
西藏自治区: [91.132212, 29.660359], 西藏自治区: [91.132212, 29.660359],
陕西省: [108.939838, 34.341275], 陕西省: [108.939838, 34.341275],
甘肃省: [103.823557, 36.058039], 甘肃省: [103.823557, 36.058039],
青海省: [101.777820, 36.616990], 青海省: [101.77782, 36.61699],
宁夏回族自治区: [106.278080, 38.466370], 宁夏回族自治区: [106.27808, 38.46637],
新疆维吾尔自治区: [87.617733, 43.792818], 新疆维吾尔自治区: [87.617733, 43.792818],
台湾省: [121.520076, 25.047310], 台湾省: [121.520076, 25.04731],
香港特别行政区: [114.165460, 22.275345], 香港特别行政区: [114.16546, 22.275345],
澳门特别行政区: [113.549148, 22.198755] 澳门特别行政区: [113.549148, 22.198755]
}) })
...@@ -107,7 +112,7 @@ const convertData = function (data) { ...@@ -107,7 +112,7 @@ const convertData = function (data) {
return res return res
} }
function initMapChart (el) { function initMapChart(el) {
const chartDom = el const chartDom = el
const myChart = echarts.init(chartDom) const myChart = echarts.init(chartDom)
echarts.registerMap('china', geoJson) echarts.registerMap('china', geoJson)
...@@ -134,7 +139,8 @@ function initMapChart (el) { ...@@ -134,7 +139,8 @@ function initMapChart (el) {
}, },
label: { label: {
show: false, show: false,
color: '#fff' color: '#fff',
fontSize: 32
}, },
itemStyle: { itemStyle: {
borderColor: '#00BDCA', borderColor: '#00BDCA',
...@@ -203,7 +209,7 @@ function initMapChart (el) { ...@@ -203,7 +209,7 @@ function initMapChart (el) {
padding: [44, 0, 0, 2] padding: [44, 0, 0, 2]
} }
}, },
formatter (params) { formatter(params) {
const { const {
// dataIndex, // dataIndex,
data data
...@@ -248,7 +254,7 @@ function initMapChart (el) { ...@@ -248,7 +254,7 @@ function initMapChart (el) {
padding: [44, 0, 0, 7] padding: [44, 0, 0, 7]
} }
}, },
formatter (params) { formatter(params) {
const { const {
// dataIndex, // dataIndex,
data data
...@@ -267,7 +273,6 @@ function initMapChart (el) { ...@@ -267,7 +273,6 @@ function initMapChart (el) {
return myChart return myChart
} }
const mapRef = ref(null) const mapRef = ref(null)
let chartInst = null let chartInst = null
...@@ -288,7 +293,9 @@ onMounted(async () => { ...@@ -288,7 +293,9 @@ onMounted(async () => {
}) })
// 监听数据变化 // 监听数据变化
watch(() => dashboardData.value.mapData, (newData) => { watch(
() => dashboardData.value.mapData,
(newData) => {
mapData2.value = newData || [] mapData2.value = newData || []
if (chartInst) { if (chartInst) {
chartInst.setOption({ chartInst.setOption({
...@@ -302,7 +309,9 @@ watch(() => dashboardData.value.mapData, (newData) => { ...@@ -302,7 +309,9 @@ watch(() => dashboardData.value.mapData, (newData) => {
] ]
}) })
} }
}, { deep: true }) },
{ deep: true }
)
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('resize', resize) window.removeEventListener('resize', resize)
...@@ -315,7 +324,6 @@ onBeforeUnmount(() => { ...@@ -315,7 +324,6 @@ onBeforeUnmount(() => {
<style scoped> <style scoped>
.map-main { .map-main {
width: 100%; width: 100%;
height: 100%; height: calc(100% - 100px);
min-height: 800px;
} }
</style> </style>
...@@ -89,6 +89,7 @@ const fetchComputeDistribution = () => { ...@@ -89,6 +89,7 @@ const fetchComputeDistribution = () => {
name: getDictLabel(DICT_TYPE.COMPUTE_RESOURCE_SOURCE, item.name) || item.name, name: getDictLabel(DICT_TYPE.COMPUTE_RESOURCE_SOURCE, item.name) || item.name,
value: item.value value: item.value
})) }))
console.log('datasets', data.source)
datasets.value.resource = data.resource || [] datasets.value.resource = data.resource || []
} }
...@@ -110,18 +111,19 @@ const getOption = (which) => { ...@@ -110,18 +111,19 @@ const getOption = (which) => {
text: titles[which], text: titles[which],
left: 'center', left: 'center',
top: 12, top: 12,
textStyle: { color: '#77CCFF', fontSize: 28, fontWeight: 600 } textStyle: { color: '#77CCFF', fontSize: 32, fontWeight: 600 }
}, },
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: ({ name, value, percent }) => `${name}: ${value} (${percent}%)` formatter: ({ name, value, percent }) => `${name}: ${value} (${percent}%)`,
textStyle: { fontSize: 32 }
}, },
legend: { legend: {
bottom: 8, bottom: 8,
left: 'center', left: 'center',
itemWidth: 12, itemWidth: 12,
itemHeight: 12, itemHeight: 12,
textStyle: { color: '#DAF7FF', fontSize: 18 }, textStyle: { color: '#DAF7FF', fontSize: 32 },
data: data.map((d) => d.name), data: data.map((d) => d.name),
formatter: (name) => { formatter: (name) => {
const v = dataMap[name] ?? 0 const v = dataMap[name] ?? 0
...@@ -129,36 +131,36 @@ const getOption = (which) => { ...@@ -129,36 +131,36 @@ const getOption = (which) => {
return `${name} ${v} (${p}%)` return `${name} ${v} (${p}%)`
} }
}, },
graphic: [ // graphic: [
{ // {
type: 'text', // type: 'text',
left: '50%', // left: '50%',
top: '55%', // top: '55%',
style: { // style: {
text: `${titles[which]}\n总计: ${total}`, // text: `${titles[which]}\n总计: ${total}`,
fill: '#FFFFFF', // fill: '#FFFFFF',
fontSize: 20, // fontSize: 32,
fontWeight: 'bold', // fontWeight: 'bold',
textAlign: 'center', // textAlign: 'center',
textVerticalAlign: 'middle', // textVerticalAlign: 'middle',
lineHeight: 28, // lineHeight: 28,
textShadowColor: 'rgba(5,25,32,0.6)', // textShadowColor: 'rgba(5,25,32,0.6)',
textShadowBlur: 8 // textShadowBlur: 8
} // }
} // }
], // ],
series: [ series: [
{ {
name: titles[which], name: titles[which],
type: 'pie', type: 'pie',
radius: '62%', // 实心饼图(非环形) radius: '50%', // 实心饼图(非环形)
center: ['50%', '58%'], center: ['50%', '45%'],
avoidLabelOverlap: true, avoidLabelOverlap: true,
selectedMode: false, selectedMode: false,
labelLayout: { hideOverlap: true }, labelLayout: { hideOverlap: true },
label: { label: {
color: '#FFFFFF', color: '#FFFFFF',
fontSize: 20, fontSize: 32,
formatter: '{b}\n{d}%' formatter: '{b}\n{d}%'
}, },
labelLine: { length: 18, length2: 10 }, labelLine: { length: 18, length2: 10 },
......
...@@ -437,7 +437,7 @@ onBeforeUnmount(() => { ...@@ -437,7 +437,7 @@ onBeforeUnmount(() => {
document.removeEventListener('MSFullscreenChange', handleFsChange) document.removeEventListener('MSFullscreenChange', handleFsChange)
}) })
// 提供全屏控制给子组件(如 EnergyManage 在“今日电耗”处放按钮调用) // 提供全屏控制给子组件(如 EnergyManage 在"今日电耗"处放按钮调用)
provide('fsState', { isFullscreen, toggleFullscreen }) provide('fsState', { isFullscreen, toggleFullscreen })
</script> </script>
...@@ -474,6 +474,7 @@ provide('fsState', { isFullscreen, toggleFullscreen }) ...@@ -474,6 +474,7 @@ provide('fsState', { isFullscreen, toggleFullscreen })
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto;
} }
.page-container { .page-container {
......
...@@ -32,7 +32,7 @@ const dashboardData = inject('dashboardData', {}) ...@@ -32,7 +32,7 @@ const dashboardData = inject('dashboardData', {})
let myChart = null let myChart = null
function init () { function init() {
const chartDom = document.getElementById('nvestment') const chartDom = document.getElementById('nvestment')
if (!myChart) { if (!myChart) {
myChart = echarts.init(chartDom) myChart = echarts.init(chartDom)
...@@ -52,7 +52,8 @@ function init () { ...@@ -52,7 +52,8 @@ function init () {
label: { label: {
backgroundColor: '#6a7985' backgroundColor: '#6a7985'
} }
} },
textStyle: { fontSize: 32 }
}, },
legend: { legend: {
data: ['上线应用', '上线API'], data: ['上线应用', '上线API'],
...@@ -63,7 +64,7 @@ function init () { ...@@ -63,7 +64,7 @@ function init () {
itemGap: 40, itemGap: 40,
textStyle: { textStyle: {
color: '#77CCFF', color: '#77CCFF',
fontSize: 24 fontSize: 32
} }
}, },
grid: { grid: {
...@@ -79,7 +80,7 @@ function init () { ...@@ -79,7 +80,7 @@ function init () {
boundaryGap: true, boundaryGap: true,
data: years, data: years,
axisLabel: { axisLabel: {
fontSize: 24, fontSize: 32,
color: '#ffffff' color: '#ffffff'
}, },
axisLine: { axisLine: {
...@@ -92,15 +93,15 @@ function init () { ...@@ -92,15 +93,15 @@ function init () {
yAxis: [ yAxis: [
{ {
type: 'value', type: 'value',
name: '(万元)', name: '()',
// min: 0, // min: 0,
// interval: 2, // interval: 2,
nameTextStyle: { nameTextStyle: {
fontSize: 24, fontSize: 32,
color: '#77CCFF' color: '#77CCFF'
}, },
axisLabel: { axisLabel: {
fontSize: 24, fontSize: 32,
color: '#77CCFF' color: '#77CCFF'
}, },
splitLine: { splitLine: {
...@@ -111,13 +112,13 @@ function init () { ...@@ -111,13 +112,13 @@ function init () {
}, },
{ {
type: 'value', type: 'value',
name: '(万元)', name: '()',
nameTextStyle: { nameTextStyle: {
fontSize: 24, fontSize: 32,
color: '#77CCFF' color: '#77CCFF'
}, },
axisLabel: { axisLabel: {
fontSize: 24, fontSize: 32,
color: '#77CCFF' color: '#77CCFF'
}, },
splitLine: { splitLine: {
...@@ -176,9 +177,13 @@ onMounted(() => { ...@@ -176,9 +177,13 @@ onMounted(() => {
}) })
// 监听数据变化 // 监听数据变化
watch(() => dashboardData.value.serviceCapability, () => { watch(
() => dashboardData.value.serviceCapability,
() => {
init() init()
}, { deep: true }) },
{ deep: true }
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
......
...@@ -6,36 +6,41 @@ ...@@ -6,36 +6,41 @@
> >
<div class="trend-toolbar"> <div class="trend-toolbar">
<div class="switcher"> <div class="switcher">
<button <button :class="{ active: rangeType === 'd' }" @click="changeRange('d')" type="button"
:class="{ active: rangeType === 'd' }" >日</button
@click="changeRange('d')" >
type="button" <button :class="{ active: rangeType === 'm' }" @click="changeRange('m')" type="button"
>日</button> >月</button
<button >
:class="{ active: rangeType === 'm' }" <button :class="{ active: rangeType === 'y' }" @click="changeRange('y')" type="button"
@click="changeRange('m')" >年</button
type="button" >
>月</button>
<button
:class="{ active: rangeType === 'y' }"
@click="changeRange('y')"
type="button"
>年</button>
</div> </div>
</div> </div>
<div ref="chartRef" class="echart-wrap"></div> <div ref="chartRef" class="echart-wrap"></div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, onBeforeUnmount, inject, watch } from 'vue' import { ref, onMounted, onBeforeUnmount, watch, inject } from 'vue'
import * as echarts from 'echarts/core' import * as echarts from 'echarts/core'
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' import {
GridComponent,
TooltipComponent,
LegendComponent,
GraphicComponent
} from 'echarts/components'
import { LineChart } from 'echarts/charts' import { LineChart } from 'echarts/charts'
import { CanvasRenderer } from 'echarts/renderers' import { CanvasRenderer } from 'echarts/renderers'
echarts.use([GridComponent, TooltipComponent, LegendComponent, LineChart, CanvasRenderer]) echarts.use([
GridComponent,
TooltipComponent,
LegendComponent,
GraphicComponent,
LineChart,
CanvasRenderer
])
const chartRef = ref(null) const chartRef = ref(null)
let chart = null let chart = null
...@@ -129,41 +134,54 @@ const getChartData = () => { ...@@ -129,41 +134,54 @@ const getChartData = () => {
} }
// 直接使用后端返回的 countDate // 直接使用后端返回的 countDate
const x = data.map(item => item.countDate) const x = data.map((item) => item.countDate)
const y = data.map(item => item.callsCount) const y = data.map((item) => item.callsCount)
return { x, y } return { x, y }
} }
function getOption(x, y) { function getOption(x, y) {
// ====== 手动调整这些值看效果 ======
// xAxis 文字大小(普通屏下视觉 = 22 * 0.356 ≈ 7.8px,会突破浏览器 12px 字体下限)
const xFontSize = 32
// yAxis 文字大小
const yFontSize = 32
// yAxis 名称("请求次数")文字大小
const yNameFontSize = 32
// tooltip 弹窗文字大小(弹窗是 DOM 渲染到 body,不受外层 transform: scale 影响)
const tooltipFontSize = 32
// legend 文字大小(本图未启用 legend,可选)
// const legendFontSize = 18
// ====== 调整区结束 ======
return { return {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'line' }, axisPointer: { type: 'line' },
textStyle: { fontSize: tooltipFontSize },
formatter: (params) => { formatter: (params) => {
const p = params && params[0] const p = params && params[0]
if (!p) return '' if (!p) return ''
return `${p.axisValue}<br/>请求次数:${p.data.toLocaleString()}` return `${p.axisValue}<br/>请求次数:${p.data.toLocaleString()}`
} }
}, },
grid: { left: 90, right: 30, top: 30, bottom: 40, containLabel: true }, grid: { left: 60, right: 30, top: 90, bottom: 0, containLabel: true },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: x, data: x,
boundaryGap: false, boundaryGap: false,
axisLabel: { color: '#FFFFFF', fontSize: 22 }, axisLabel: { color: '#FFFFFF', fontSize: xFontSize },
axisLine: { lineStyle: { color: '#105E86' } } axisLine: { lineStyle: { color: '#105E86' } }
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: '请求次数', name: '请求次数',
nameLocation: 'middle', nameGap: 20,
nameRotate: 90, nameTextStyle: { color: '#77CCFF', fontSize: yNameFontSize },
nameGap: 50,
nameTextStyle: { color: '#77CCFF', fontSize: 20 },
axisLabel: { axisLabel: {
color: '#77CCFF', fontSize: 20, color: '#77CCFF',
formatter: (v) => v >= 10000 ? (v / 10000).toFixed(0) + '万' : v fontSize: yFontSize,
formatter: (v) => (v >= 10000 ? (v / 10000).toFixed(0) + '万' : v)
}, },
splitLine: { lineStyle: { color: '#105E86' } } splitLine: { lineStyle: { color: '#105E86' } }
}, },
...@@ -213,7 +231,8 @@ function detectIframe() { ...@@ -213,7 +231,8 @@ function detectIframe() {
} }
function onFsChange() { function onFsChange() {
const el = document.fullscreenElement || const el =
document.fullscreenElement ||
document.webkitFullscreenElement || document.webkitFullscreenElement ||
document.mozFullScreenElement || document.mozFullScreenElement ||
document.msFullscreenElement document.msFullscreenElement
...@@ -246,9 +265,13 @@ onMounted(() => { ...@@ -246,9 +265,13 @@ onMounted(() => {
}) })
// 监听数据变化 // 监听数据变化
watch(() => dashboardData.value.apiCalls, () => { watch(
() => dashboardData.value.apiCalls,
() => {
render() render()
}, { deep: true }) },
{ deep: true }
)
// 监听维度切换 // 监听维度切换
watch(rangeType, () => { watch(rangeType, () => {
...@@ -279,7 +302,10 @@ onBeforeUnmount(() => { ...@@ -279,7 +302,10 @@ onBeforeUnmount(() => {
margin-bottom: 8px; margin-bottom: 8px;
padding: 0 6px; padding: 0 6px;
} }
.trend-toolbar .muted { color: rgba(218,247,255,0.7); font-size: 16px; } .trend-toolbar .muted {
color: rgba(218, 247, 255, 0.7);
font-size: 16px;
}
.trend-toolbar .switcher { .trend-toolbar .switcher {
display: flex; display: flex;
...@@ -290,16 +316,16 @@ onBeforeUnmount(() => { ...@@ -290,16 +316,16 @@ onBeforeUnmount(() => {
font-size: 18px; font-size: 18px;
line-height: 1; line-height: 1;
padding: 6px 14px; padding: 6px 14px;
color: #A3E9FF; color: #a3e9ff;
background: rgba(16, 94, 134, 0.35); background: rgba(16, 94, 134, 0.35);
border: 1px solid #1DBAFF; border: 1px solid #1dbaff;
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
} }
.trend-toolbar .switcher button.active { .trend-toolbar .switcher button.active {
color: #FFFFFF; color: #ffffff;
background: linear-gradient(90deg, rgba(19,101,106,0.9), rgba(38,169,163,0.9)); background: linear-gradient(90deg, rgba(19, 101, 106, 0.9), rgba(38, 169, 163, 0.9));
} }
.echart-wrap { .echart-wrap {
......
...@@ -2,9 +2,15 @@ ...@@ -2,9 +2,15 @@
<div class="order-manage-wrap"> <div class="order-manage-wrap">
<div class="toolbar"> <div class="toolbar">
<div class="switcher"> <div class="switcher">
<button :class="{ active: dateType === 'd' }" @click="changeType('d')" type="button">日</button> <button :class="{ active: dateType === 'd' }" @click="changeType('d')" type="button"
<button :class="{ active: dateType === 'm' }" @click="changeType('m')" type="button">月</button> >日</button
<button :class="{ active: dateType === 'y' }" @click="changeType('y')" type="button">年</button> >
<button :class="{ active: dateType === 'm' }" @click="changeType('m')" type="button"
>月</button
>
<button :class="{ active: dateType === 'y' }" @click="changeType('y')" type="button"
>年</button
>
</div> </div>
<!-- 全屏按钮 --> <!-- 全屏按钮 -->
<button <button
...@@ -48,45 +54,50 @@ const { t } = useI18n() ...@@ -48,45 +54,50 @@ const { t } = useI18n()
const dateType = ref<'d' | 'm' | 'y'>('d') const dateType = ref<'d' | 'm' | 'y'>('d')
let chart: echarts.ECharts | null = null let chart: echarts.ECharts | null = null
function getOption(xData: string[], seriesData: { function getOption(
xData: string[],
seriesData: {
computeCount: number[] computeCount: number[]
apiCount: number[] apiCount: number[]
computeAmount: number[] computeAmount: number[]
apiAmount: number[] apiAmount: number[]
}): EChartsOption { }
): EChartsOption {
return { return {
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis',
textStyle: { fontSize: 32 }
}, },
legend: { legend: {
data: ['算力订单', 'API订单', '算力订单金额', 'API订单金额'], data: ['算力订单', 'API订单', '算力订单金额', 'API订单金额'],
right: '2%', right: '2%',
top: 0, top: 0,
textStyle: { color: '#A3E9FF', fontSize: 18 } textStyle: { color: '#A3E9FF', fontSize: 32 }
}, },
grid: { left: 80, right: 80, top: 40, bottom: 30, containLabel: true }, grid: { left: 80, right: 80, top: 100, bottom: 30, containLabel: true },
xAxis: { xAxis: {
type: 'category', type: 'category',
boundaryGap: false, boundaryGap: false,
data: xData, data: xData,
axisLabel: { color: '#FFFFFF', fontSize: 20 }, axisLabel: { color: '#FFFFFF', fontSize: 32 },
axisLine: { lineStyle: { color: '#105E86' } } axisLine: { lineStyle: { color: '#105E86' } }
}, },
yAxis: [ yAxis: [
{ {
type: 'value', type: 'value',
name: '数量', name: '数量',
nameTextStyle: { color: '#77CCFF', fontSize: 18 }, nameTextStyle: { color: '#77CCFF', fontSize: 32 },
axisLabel: { color: '#77CCFF', fontSize: 18 }, axisLabel: { color: '#77CCFF', fontSize: 32 },
splitLine: { lineStyle: { color: '#105E86' } } splitLine: { lineStyle: { color: '#105E86' } }
}, },
{ {
type: 'value', type: 'value',
name: '金额(元)', name: '金额(元)',
position: 'right', position: 'right',
nameTextStyle: { color: '#77CCFF', fontSize: 18 }, nameTextStyle: { color: '#77CCFF', fontSize: 32 },
axisLabel: { axisLabel: {
color: '#77CCFF', fontSize: 18, color: '#77CCFF',
fontSize: 32,
formatter: (v: number) => Number(v).toLocaleString() formatter: (v: number) => Number(v).toLocaleString()
}, },
splitLine: { show: false } splitLine: { show: false }
...@@ -146,7 +157,9 @@ async function render() { ...@@ -146,7 +157,9 @@ async function render() {
const x = res.map((item: any) => t(item.countDate)) const x = res.map((item: any) => t(item.countDate))
const computeCount = res.map((item: any) => item.computeOrdersCount || 0) const computeCount = res.map((item: any) => item.computeOrdersCount || 0)
const apiCount = res.map((item: any) => item.apiOrdersCount || 0) const apiCount = res.map((item: any) => item.apiOrdersCount || 0)
const computeAmount = res.map((item: any) => Number(((item.computeOrdersAmount || 0) / 100).toFixed(2))) const computeAmount = res.map((item: any) =>
Number(((item.computeOrdersAmount || 0) / 100).toFixed(2))
)
const apiAmount = res.map((item: any) => Number(((item.apiOrdersAmount || 0) / 100).toFixed(2))) const apiAmount = res.map((item: any) => Number(((item.apiOrdersAmount || 0) / 100).toFixed(2)))
if (!chart) { if (!chart) {
...@@ -166,9 +179,13 @@ function changeType(t: 'd' | 'm' | 'y') { ...@@ -166,9 +179,13 @@ function changeType(t: 'd' | 'm' | 'y') {
const onResize = () => chart && chart.resize() const onResize = () => chart && chart.resize()
// 监听数据变化 // 监听数据变化
watch(() => dashboardData.value.orders, () => { watch(
() => dashboardData.value.orders,
() => {
render() render()
}, { deep: true }) },
{ deep: true }
)
onMounted(() => { onMounted(() => {
render() render()
...@@ -184,7 +201,9 @@ onBeforeUnmount(() => { ...@@ -184,7 +201,9 @@ onBeforeUnmount(() => {
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.order-manage-wrap { position: relative; } .order-manage-wrap {
position: relative;
}
.toolbar { .toolbar {
position: absolute; position: absolute;
...@@ -205,19 +224,23 @@ onBeforeUnmount(() => { ...@@ -205,19 +224,23 @@ onBeforeUnmount(() => {
font-size: 18px; font-size: 18px;
line-height: 1; line-height: 1;
padding: 6px 14px; padding: 6px 14px;
color: #A3E9FF; color: #a3e9ff;
background: rgba(16, 94, 134, 0.35); background: rgba(16, 94, 134, 0.35);
border: 1px solid #1DBAFF; border: 1px solid #1dbaff;
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
} }
.switcher button.active { .switcher button.active {
color: #FFFFFF; color: #ffffff;
background: linear-gradient(90deg, rgba(19,101,106,0.9), rgba(38,169,163,0.9)); background: linear-gradient(90deg, rgba(19, 101, 106, 0.9), rgba(38, 169, 163, 0.9));
} }
.echart-wrap { height: 640px; padding-top: 100px; box-sizing: border-box; } .echart-wrap {
height: 640px;
padding-top: 100px;
box-sizing: border-box;
}
/* 全屏按钮样式,与项目现有风格一致 */ /* 全屏按钮样式,与项目现有风格一致 */
.fs-float-btn { .fs-float-btn {
......
...@@ -2,9 +2,15 @@ ...@@ -2,9 +2,15 @@
<div class="order-manage-wrap"> <div class="order-manage-wrap">
<div class="toolbar"> <div class="toolbar">
<div class="switcher"> <div class="switcher">
<button :class="{ active: dateType === 'd' }" @click="changeType('d')" type="button">日</button> <button :class="{ active: dateType === 'd' }" @click="changeType('d')" type="button"
<button :class="{ active: dateType === 'm' }" @click="changeType('m')" type="button">月</button> >日</button
<button :class="{ active: dateType === 'y' }" @click="changeType('y')" type="button">年</button> >
<button :class="{ active: dateType === 'm' }" @click="changeType('m')" type="button"
>月</button
>
<button :class="{ active: dateType === 'y' }" @click="changeType('y')" type="button"
>年</button
>
</div> </div>
</div> </div>
<div id="userManageChart" class="echart-wrap"></div> <div id="userManageChart" class="echart-wrap"></div>
...@@ -38,33 +44,41 @@ const { t } = useI18n() ...@@ -38,33 +44,41 @@ const { t } = useI18n()
const dateType = ref<'d' | 'm' | 'y'>('d') const dateType = ref<'d' | 'm' | 'y'>('d')
let chart: echarts.ECharts | null = null let chart: echarts.ECharts | null = null
function getOption(xData: string[], growth: number[], active: number[]): EChartsOption { function getOption(
xData: string[],
total: number[],
growth: number[],
active: number[]
): EChartsOption {
return { return {
tooltip: { trigger: 'axis' }, tooltip: {
trigger: 'axis',
textStyle: { fontSize: 32 }
},
legend: { legend: {
data: ['增长用户数', '活跃用户数'], data: ['累计用户数', '增长用户数', '活跃用户数'],
right: '2%', right: '2%',
top: 0, top: 0,
textStyle: { color: '#A3E9FF', fontSize: 18 } textStyle: { color: '#A3E9FF', fontSize: 32 }
}, },
grid: { left: 80, right: 40, top: 40, bottom: 30, containLabel: true }, grid: { left: 80, right: 40, top: 80, bottom: 0, containLabel: true },
xAxis: { xAxis: {
type: 'category', type: 'category',
boundaryGap: false, boundaryGap: false,
data: xData, data: xData,
axisLabel: { color: '#FFFFFF', fontSize: 20 }, axisLabel: { color: '#FFFFFF', fontSize: 32 },
axisLine: { lineStyle: { color: '#105E86' } } axisLine: { lineStyle: { color: '#105E86' } }
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: '用户数', name: '用户数',
nameTextStyle: { color: '#77CCFF', fontSize: 18 }, nameTextStyle: { color: '#77CCFF', fontSize: 32 },
axisLabel: { color: '#77CCFF', fontSize: 18 }, axisLabel: { color: '#77CCFF', fontSize: 32 },
splitLine: { lineStyle: { color: '#105E86' } } splitLine: { lineStyle: { color: '#105E86' } }
}, },
series: [ series: [
{ {
name: '增长用户数', name: '累计用户数',
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'circle', symbol: 'circle',
...@@ -72,10 +86,10 @@ function getOption(xData: string[], growth: number[], active: number[]): ECharts ...@@ -72,10 +86,10 @@ function getOption(xData: string[], growth: number[], active: number[]): ECharts
itemStyle: { color: '#67C23A' }, itemStyle: { color: '#67C23A' },
lineStyle: { width: 3, color: '#67C23A' }, lineStyle: { width: 3, color: '#67C23A' },
areaStyle: { opacity: 0.08 }, areaStyle: { opacity: 0.08 },
data: growth data: total
}, },
{ {
name: '活跃用户数', name: '增长用户数',
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'diamond', symbol: 'diamond',
...@@ -83,6 +97,17 @@ function getOption(xData: string[], growth: number[], active: number[]): ECharts ...@@ -83,6 +97,17 @@ function getOption(xData: string[], growth: number[], active: number[]): ECharts
itemStyle: { color: '#409EFF' }, itemStyle: { color: '#409EFF' },
lineStyle: { width: 3, color: '#409EFF' }, lineStyle: { width: 3, color: '#409EFF' },
areaStyle: { opacity: 0.06 }, areaStyle: { opacity: 0.06 },
data: growth
},
{
name: '活跃用户数',
type: 'line',
smooth: true,
symbol: 'triangle',
symbolSize: 6,
itemStyle: { color: '#E6A23C' },
lineStyle: { width: 3, color: '#E6A23C' },
areaStyle: { opacity: 0.05 },
data: active data: active
} }
] ]
...@@ -95,17 +120,19 @@ async function render() { ...@@ -95,17 +120,19 @@ async function render() {
const arr = users[dateType.value] || [] const arr = users[dateType.value] || []
const x = arr.map((item: any) => t(item.countDate)) const x = arr.map((item: any) => t(item.countDate))
const growth = arr.map((item: any) => item.growthUsersCount ?? item.usersCount ?? 0) // 累计用户数 = 后端 usersCount
const active = arr.map((item: any, idx: number) => const total = arr.map((item: any) => item.usersCount ?? 0)
item.activeUsersCount ?? item.visitUserCount ?? Math.round((item.usersCount ?? 0) * 1.4 + (idx % 3 === 0 ? 3 : 8)) // 增长用户数 = 后端 growthUsersCount(每日新增,独立于累计)
) const growth = arr.map((item: any) => item.growthUsersCount ?? 0)
// 活跃用户数 = 后端 activeUsersCount(按天/月对登录成功的 userId 去重计数)
const active = arr.map((item: any) => item.activeUsersCount ?? 0)
if (!chart) { if (!chart) {
const el = document.getElementById('userManageChart') as HTMLElement | null const el = document.getElementById('userManageChart') as HTMLElement | null
if (!el) return if (!el) return
chart = echarts.init(el) chart = echarts.init(el)
} }
chart.setOption(getOption(x, growth, active), true) chart.setOption(getOption(x, total, growth, active), true)
} }
function changeType(t: 'd' | 'm' | 'y') { function changeType(t: 'd' | 'm' | 'y') {
...@@ -117,9 +144,13 @@ function changeType(t: 'd' | 'm' | 'y') { ...@@ -117,9 +144,13 @@ function changeType(t: 'd' | 'm' | 'y') {
const onResize = () => chart && chart.resize() const onResize = () => chart && chart.resize()
// 监听数据变化 // 监听数据变化
watch(() => dashboardData.value.users, () => { watch(
() => dashboardData.value.users,
() => {
render() render()
}, { deep: true }) },
{ deep: true }
)
// 监听维度切换 // 监听维度切换
watch(dateType, () => { watch(dateType, () => {
...@@ -140,7 +171,9 @@ onBeforeUnmount(() => { ...@@ -140,7 +171,9 @@ onBeforeUnmount(() => {
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.order-manage-wrap { position: relative; } .order-manage-wrap {
position: relative;
}
.toolbar { .toolbar {
position: absolute; position: absolute;
...@@ -161,18 +194,21 @@ onBeforeUnmount(() => { ...@@ -161,18 +194,21 @@ onBeforeUnmount(() => {
font-size: 18px; font-size: 18px;
line-height: 1; line-height: 1;
padding: 6px 14px; padding: 6px 14px;
color: #A3E9FF; color: #a3e9ff;
background: rgba(16, 94, 134, 0.35); background: rgba(16, 94, 134, 0.35);
border: 1px solid #1DBAFF; border: 1px solid #1dbaff;
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
} }
.switcher button.active { .switcher button.active {
color: #FFFFFF; color: #ffffff;
background: linear-gradient(90deg, rgba(19,101,106,0.9), rgba(38,169,163,0.9)); background: linear-gradient(90deg, rgba(19, 101, 106, 0.9), rgba(38, 169, 163, 0.9));
} }
.echart-wrap { height: 560px; padding-top: 100px; box-sizing: border-box; } .echart-wrap {
height: 560px;
padding-top: 100px;
box-sizing: border-box;
}
</style> </style>
...@@ -42,6 +42,16 @@ ...@@ -42,6 +42,16 @@
<el-form-item label="详情备注" prop="detailRemark"> <el-form-item label="详情备注" prop="detailRemark">
<el-input v-model="formData.detailRemark" placeholder="请输入详情备注"/> <el-input v-model="formData.detailRemark" placeholder="请输入详情备注"/>
</el-form-item> </el-form-item>
<el-form-item label="算力(TOPS)" prop="compute">
<el-input-number
v-model="formData.compute"
controls-position="right"
:min="0"
:precision="2"
:step="0.01"
placeholder="默认 0,运营可手动配置"
/>
</el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
...@@ -71,7 +81,8 @@ const formData = ref({ ...@@ -71,7 +81,8 @@ const formData = ref({
configOption: undefined, configOption: undefined,
sort: undefined, sort: undefined,
status: 0, status: 0,
detailRemark: undefined detailRemark: undefined,
compute: 0
}) })
const formRules = reactive({ const formRules = reactive({
configCategory: [{required: true, message: '配置类别不能为空', trigger: 'blur'}], configCategory: [{required: true, message: '配置类别不能为空', trigger: 'blur'}],
...@@ -130,7 +141,8 @@ const resetForm = () => { ...@@ -130,7 +141,8 @@ const resetForm = () => {
configOption: undefined, configOption: undefined,
sort: undefined, sort: undefined,
status: 0, status: 0,
detailRemark: undefined detailRemark: undefined,
compute: 0
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
......
...@@ -134,6 +134,7 @@ ...@@ -134,6 +134,7 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="详情备注" align="center" prop="detailRemark" min-width="200" /> <el-table-column label="详情备注" align="center" prop="detailRemark" min-width="200" />
<el-table-column label="算力(TOPS)" align="center" prop="compute" width="120px" />
<el-table-column <el-table-column
label="创建时间" label="创建时间"
align="center" align="center"
......
<template> <template>
<ContentWrap> <ContentWrap>
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<!-- 全部订单 Tab:含搜索表单 -->
<el-tab-pane label="全部订单" name="all">
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<el-form <el-form
class="-mb-15px" class="-mb-15px"
...@@ -44,21 +47,21 @@ ...@@ -44,21 +47,21 @@
<!-- class="!w-240px"--> <!-- class="!w-240px"-->
<!-- />--> <!-- />-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
<!-- <el-form-item label="订单状态" prop="status">--> <el-form-item label="订单状态" prop="status">
<!-- <el-select--> <el-select
<!-- v-model="queryParams.status"--> v-model="queryParams.status"
<!-- placeholder="请选择订单状态"--> placeholder="请选择订单状态"
<!-- clearable--> clearable
<!-- class="!w-240px"--> class="!w-240px"
<!-- >--> >
<!-- <el-option--> <el-option
<!-- v-for="dict in getIntDictOptions(DICT_TYPE.COMPUTE_RESOURCE_ORDER_STATUS)"--> v-for="dict in getIntDictOptions(DICT_TYPE.COMPUTE_RESOURCE_ORDER_STATUS)"
<!-- :key="dict.value"--> :key="dict.value"
<!-- :label="dict.label"--> :label="dict.label"
<!-- :value="dict.value"--> :value="dict.value"
<!-- />--> />
<!-- </el-select>--> </el-select>
<!-- </el-form-item>--> </el-form-item>
<!-- <el-form-item label="市场价格" prop="marketPrice">--> <!-- <el-form-item label="市场价格" prop="marketPrice">-->
<!-- <el-input--> <!-- <el-input-->
<!-- v-model="queryParams.marketPrice"--> <!-- v-model="queryParams.marketPrice"-->
...@@ -253,7 +256,27 @@ ...@@ -253,7 +256,27 @@
</el-button> --> </el-button> -->
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </el-tab-pane>
<!-- 待发货 Tab -->
<el-tab-pane name="pendingDelivery">
<template #label>
<el-badge :value="pendingDeliveryCount" :hidden="pendingDeliveryCount === 0" :max="99">
<span>待发货</span>
</el-badge>
</template>
</el-tab-pane>
<!-- 待退款 Tab -->
<el-tab-pane name="pendingRefund">
<template #label>
<el-badge :value="pendingRefundCount" :hidden="pendingRefundCount === 0" :max="99">
<span>待退款</span>
</el-badge>
</template>
</el-tab-pane>
</el-tabs>
</ContentWrap>
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
...@@ -490,6 +513,9 @@ defineOptions({ name: 'ResourceOrder' }) ...@@ -490,6 +513,9 @@ defineOptions({ name: 'ResourceOrder' })
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const activeTab = ref<'all' | 'pendingDelivery' | 'pendingRefund'>('all') // 当前 Tab
const pendingDeliveryCount = ref(0) // 待发货数量
const pendingRefundCount = ref(0) // 待退款数量
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const list = ref<ResourceOrder[]>([]) // 列表的数据 const list = ref<ResourceOrder[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数 const total = ref(0) // 列表的总页数
...@@ -527,16 +553,49 @@ const detailRef = ref() // 详情弹窗ref ...@@ -527,16 +553,49 @@ const detailRef = ref() // 详情弹窗ref
const pdfDialogVisible = ref(false) // 发票预览弹窗 const pdfDialogVisible = ref(false) // 发票预览弹窗
const invoiceUrl = ref('') // 发票地址 const invoiceUrl = ref('') // 发票地址
/** 根据当前 Tab 解析出实际请求时的 status(待发货/待退款强制覆盖,搜索表单的 status 仅在「全部订单」Tab 生效) */
const resolveStatus = (): number | undefined => {
if (activeTab.value === 'pendingDelivery') return 5 // PENDING_DELIVERY
if (activeTab.value === 'pendingRefund') return 7 // PENDING_REFUND
return queryParams.status
}
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
const data = await ResourceOrderApi.getResourceOrderPage(queryParams) const data = await ResourceOrderApi.getResourceOrderPage({
...queryParams,
status: resolveStatus()
})
list.value = data.list list.value = data.list
total.value = data.total total.value = data.total
} finally { } finally {
loading.value = false loading.value = false
} }
// 异步刷新待发货 / 待退款角标
getPendingCount()
}
/** Tab 切换:仅重置分页与列表,status 由 resolveStatus 统一处理 */
const handleTabChange = () => {
queryParams.pageNo = 1
getList()
}
/** 获取待发货 / 待退款角标数量(不受当前 Tab / 搜索表单影响,始终统计全局) */
const getPendingCount = async () => {
try {
const [delivery, refund] = await Promise.all([
ResourceOrderApi.getResourceOrderPage({ pageNo: 1, pageSize: 1, status: 5 }),
ResourceOrderApi.getResourceOrderPage({ pageNo: 1, pageSize: 1, status: 7 })
])
pendingDeliveryCount.value = delivery?.total ?? 0
pendingRefundCount.value = refund?.total ?? 0
} catch {
pendingDeliveryCount.value = 0
pendingRefundCount.value = 0
}
} }
/** 搜索按钮操作 */ /** 搜索按钮操作 */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment