feat: 看板服务状态优化,移除前端访问统计埋点,修正列表页文案与类型

- 看板:补齐各服务模块图标,服务状态表格改为自适应行高并支持滚动
- 移除前端请求访问统计埋点及对应 audit-visit API 模块
- 平台接口:清理 service_checks 循环中未使用的路径变量
- 系统模块:导出 SystemUser 类型
- 项目/租户列表:统一“项目名称/编码 ID/租户 ID”文案,补充表格行类型断言修复

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
wuyongtao
2026-08-04 11:34:04 +08:00
parent 7b36bc774e
commit 250e060271
7 changed files with 28 additions and 59 deletions

View File

@@ -421,7 +421,7 @@ async def dashboard_stats() -> dict[str, Any]:
("数据类型转换", "/data-convert", "数据类型转换"), ("数据类型转换", "/data-convert", "数据类型转换"),
] ]
service_status = [] service_status = []
for svc_type, path, _label in service_checks: for svc_type, _path, _label in service_checks:
try: try:
svc_count = 0 svc_count = 0
if svc_type == "模型训练": if svc_type == "模型训练":

View File

@@ -1,5 +0,0 @@
import { post } from '../request'
/** 记录用户访问某个业务模块(用于看板用户操作分布统计) */
export const recordModuleVisit = (module: string, detail?: string) =>
post('/system/audit/visit', { action: module, detail: detail || '' })

View File

@@ -8,6 +8,8 @@ import type {
UpdateUserAccessPayload, UpdateUserAccessPayload,
} from '@/types' } from '@/types'
export type { SystemUser } from '@/types'
/** 系统信息CPU/内存/磁盘/GPU/网络/系统) */ /** 系统信息CPU/内存/磁盘/GPU/网络/系统) */
export const getSystemInfo = () => get<SystemInfo>('/system-info') export const getSystemInfo = () => get<SystemInfo>('/system-info')

View File

