Commit 1fe9a380 by Jony.L

大屏数据:大地图 真实数据 管理端调整

parent 4ffe89c8
...@@ -16,6 +16,7 @@ export interface ResourceSpu { ...@@ -16,6 +16,7 @@ export interface ResourceSpu {
nic?: string; // 网卡配置 nic?: string; // 网卡配置
ip?: string; // 服务器ip ip?: string; // 服务器ip
location?: string; // 位置 location?: string; // 位置
areaId?: number; // 地区ID
initUsername?: string; // 初始用户名 initUsername?: string; // 初始用户名
initPassword?: string; // 初始密码 initPassword?: string; // 初始密码
intro?: string; // 商品简介 intro?: string; // 商品简介
......
...@@ -34,20 +34,18 @@ ...@@ -34,20 +34,18 @@
<div class="statistical-item"> <div class="statistical-item">
<i></i> <i></i>
<div> <div>
<div class="label">算力利用率</div> <div class="label">闲置算力</div>
<div class="value">{{ dashboardData.overallSituation.computeUtilizationRate }}%</div> <div class="value">
<!-- 42.37%--> {{ dashboardData.overallSituation.idleCompute }} TOPS
</div>
</div> </div>
</div> </div>
<div class="statistical-item"> <div class="statistical-item">
<i></i> <i></i>
<div> <div>
<div class="label">运行中任务数</div> <div class="label">算力利用率</div>
<div class="value"> <div class="value">{{ dashboardData.overallSituation.computeUtilizationRate }}%</div>
{{ dashboardData.overallSituation.runningTaskCount }} <!-- 42.37%-->
<!-- 29
<animation-count :end-val="0.83" decimals :range-min="40" :range-max="42" />-->
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -268,6 +266,11 @@ const fetchRealData = async () => { ...@@ -268,6 +266,11 @@ const fetchRealData = async () => {
if (res.carouselItems) { if (res.carouselItems) {
dashboardData.value.carouselItems = res.carouselItems dashboardData.value.carouselItems = res.carouselItems
} }
// 地图数据
if (res.mapData) {
dashboardData.value.mapData = res.mapData
}
} }
} catch (error) { } catch (error) {
console.error('获取真实数据失败:', error) console.error('获取真实数据失败:', error)
......
...@@ -21,9 +21,6 @@ ...@@ -21,9 +21,6 @@
</div> </div>
</el-card> </el-card>
<!-- 第一行:平台总体态势 + API请求趋势 -->
<el-row :gutter="20">
<el-col :span="12">
<!-- 平台总体态势 --> <!-- 平台总体态势 -->
<el-card class="config-card" shadow="never"> <el-card class="config-card" shadow="never">
<template #header> <template #header>
...@@ -33,35 +30,34 @@ ...@@ -33,35 +30,34 @@
<el-button type="primary" size="small" @click="saveOverallSituation">保存</el-button> <el-button type="primary" size="small" @click="saveOverallSituation">保存</el-button>
</div> </div>
</template> </template>
<el-form :model="overallSituation" label-width="140px" class="config-form"> <el-form :model="overallSituation" label-width="130px" class="config-form">
<el-row :gutter="20"> <el-row :gutter="0">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="算力总规模(P)"> <el-form-item label="算力总规模(TOPS)">
<el-input-number v-model="overallSituation.allCompute" :precision="2" :min="0" /> <el-input-number v-model="overallSituation.allCompute" :precision="2" :min="0" :controls="false" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="已租赁算力(P)"> <el-form-item label="已租赁算力(TOPS)">
<el-input-number v-model="overallSituation.leaseCompute" :precision="2" :min="0" /> <el-input-number v-model="overallSituation.leaseCompute" :precision="2" :min="0" :controls="false" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="算力利用率(%)"> <el-form-item label="闲置算力(TOPS)">
<el-input-number v-model="overallSituation.computeUtilizationRate" :precision="2" :min="0" :max="100" /> <el-input-number v-model="overallSituation.idleCompute" :precision="2" :min="0" :controls="false" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="运行中任务数"> <el-form-item label="算力利用率(%)">
<el-input-number v-model="overallSituation.runningTaskCount" :min="0" /> <el-input-number v-model="overallSituation.computeUtilizationRate" :precision="2" :min="0" :max="100" :controls="false" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
</el-card> </el-card>
</el-col>
<!-- 第二行:API请求趋势 + 算力资源分布 -->
<el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<!-- API请求趋势 --> <!-- API请求趋势 -->
<el-card class="config-card" shadow="never"> <el-card class="config-card" shadow="never">
...@@ -72,11 +68,14 @@ ...@@ -72,11 +68,14 @@
<el-button type="primary" size="small" @click="saveApiCalls">保存</el-button> <el-button type="primary" size="small" @click="saveApiCalls">保存</el-button>
</div> </div>
</template> </template>
<el-tabs v-model="apiCallsTab" type="border-card">
<!-- 日维度 -->
<el-tab-pane label="日" name="d">
<div class="table-section"> <div class="table-section">
<el-button type="primary" size="small" @click="addApiCallItem" style="margin-bottom: 10px;"> <el-button type="primary" size="small" @click="addApiCallItem('d')" style="margin-bottom: 10px;">
<Icon icon="ep:plus" class="mr-5px" />添加数据项 <Icon icon="ep:plus" class="mr-5px" />添加数据项
</el-button> </el-button>
<table class="data-table" v-if="apiCallsList.length > 0"> <table class="data-table" v-if="apiCallsData.d && apiCallsData.d.length > 0">
<thead> <thead>
<tr> <tr>
<th>日期</th> <th>日期</th>
...@@ -85,7 +84,7 @@ ...@@ -85,7 +84,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(item, index) in apiCallsList" :key="index"> <tr v-for="(item, index) in apiCallsData.d" :key="index">
<td> <td>
<el-date-picker <el-date-picker
v-model="item.countDate" v-model="item.countDate"
...@@ -97,10 +96,201 @@ ...@@ -97,10 +96,201 @@
/> />
</td> </td>
<td> <td>
<el-input-number v-model="item.callsCount" :min="0" size="small" /> <el-input-number v-model="item.callsCount" :min="0" :controls="false" size="small" />
</td>
<td>
<el-button type="danger" size="small" @click="removeApiCallItem('d', index)">
删除
</el-button>
</td>
</tr>
</tbody>
</table>
<el-empty v-else description="暂无数据,请添加" :image-size="80" />
</div>
</el-tab-pane>
<!-- 月维度 -->
<el-tab-pane label="月" name="m">
<div class="table-section">
<el-button type="primary" size="small" @click="addApiCallItem('m')" style="margin-bottom: 10px;">
<Icon icon="ep:plus" class="mr-5px" />添加数据项
</el-button>
<table class="data-table" v-if="apiCallsData.m && apiCallsData.m.length > 0">
<thead>
<tr>
<th>日期</th>
<th>调用次数</th>
<th style="width: 80px">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in apiCallsData.m" :key="index">
<td>
<el-date-picker
v-model="item.countDate"
type="month"
placeholder="选择月份"
format="YYYY-MM"
value-format="YYYY-MM"
size="small"
/>
</td>
<td>
<el-input-number v-model="item.callsCount" :min="0" :controls="false" size="small" />
</td>
<td>
<el-button type="danger" size="small" @click="removeApiCallItem('m', index)">
删除
</el-button>
</td>
</tr>
</tbody>
</table>
<el-empty v-else description="暂无数据,请添加" :image-size="80" />
</div>
</el-tab-pane>
<!-- 年维度 -->
<el-tab-pane label="年" name="y">
<div class="table-section">
<el-button type="primary" size="small" @click="addApiCallItem('y')" style="margin-bottom: 10px;">
<Icon icon="ep:plus" class="mr-5px" />添加数据项
</el-button>
<table class="data-table" v-if="apiCallsData.y && apiCallsData.y.length > 0">
<thead>
<tr>
<th>年份</th>
<th>调用次数</th>
<th style="width: 80px">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in apiCallsData.y" :key="index">
<td>
<el-input v-model="item.countDate" placeholder="如:2025" size="small" />
</td>
<td>
<el-input-number v-model="item.callsCount" :min="0" :controls="false" size="small" />
</td>
<td>
<el-button type="danger" size="small" @click="removeApiCallItem('y', index)">
删除
</el-button>
</td>
</tr>
</tbody>
</table>
<el-empty v-else description="暂无数据,请添加" :image-size="80" />
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
<el-col :span="12">
<!-- 算力资源分布 -->
<el-card class="config-card" shadow="never">
<template #header>
<div class="card-header">
<Icon icon="ep:pie-chart" :size="20" />
<span class="title">算力资源分布</span>
<el-button type="primary" size="small" @click="saveComputeDistribution">保存</el-button>
</div>
</template>
<el-tabs v-model="activeTab" type="border-card">
<!-- GPU型号 -->
<el-tab-pane label="GPU型号" name="gpu">
<div class="table-section">
<el-button type="primary" size="small" @click="addComputeItem('gpu')" style="margin-bottom: 10px;">
<Icon icon="ep:plus" class="mr-5px" />添加GPU型号
</el-button>
<table class="data-table" v-if="computeDistribution.gpu && computeDistribution.gpu.length > 0">
<thead>
<tr>
<th>型号名称</th>
<th>占比</th>
<th style="width: 80px">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in computeDistribution.gpu" :key="index">
<td>
<el-input v-model="item.name" placeholder="如:4090" size="small" />
</td>
<td>
<el-input-number v-model="item.value" :precision="2" :min="0" :controls="false" size="small" />
</td>
<td>
<el-button type="danger" size="small" @click="removeComputeItem('gpu', index)">
删除
</el-button>
</td>
</tr>
</tbody>
</table>
<el-empty v-else description="暂无数据,请添加" :image-size="80" />
</div>
</el-tab-pane>
<!-- 算力来源 -->
<el-tab-pane label="算力来源" name="source">
<div class="table-section">
<el-button type="primary" size="small" @click="addComputeItem('source')" style="margin-bottom: 10px;">
<Icon icon="ep:plus" class="mr-5px" />添加来源
</el-button>
<table class="data-table" v-if="computeDistribution.source && computeDistribution.source.length > 0">
<thead>
<tr>
<th>来源名称</th>
<th>占比</th>
<th style="width: 80px">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in computeDistribution.source" :key="index">
<td>
<el-input v-model="item.name" placeholder="如:自有" size="small" />
</td>
<td>
<el-input-number v-model="item.value" :precision="2" :min="0" :controls="false" size="small" />
</td>
<td>
<el-button type="danger" size="small" @click="removeComputeItem('source', index)">
删除
</el-button>
</td>
</tr>
</tbody>
</table>
<el-empty v-else description="暂无数据,请添加" :image-size="80" />
</div>
</el-tab-pane>
<!-- 计算资源 -->
<el-tab-pane label="计算资源" name="resource">
<div class="table-section">
<el-button type="primary" size="small" @click="addComputeItem('resource')" style="margin-bottom: 10px;">
<Icon icon="ep:plus" class="mr-5px" />添加资源类型
</el-button>
<table class="data-table" v-if="computeDistribution.resource && computeDistribution.resource.length > 0">
<thead>
<tr>
<th>资源名称</th>
<th>占比</th>
<th style="width: 80px">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in computeDistribution.resource" :key="index">
<td>
<el-input v-model="item.name" placeholder="如:裸金属" size="small" />
</td>
<td>
<el-input-number v-model="item.value" :precision="2" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-button type="danger" size="small" @click="removeApiCallItem(index)"> <el-button type="danger" size="small" @click="removeComputeItem('resource', index)">
删除 删除
</el-button> </el-button>
</td> </td>
...@@ -109,13 +299,16 @@ ...@@ -109,13 +299,16 @@
</table> </table>
<el-empty v-else description="暂无数据,请添加" :image-size="80" /> <el-empty v-else description="暂无数据,请添加" :image-size="80" />
</div> </div>
</el-tab-pane>
</el-tabs>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
<!-- 第二行:算力资源分布 + 用户管理 --> <!-- 第三行:用户管理 + 服务能力 -->
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<!-- 用户管理 -->
<!-- 算力资源分布 --> <!-- 算力资源分布 -->
<el-card class="config-card" shadow="never"> <el-card class="config-card" shadow="never">
<template #header> <template #header>
...@@ -146,7 +339,7 @@ ...@@ -146,7 +339,7 @@
<el-input v-model="item.name" placeholder="如:4090" size="small" /> <el-input v-model="item.name" placeholder="如:4090" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.value" :precision="2" :min="0" size="small" /> <el-input-number v-model="item.value" :precision="2" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-button type="danger" size="small" @click="removeComputeItem('gpu', index)"> <el-button type="danger" size="small" @click="removeComputeItem('gpu', index)">
...@@ -180,7 +373,7 @@ ...@@ -180,7 +373,7 @@
<el-input v-model="item.name" placeholder="如:自有" size="small" /> <el-input v-model="item.name" placeholder="如:自有" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.value" :precision="2" :min="0" size="small" /> <el-input-number v-model="item.value" :precision="2" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-button type="danger" size="small" @click="removeComputeItem('source', index)"> <el-button type="danger" size="small" @click="removeComputeItem('source', index)">
...@@ -214,7 +407,7 @@ ...@@ -214,7 +407,7 @@
<el-input v-model="item.name" placeholder="如:裸金属" size="small" /> <el-input v-model="item.name" placeholder="如:裸金属" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.value" :precision="2" :min="0" size="small" /> <el-input-number v-model="item.value" :precision="2" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-button type="danger" size="small" @click="removeComputeItem('resource', index)"> <el-button type="danger" size="small" @click="removeComputeItem('resource', index)">
...@@ -241,11 +434,14 @@ ...@@ -241,11 +434,14 @@
<el-button type="primary" size="small" @click="saveUsers">保存</el-button> <el-button type="primary" size="small" @click="saveUsers">保存</el-button>
</div> </div>
</template> </template>
<el-tabs v-model="usersTab" type="border-card">
<!-- 日维度 -->
<el-tab-pane label="日" name="d">
<div class="table-section"> <div class="table-section">
<el-button type="primary" size="small" @click="addUserItem" style="margin-bottom: 10px;"> <el-button type="primary" size="small" @click="addUserItem('d')" style="margin-bottom: 10px;">
<Icon icon="ep:plus" class="mr-5px" />添加数据项 <Icon icon="ep:plus" class="mr-5px" />添加数据项
</el-button> </el-button>
<table class="data-table" v-if="usersList.length > 0"> <table class="data-table" v-if="usersData.d && usersData.d.length > 0">
<thead> <thead>
<tr> <tr>
<th>日期</th> <th>日期</th>
...@@ -256,7 +452,7 @@ ...@@ -256,7 +452,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(item, index) in usersList" :key="index"> <tr v-for="(item, index) in usersData.d" :key="index">
<td> <td>
<el-date-picker <el-date-picker
v-model="item.countDate" v-model="item.countDate"
...@@ -268,16 +464,16 @@ ...@@ -268,16 +464,16 @@
/> />
</td> </td>
<td> <td>
<el-input-number v-model="item.usersCount" :min="0" size="small" /> <el-input-number v-model="item.usersCount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.growthUsersCount" :min="0" size="small" /> <el-input-number v-model="item.growthUsersCount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.activeUsersCount" :min="0" size="small" /> <el-input-number v-model="item.activeUsersCount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-button type="danger" size="small" @click="removeUserItem(index)"> <el-button type="danger" size="small" @click="removeUserItem('d', index)">
删除 删除
</el-button> </el-button>
</td> </td>
...@@ -286,6 +482,99 @@ ...@@ -286,6 +482,99 @@
</table> </table>
<el-empty v-else description="暂无数据,请添加" :image-size="80" /> <el-empty v-else description="暂无数据,请添加" :image-size="80" />
</div> </div>
</el-tab-pane>
<!-- 月维度 -->
<el-tab-pane label="月" name="m">
<div class="table-section">
<el-button type="primary" size="small" @click="addUserItem('m')" style="margin-bottom: 10px;">
<Icon icon="ep:plus" class="mr-5px" />添加数据项
</el-button>
<table class="data-table" v-if="usersData.m && usersData.m.length > 0">
<thead>
<tr>
<th>日期</th>
<th>用户总数</th>
<th>增长用户数</th>
<th>活跃用户数</th>
<th style="width: 80px">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in usersData.m" :key="index">
<td>
<el-date-picker
v-model="item.countDate"
type="month"
placeholder="选择月份"
format="YYYY-MM"
value-format="YYYY-MM"
size="small"
/>
</td>
<td>
<el-input-number v-model="item.usersCount" :min="0" :controls="false" size="small" />
</td>
<td>
<el-input-number v-model="item.growthUsersCount" :min="0" :controls="false" size="small" />
</td>
<td>
<el-input-number v-model="item.activeUsersCount" :min="0" :controls="false" size="small" />
</td>
<td>
<el-button type="danger" size="small" @click="removeUserItem('m', index)">
删除
</el-button>
</td>
</tr>
</tbody>
</table>
<el-empty v-else description="暂无数据,请添加" :image-size="80" />
</div>
</el-tab-pane>
<!-- 年维度 -->
<el-tab-pane label="年" name="y">
<div class="table-section">
<el-button type="primary" size="small" @click="addUserItem('y')" style="margin-bottom: 10px;">
<Icon icon="ep:plus" class="mr-5px" />添加数据项
</el-button>
<table class="data-table" v-if="usersData.y && usersData.y.length > 0">
<thead>
<tr>
<th>年份</th>
<th>用户总数</th>
<th>增长用户数</th>
<th>活跃用户数</th>
<th style="width: 80px">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in usersData.y" :key="index">
<td>
<el-input v-model="item.countDate" placeholder="如:2025" size="small" />
</td>
<td>
<el-input-number v-model="item.usersCount" :min="0" :controls="false" size="small" />
</td>
<td>
<el-input-number v-model="item.growthUsersCount" :min="0" :controls="false" size="small" />
</td>
<td>
<el-input-number v-model="item.activeUsersCount" :min="0" :controls="false" size="small" />
</td>
<td>
<el-button type="danger" size="small" @click="removeUserItem('y', index)">
删除
</el-button>
</td>
</tr>
</tbody>
</table>
<el-empty v-else description="暂无数据,请添加" :image-size="80" />
</div>
</el-tab-pane>
</el-tabs>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
...@@ -302,17 +591,40 @@ ...@@ -302,17 +591,40 @@
<el-button type="primary" size="small" @click="saveServiceCapability">保存</el-button> <el-button type="primary" size="small" @click="saveServiceCapability">保存</el-button>
</div> </div>
</template> </template>
<el-form :model="serviceCapability" label-width="120px" class="config-form"> <div class="table-section">
<el-form-item label="年份"> <el-button type="primary" size="small" @click="addServiceCapabilityItem" style="margin-bottom: 10px;">
<el-input v-model="serviceCapability.yearsStr" placeholder="如:2018,2019,2020" /> <Icon icon="ep:plus" class="mr-5px" />添加数据项
</el-form-item> </el-button>
<el-form-item label="上线应用"> <table class="data-table" v-if="serviceCapabilityList.length > 0">
<el-input v-model="serviceCapability.appOnlineStr" placeholder="如:10,20,30,40,50(逗号分隔)" /> <thead>
</el-form-item> <tr>
<el-form-item label="上线API"> <th>年份</th>
<el-input v-model="serviceCapability.apiOnlineStr" placeholder="如:100,200,300,400,500(逗号分隔)" /> <th>上线应用数</th>
</el-form-item> <th>上线API数</th>
</el-form> <th style="width: 80px">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in serviceCapabilityList" :key="index">
<td>
<el-input v-model="item.year" placeholder="如:2018" size="small" />
</td>
<td>
<el-input-number v-model="item.appOnline" :min="0" :controls="false" size="small" />
</td>
<td>
<el-input-number v-model="item.apiOnline" :min="0" :controls="false" size="small" />
</td>
<td>
<el-button type="danger" size="small" @click="removeServiceCapabilityItem(index)">
删除
</el-button>
</td>
</tr>
</tbody>
</table>
<el-empty v-else description="暂无数据,请添加" :image-size="80" />
</div>
</el-card> </el-card>
</el-col> </el-col>
...@@ -357,16 +669,16 @@ ...@@ -357,16 +669,16 @@
/> />
</td> </td>
<td> <td>
<el-input-number v-model="item.computeOrdersCount" :min="0" size="small" /> <el-input-number v-model="item.computeOrdersCount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.apiOrdersCount" :min="0" size="small" /> <el-input-number v-model="item.apiOrdersCount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.computeOrdersAmount" :min="0" size="small" /> <el-input-number v-model="item.computeOrdersAmount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.apiOrdersAmount" :min="0" size="small" /> <el-input-number v-model="item.apiOrdersAmount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-button type="danger" size="small" @click="removeOrderItem('d', index)"> <el-button type="danger" size="small" @click="removeOrderItem('d', index)">
...@@ -410,16 +722,16 @@ ...@@ -410,16 +722,16 @@
/> />
</td> </td>
<td> <td>
<el-input-number v-model="item.computeOrdersCount" :min="0" size="small" /> <el-input-number v-model="item.computeOrdersCount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.apiOrdersCount" :min="0" size="small" /> <el-input-number v-model="item.apiOrdersCount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.computeOrdersAmount" :min="0" size="small" /> <el-input-number v-model="item.computeOrdersAmount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.apiOrdersAmount" :min="0" size="small" /> <el-input-number v-model="item.apiOrdersAmount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-button type="danger" size="small" @click="removeOrderItem('m', index)"> <el-button type="danger" size="small" @click="removeOrderItem('m', index)">
...@@ -456,16 +768,16 @@ ...@@ -456,16 +768,16 @@
<el-input v-model="item.countDate" placeholder="如:2025" size="small" /> <el-input v-model="item.countDate" placeholder="如:2025" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.computeOrdersCount" :min="0" size="small" /> <el-input-number v-model="item.computeOrdersCount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.apiOrdersCount" :min="0" size="small" /> <el-input-number v-model="item.apiOrdersCount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.computeOrdersAmount" :min="0" size="small" /> <el-input-number v-model="item.computeOrdersAmount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-input-number v-model="item.apiOrdersAmount" :min="0" size="small" /> <el-input-number v-model="item.apiOrdersAmount" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-button type="danger" size="small" @click="removeOrderItem('y', index)"> <el-button type="danger" size="small" @click="removeOrderItem('y', index)">
...@@ -485,7 +797,7 @@ ...@@ -485,7 +797,7 @@
<!-- 地图数据 --> <!-- 地图数据 -->
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="24"> <el-col :span="12">
<el-card class="config-card" shadow="never"> <el-card class="config-card" shadow="never">
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
...@@ -514,7 +826,7 @@ ...@@ -514,7 +826,7 @@
</el-select> </el-select>
</td> </td>
<td> <td>
<el-input-number v-model="item.value" :precision="4" :min="0" size="small" /> <el-input-number v-model="item.value" :precision="4" :min="0" :controls="false" size="small" />
</td> </td>
<td> <td>
<el-button type="danger" size="small" @click="removeMapDataItem(index)"> <el-button type="danger" size="small" @click="removeMapDataItem(index)">
...@@ -551,21 +863,27 @@ const overallSituation = ref({ ...@@ -551,21 +863,27 @@ const overallSituation = ref({
allCompute: 432.58, allCompute: 432.58,
leaseCompute: 139.94, leaseCompute: 139.94,
computeUtilizationRate: 42.37, computeUtilizationRate: 42.37,
runningTaskCount: 29 idleCompute: 292.64
}) })
// API请求趋势 // API请求趋势
const apiCallsList = ref([]) const apiCallsTab = ref('d')
const apiCallsData = ref({
d: [],
m: [],
y: []
})
// 用户管理 // 用户管理
const usersList = ref([]) const usersTab = ref('d')
const usersData = ref({
d: [],
m: [],
y: []
})
// 服务能力 // 服务能力
const serviceCapability = ref({ const serviceCapabilityList = ref([])
yearsStr: '',
appOnlineStr: '',
apiOnlineStr: ''
})
// 算力资源分布 // 算力资源分布
const activeTab = ref('gpu') const activeTab = ref('gpu')
...@@ -611,7 +929,9 @@ const loadAllMockData = async () => { ...@@ -611,7 +929,9 @@ const loadAllMockData = async () => {
} }
} else if (item.configKey === 'mock_api_calls') { } else if (item.configKey === 'mock_api_calls') {
try { try {
apiCallsList.value = JSON.parse(item.configValue) const data = JSON.parse(item.configValue)
// API请求趋势数据是对象格式,包含 d/m/y 三个维度
apiCallsData.value = data
} catch (e) { } catch (e) {
console.error('解析API请求趋势失败', e) console.error('解析API请求趋势失败', e)
} }
...@@ -623,18 +943,21 @@ const loadAllMockData = async () => { ...@@ -623,18 +943,21 @@ const loadAllMockData = async () => {
} }
} else if (item.configKey === 'mock_users') { } else if (item.configKey === 'mock_users') {
try { try {
usersList.value = JSON.parse(item.configValue) const data = JSON.parse(item.configValue)
// 用户管理数据是对象格式,包含 d/m/y 三个维度
usersData.value = data
} catch (e) { } catch (e) {
console.error('解析用户管理失败', e) console.error('解析用户管理失败', e)
} }
} else if (item.configKey === 'mock_service_capability') { } else if (item.configKey === 'mock_service_capability') {
try { try {
const data = JSON.parse(item.configValue) const data = JSON.parse(item.configValue)
serviceCapability.value = { // 将原来分开的数组合并成表格行
yearsStr: data.years ? data.years.join(',') : '', serviceCapabilityList.value = (data.years || []).map((year, index) => ({
appOnlineStr: data.appOnline ? data.appOnline.join(',') : '', year: year,
apiOnlineStr: data.apiOnline ? data.apiOnline.join(',') : '' appOnline: data.appOnline ? data.appOnline[index] || 0 : 0,
} apiOnline: data.apiOnline ? data.apiOnline[index] || 0 : 0
}))
} catch (e) { } catch (e) {
console.error('解析服务能力失败', e) console.error('解析服务能力失败', e)
} }
...@@ -701,7 +1024,7 @@ const saveApiCalls = async () => { ...@@ -701,7 +1024,7 @@ const saveApiCalls = async () => {
if (res && res.list) { if (res && res.list) {
const config = res.list.find(item => item.configKey === 'mock_api_calls') const config = res.list.find(item => item.configKey === 'mock_api_calls')
if (config) { if (config) {
const updateData = { ...config, configValue: JSON.stringify(apiCallsList.value) } const updateData = { ...config, configValue: JSON.stringify(apiCallsData.value) }
await HomeDashboardMockApi.updateHomeDashboardMock(updateData) await HomeDashboardMockApi.updateHomeDashboardMock(updateData)
message.success('保存成功') message.success('保存成功')
} }
...@@ -712,17 +1035,20 @@ const saveApiCalls = async () => { ...@@ -712,17 +1035,20 @@ const saveApiCalls = async () => {
} }
} }
// 添加API请求数据项 // API请求趋势 - 添加数据项
const addApiCallItem = () => { const addApiCallItem = (type: 'd' | 'm' | 'y') => {
apiCallsList.value.push({ if (!apiCallsData.value[type]) {
apiCallsData.value[type] = []
}
apiCallsData.value[type].push({
countDate: '', countDate: '',
callsCount: 0 callsCount: 0
}) })
} }
// 删除API请求数据项 // API请求趋势 - 删除数据项
const removeApiCallItem = (index: number) => { const removeApiCallItem = (type: 'd' | 'm' | 'y', index: number) => {
apiCallsList.value.splice(index, 1) apiCallsData.value[type].splice(index, 1)
} }
// 保存算力资源分布 // 保存算力资源分布
...@@ -766,7 +1092,7 @@ const saveUsers = async () => { ...@@ -766,7 +1092,7 @@ const saveUsers = async () => {
if (res && res.list) { if (res && res.list) {
const config = res.list.find(item => item.configKey === 'mock_users') const config = res.list.find(item => item.configKey === 'mock_users')
if (config) { if (config) {
const updateData = { ...config, configValue: JSON.stringify(usersList.value) } const updateData = { ...config, configValue: JSON.stringify(usersData.value) }
await HomeDashboardMockApi.updateHomeDashboardMock(updateData) await HomeDashboardMockApi.updateHomeDashboardMock(updateData)
message.success('保存成功') message.success('保存成功')
} }
...@@ -777,9 +1103,12 @@ const saveUsers = async () => { ...@@ -777,9 +1103,12 @@ const saveUsers = async () => {
} }
} }
// 添加用户数据项 // 用户管理 - 添加数据项
const addUserItem = () => { const addUserItem = (type: 'd' | 'm' | 'y') => {
usersList.value.push({ if (!usersData.value[type]) {
usersData.value[type] = []
}
usersData.value[type].push({
countDate: '', countDate: '',
usersCount: 0, usersCount: 0,
growthUsersCount: 0, growthUsersCount: 0,
...@@ -787,9 +1116,9 @@ const addUserItem = () => { ...@@ -787,9 +1116,9 @@ const addUserItem = () => {
}) })
} }
// 删除用户数据项 // 用户管理 - 删除数据项
const removeUserItem = (index: number) => { const removeUserItem = (type: 'd' | 'm' | 'y', index: number) => {
usersList.value.splice(index, 1) usersData.value[type].splice(index, 1)
} }
// 保存服务能力 // 保存服务能力
...@@ -798,10 +1127,11 @@ const saveServiceCapability = async () => { ...@@ -798,10 +1127,11 @@ const saveServiceCapability = async () => {
const res = await HomeDashboardMockApi.getHomeDashboardMockPage({ pageNo: 1, pageSize: 100 }) const res = await HomeDashboardMockApi.getHomeDashboardMockPage({ pageNo: 1, pageSize: 100 })
if (res && res.list) { if (res && res.list) {
const config = res.list.find(item => item.configKey === 'mock_service_capability') const config = res.list.find(item => item.configKey === 'mock_service_capability')
// 将表格数据转换成原来的格式(分开的数组)
const data = { const data = {
years: serviceCapability.value.yearsStr.split(',').map(s => s.trim()), years: serviceCapabilityList.value.map(item => item.year),
appOnline: serviceCapability.value.appOnlineStr.split(',').map(s => parseFloat(s.trim()) || 0), appOnline: serviceCapabilityList.value.map(item => item.appOnline),
apiOnline: serviceCapability.value.apiOnlineStr.split(',').map(s => parseFloat(s.trim()) || 0) apiOnline: serviceCapabilityList.value.map(item => item.apiOnline)
} }
if (config) { if (config) {
const updateData = { ...config, configValue: JSON.stringify(data) } const updateData = { ...config, configValue: JSON.stringify(data) }
...@@ -823,6 +1153,20 @@ const saveServiceCapability = async () => { ...@@ -823,6 +1153,20 @@ const saveServiceCapability = async () => {
} }
} }
// 服务能力 - 添加数据项
const addServiceCapabilityItem = () => {
serviceCapabilityList.value.push({
year: '',
appOnline: 0,
apiOnline: 0
})
}
// 服务能力 - 删除数据项
const removeServiceCapabilityItem = (index: number) => {
serviceCapabilityList.value.splice(index, 1)
}
// 订单管理 - 保存 // 订单管理 - 保存
const saveOrders = async () => { const saveOrders = async () => {
try { try {
......
...@@ -130,15 +130,30 @@ ...@@ -130,15 +130,30 @@
<!-- 5. 服务器信息 --> <!-- 5. 服务器信息 -->
<div style="display: flex; margin-bottom: 24px;"> <div style="display: flex; margin-bottom: 24px;">
<el-form-item label="服务器所在地" prop="location" style="margin-right: 20px; margin-bottom: 0;"> <el-form-item label="选择地区" prop="areaId" style="margin-bottom: 0;">
<el-select v-model="formData.location" placeholder="请选择服务器所在地" clearable style="width: 180px;" :disabled="isDetailMode"> <el-cascader
<el-option v-if="areaList.length > 0"
v-for="option in resourceConfigStore.getOptionsByType('location')" v-model="formData.areaId"
:key="option.id" :options="areaList"
:label="option.configOption" :props="{
:value="option.configOption" value: 'id',
label: 'name',
children: 'children',
expandTrigger: 'click'
}"
placeholder="请选择省市区"
clearable
filterable
style="width: 300px;"
:disabled="isDetailMode"
@change="handleAreaChange"
/> />
</el-select> <el-text v-else type="info">加载中...</el-text>
</el-form-item>
</div>
<div style="display: flex; margin-bottom: 24px;">
<el-form-item label="服务器所在地" prop="location" style="margin-right: 20px; margin-bottom: 0;">
<el-input v-model="formData.location" placeholder="选择地区后自动显示" style="width: 180px;" disabled />
</el-form-item> </el-form-item>
<el-form-item label="服务器IP" prop="ip" style="margin-bottom: 0;"> <el-form-item label="服务器IP" prop="ip" style="margin-bottom: 0;">
<el-input v-model="formData.ip" placeholder="请输入服务器IP" style="width: 180px;" :disabled="isDetailMode" /> <el-input v-model="formData.ip" placeholder="请输入服务器IP" style="width: 180px;" :disabled="isDetailMode" />
...@@ -217,6 +232,7 @@ import { UploadImg } from '@/components/UploadFile' ...@@ -217,6 +232,7 @@ import { UploadImg } from '@/components/UploadFile'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import { useResourceConfigStore } from '@/store/modules/compute/hardwareConfig' import { useResourceConfigStore } from '@/store/modules/compute/hardwareConfig'
import { ResourceSpu, ResourceSpuApi } from '@/api/compute/resourcespu' import { ResourceSpu, ResourceSpuApi } from '@/api/compute/resourcespu'
import * as AreaApi from '@/api/system/area'
defineOptions({ name: 'ResourceSpuAdd' }) defineOptions({ name: 'ResourceSpuAdd' })
...@@ -239,6 +255,7 @@ const resourceConfigStore = useResourceConfigStore() // 资源配置store ...@@ -239,6 +255,7 @@ const resourceConfigStore = useResourceConfigStore() // 资源配置store
const formLoading = ref(false) // 表单的加载中 const formLoading = ref(false) // 表单的加载中
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
const categoryList = ref<any[]>([]) const categoryList = ref<any[]>([])
const areaList = ref<any[]>([]) // 地区树数据
// 从store获取硬件配置选项 // 从store获取硬件配置选项
const cpuOptions = computed(() => resourceConfigStore.getOptionsByType('cpu')) const cpuOptions = computed(() => resourceConfigStore.getOptionsByType('cpu'))
...@@ -271,6 +288,7 @@ const formData = ref<ResourceSpu>({ ...@@ -271,6 +288,7 @@ const formData = ref<ResourceSpu>({
memoryCapacity: undefined, memoryCapacity: undefined,
sales: undefined, sales: undefined,
location: undefined, location: undefined,
areaId: undefined,
status: undefined status: undefined
}) })
...@@ -292,10 +310,41 @@ const formRules = reactive({ ...@@ -292,10 +310,41 @@ const formRules = reactive({
categoryId: [{ required: true, message: '算力资源分类编号不能为空', trigger: 'blur' }], categoryId: [{ required: true, message: '算力资源分类编号不能为空', trigger: 'blur' }],
picUrl: [{ required: true, message: '商品封面图不能为空', trigger: 'blur' }], picUrl: [{ required: true, message: '商品封面图不能为空', trigger: 'blur' }],
sales: [{ required: true, message: '商品销量不能为空', trigger: 'blur' }], sales: [{ required: true, message: '商品销量不能为空', trigger: 'blur' }],
location: [{ required: true, message: '服务器所在地不能为空', trigger: 'blur' }], areaId: [{ required: true, message: '服务器所在地不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }] status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
}) })
/** 地区选择变化时,自动设置 location 为市级名称(第二级) */
const handleAreaChange = (value: number[]) => {
if (!value || value.length === 0) {
formData.value.location = undefined
formData.value.areaId = undefined
return
}
// el-cascader 返回的是数组 [省级id, 市级id, 区县级id]
// 取区县级id(最后一个元素)作为 areaId 的值
const districtId = value[value.length - 1]
formData.value.areaId = districtId
// 获取市级id(第二个元素)
const cityId = value[1]
// 查找市级名称
const findCityName = (list: any[], cityId: number): string | null => {
for (const item of list) {
if (item.children) {
for (const city of item.children) {
if (city.id === cityId) {
return city.name
}
}
}
}
return null
}
formData.value.location = findCityName(areaList.value, cityId) || undefined
}
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
...@@ -330,12 +379,14 @@ const close = () => { ...@@ -330,12 +379,14 @@ const close = () => {
/** 初始化 */ /** 初始化 */
onMounted(async () => { onMounted(async () => {
try { try {
// 并行加载分类列表和硬件配置选项 // 并行加载分类列表、硬件配置选项和地区树
const [categoryResponse] = await Promise.all([ const [categoryResponse, _, areaResponse] = await Promise.all([
ResourceSpuApi.listSimpleCategory(), ResourceSpuApi.listSimpleCategory(),
resourceConfigStore.loadConfigOptions() resourceConfigStore.loadConfigOptions(),
AreaApi.getAreaTree()
]) ])
categoryList.value = categoryResponse categoryList.value = categoryResponse
areaList.value = areaResponse
// 编辑模式或详情模式需要获取数据 // 编辑模式或详情模式需要获取数据
if (isEditMode.value || isDetailMode.value) { if (isEditMode.value || isDetailMode.value) {
......
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