@@ -1,37 +1,6 @@
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios' import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
/**
* 用户操作分布:哪些模块路径算"业务操作"(用于看板统计)
* 请求命中这些路径时,会自动调用 record_visit 记录一次(同一模块 60 秒内去重)
*/
const VISIT_TRACKED_PREFIXES: Array<[string, string]> = [
['/fine-tune', 'fine-tune'],
['/model-eval', 'model-eval'],
['/model-inference', 'model-inference'],
['/data-process', 'data-process'],
['/data-convert', 'data-convert'],
['/model-manage', 'model-manage'],
['/dataset-manage', 'dataset'],
]
function trackVisit(url: string | undefined) {
if (!url) return
for (const [prefix, module] of VISIT_TRACKED_PREFIXES) {
if (url.includes(prefix)) {
const key = `visit:${module}`
const last = Number(sessionStorage.getItem(key) || 0)
if (Date.now() - last < 60000) return // 60 秒内去重
sessionStorage.setItem(key, String(Date.now()))
// fire-and-forget 调用后端记录接口
import('./modules/audit-visit').then(({ recordModuleVisit }) => {
recordModuleVisit(module, url).catch(() => { /* ignore */ })
}).catch(() => { /* ignore */ })
return
}
}
}
/** /**
* 后端统一响应格式 * 后端统一响应格式
* code === 0 表示成功data 为业务数据 * code === 0 表示成功data 为业务数据
@@ -91,8 +60,6 @@ service.interceptors.response.use(
return response return response
} }
if (res.code === 0) { if (res.code === 0) {
// 记录业务模块访问(用于看板用户操作分布统计)
trackVisit(response.config.url)
return res.data return res.data
} }
// 业务错误 // 业务错误

View File

@@ -66,8 +66,12 @@ const operationDistribution = ref<{ name: string; value: number }[]>([])
const serviceIcon: Record<string, string> = { const serviceIcon: Record<string, string> = {
'模型推理': 'fa-cube', '模型推理': 'fa-cube',
'模型微调': 'fa-sliders', '模型微调': 'fa-sliders',
'模型训练': 'fa-sliders',
'模型评测': 'fa-bar-chart', '模型评测': 'fa-bar-chart',
'模型管理': 'fa-cubes',
'数据集管理': 'fa-file-text',
'数据处理': 'fa-filter', '数据处理': 'fa-filter',
'数据类型转换': 'fa-exchange',
} }
const roleLabel: Record<string, string> = { const roleLabel: Record<string, string> = {
admin: '超级管理员', admin: '超级管理员',
@@ -115,7 +119,7 @@ const chartOption = computed<EChartsOption>(() => ({
borderWidth: 0, borderWidth: 0,
padding: [10, 12], padding: [10, 12],
textStyle: { color: '#ffffff', fontSize: 12 }, textStyle: { color: '#ffffff', fontSize: 12 },
valueFormatter: (value) => `${value}`, valueFormatter: (value) => String(value ?? ''),
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
@@ -252,7 +256,7 @@ const loginDurationChartOption = computed<EChartsOption>(() => {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'shadow' }, axisPointer: { type: 'shadow' },
valueFormatter: (value: number) => `${value} 小时`, valueFormatter: (value: unknown) => String(Number(Array.isArray(value) ? value[0] : value) || 0) + ' 小时',
}, },
xAxis: { xAxis: {
type: 'value', type: 'value',
@@ -738,10 +742,11 @@ function viewTask(task: DashboardTask) {
.service-table { .service-table {
display: grid; display: grid;
grid-template-rows: 36px repeat(4, minmax(48px, 1fr)); grid-auto-rows: minmax(44px, auto);
flex: 1 1 auto; flex: 1 1 auto;
margin-top: 12px; margin-top: 12px;
min-height: 0; min-height: 0;
overflow-y: auto;
} }
.service-row { .service-row {
@@ -935,7 +940,7 @@ function viewTask(task: DashboardTask) {
} }
.service-table { .service-table {
grid-template-rows: 32px repeat(4, minmax(40px, 1fr)); grid-auto-rows: minmax(38px, auto);
margin-top: 8px; margin-top: 8px;
} }

View File

@@ -99,7 +99,7 @@ onMounted(() => {
<el-button type="primary" :icon="Plus" @click="showCreate = true">新建项目</el-button> <el-button type="primary" :icon="Plus" @click="showCreate = true">新建项目</el-button>
</template> </template>
<template #columns> <template #columns>
<el-table-column prop="name" label="项目名" min-width="140" /> <el-table-column prop="name" label="项目名" min-width="140" />
<el-table-column prop="code" label="编码 ID" min-width="100" /> <el-table-column prop="code" label="编码 ID" min-width="100" />
<el-table-column prop="status" label="状态" min-width="100" /> <el-table-column prop="status" label="状态" min-width="100" />
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip /> <el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
@@ -107,13 +107,13 @@ onMounted(() => {
</template> </template>
<template #actions="{ row }"> <template #actions="{ row }">
<el-button link type="primary" @click="openDetail(row.id)">详情</el-button> <el-button link type="primary" @click="openDetail(row.id)">详情</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button> <el-button link type="danger" @click="handleDelete(asProject(row))">删除</el-button>
</template> </template>
</DataTablePage> </DataTablePage>
<el-dialog v-model="showCreate" title="新建项目" width="520px"> <el-dialog v-model="showCreate" title="新建项目" width="520px">
<el-form label-width="90px"> <el-form label-width="90px">
<el-form-item label="名称" required> <el-form-item label="名称" required>
<el-input v-model="form.name" placeholder="项目名" /> <el-input v-model="form.name" placeholder="项目名" />
</el-form-item> </el-form-item>
<el-form-item label="编码 ID" required> <el-form-item label="编码 ID" required>
<el-select v-model="form.tenant_id" style="width: 100%" placeholder="选择租户编码"> <el-select v-model="form.tenant_id" style="width: 100%" placeholder="选择租户编码">

View File

@@ -30,7 +30,7 @@ function formatQuota(quota: Record<string, unknown> | undefined | null) {
if (q.gpu > 0) parts.push(`GPU ${q.gpu}`) if (q.gpu > 0) parts.push(`GPU ${q.gpu}`)
if (q.storage > 0) parts.push(`存储 ${q.storage}GB`) if (q.storage > 0) parts.push(`存储 ${q.storage}GB`)
if (q.maxProjects > 0) parts.push(`项目 ${q.maxProjects}`) if (q.maxProjects > 0) parts.push(`项目 ${q.maxProjects}`)
return parts.length ? parts.join(' | ') : '' return parts.length ? parts.join(' | ') : '-'
} }
async function load() { async function load() {
@@ -52,7 +52,7 @@ function asTenant(row: unknown): Tenant {
function quotaText(row: unknown): string { function quotaText(row: unknown): string {
const quota = asTenant(row).quota || {} const quota = asTenant(row).quota || {}
return Object.keys(quota).length ? JSON.stringify(quota) : '' return Object.keys(quota).length ? JSON.stringify(quota) : '-'
} }
async function submitCreate() { async function submitCreate() {
@@ -118,13 +118,13 @@ onMounted(load)
</template> </template>
<template #columns> <template #columns>
<el-table-column prop="name" label="租户名称" min-width="140" /> <el-table-column prop="name" label="租户名称" min-width="140" />
<el-table-column prop="code" label="用户ID" min-width="100" /> <el-table-column prop="code" label="租户 ID" min-width="100" />
<el-table-column prop="status" label="状态" min-width="100" /> <el-table-column prop="status" label="状态" min-width="100" />
<el-table-column prop="create_time" label="创建时间" min-width="180" /> <el-table-column prop="create_time" label="创建时间" min-width="180" />
</template> </template>
<template #actions="{ row }"> <template #actions="{ row }">
<el-button link type="primary" @click="openDetail(row.id)">详情</el-button> <el-button link type="primary" @click="openDetail(row.id)">详情</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button> <el-button link type="danger" @click="handleDelete(asTenant(row))">删除</el-button>
</template> </template>
</DataTablePage> </DataTablePage>