This commit is contained in:
wangjiming
2026-08-18 14:49:34 +08:00
10 changed files with 313 additions and 36 deletions

View File

@@ -6,6 +6,8 @@ import type {
DatasetVersionList,
} from '@/types'
export type { DatasetItem }
/** 数据集列表 */
export const getDatasetList = () => get<DatasetItem[]>('/dataset-manage')
@@ -101,10 +103,14 @@ export const downloadFileUrl = (
fileId: string | number,
versionId?: string,
) => {
const baseUrl = `/modelTF/dataset-manage/download/${datasetId}/${fileId}`
const baseUrl = `/dataset-manage/download/${datasetId}/${fileId}`
return versionId ? `${baseUrl}?version_id=${encodeURIComponent(versionId)}` : baseUrl
}
/** 打包下载数据集 URL */
export const downloadDatasetUrl = (datasetId: string | number) =>
`/modelTF/dataset-manage/download/${datasetId}`
`/dataset-manage/download/${datasetId}`
/** 通过统一请求客户端下载数据集,确保携带登录 Token。 */
export const downloadDataset = (datasetId: string | number) =>
get<unknown>(downloadDatasetUrl(datasetId), undefined, { responseType: 'blob' })

View File

@@ -3,7 +3,7 @@ import { ref, onMounted, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import DataTablePage from '@/components/DataTablePage.vue'
import { getDatasetList, deleteDataset, downloadDatasetUrl } from '@/api/modules/dataset'
import { getDatasetList, deleteDataset, downloadDataset } from '@/api/modules/dataset'
import { DATASET_TYPE_MAP, STORAGE_MAP } from '@/constants'
import { formatMegabytes } from '@/utils/fileSize'
import type { DatasetItem, DatasetSource } from '@/types'
@@ -104,8 +104,29 @@ function handlePreview(row: any) {
router.push(`/dataset/${row.id}/preview`)
}
function handleDownload(row: any) {
window.open(downloadDatasetUrl(row.id), '_blank')
async function handleDownload(row: any) {
try {
const response = await downloadDataset(row.id) as {
data?: Blob
headers?: Record<string, string>
}
const blob = response.data instanceof Blob ? response.data : new Blob()
const disposition = response.headers?.['content-disposition'] || ''
const encodedName = disposition.match(/filename\*=UTF-8''([^;]+)/i)?.[1]
const filename = encodedName
? decodeURIComponent(encodedName)
: `${row.name || row.id}.zip`
const url = URL.createObjectURL(blob)
const anchor = document.createElement('a')
anchor.href = url
anchor.download = filename
document.body.appendChild(anchor)
anchor.click()
anchor.remove()
URL.revokeObjectURL(url)
} catch {
// 统一请求层已展示后端返回的失败原因。
}
}
function formatSizeMb(row: DatasetItem) {

View File

@@ -63,12 +63,12 @@ async function loadAll() {
}
// 获取当前资源类型的列表
function currentResourceList() {
function currentResourceList(): Array<{ id: string; name: string }> {
if (form.resourceType === 'dataset') {
return datasets.value.map((d) => ({ id: d.id, name: d.name || d.id }))
return datasets.value.map((d: DatasetItem) => ({ id: String(d.id), name: d.name || String(d.id) }))
}
if (form.resourceType === 'trained_model') {
return models.value.map((m) => ({ id: String(m.id || m.name || ''), name: m.name || String(m.id) || '' }))
return models.value.map((m: TrainedModel) => ({ id: String(m.id || m.name || ''), name: m.name || String(m.id) || '' }))
}
return []
}
@@ -176,9 +176,10 @@ async function saveAcl() {
}
// 显示名称
function principalName(entry: AclEntry) {
const user = users.value.find((u) => u.id === entry.principal_id)
return user ? `${user.display_name || user.username}` : entry.principal_id
function principalName(entry: unknown) {
const aclEntry = entry as AclEntry
const user = users.value.find((u) => u.id === aclEntry.principal_id)
return user ? `${user.display_name || user.username}` : aclEntry.principal_id
}
onMounted(loadAll)
@@ -250,7 +251,7 @@ onMounted(loadAll)
v-for="p in row.permissions"
:key="p"
size="small"
:type="p === 'admin' ? 'danger' : p === 'delete' || p === 'write' ? 'warning' : ''"
:type="p === 'admin' ? 'danger' : p === 'delete' || p === 'write' ? 'warning' : undefined"
>{{ p }}</el-tag>
</el-space>
</template>

View File

@@ -128,7 +128,14 @@ export function buildMetricChartOption(
color: string,
logScale = false,
): EChartsOption {
const visibleData = data.map((value) => (Number.isFinite(value) ? value : null))
// 每条曲线独立过滤缺失采样点,避免某个指标缺失导致三个图共用的 step 轴出现空洞。
const points = data
.map((value, index) => ({ value, step: steps[index] }))
.filter((point) => Number.isFinite(point.value))
const visibleData = points.map((point) => point.value)
const visibleSteps = points.map((point, index) => (
Number.isFinite(point.step) ? String(point.step) : String(index + 1)
))
return {
grid: { top: 24, right: 20, bottom: 56, left: 56 },
graphic: visibleData.some((value) => value != null)
@@ -150,7 +157,7 @@ export function buildMetricChartOption(
},
xAxis: {
type: 'category',
data: steps.map((step, index) => (Number.isFinite(step) ? String(step) : String(index + 1))),
data: visibleSteps,
boundaryGap: false,
name: 'Step',
nameTextStyle: { color: '#94a3b8', fontSize: 11 },
@@ -167,7 +174,7 @@ export function buildMetricChartOption(
axisLabel: { color: '#94a3b8', fontSize: 11 },
splitLine: { lineStyle: { color: '#f1f5f9' } },
},
dataZoom: data.length > 30
dataZoom: visibleData.length > 30
? [
{ type: 'inside', start: 0, end: 100 },
{ type: 'slider', height: 16, bottom: 8, borderColor: 'transparent', fillerColor: 'rgba(79,70,229,0.08)', handleStyle: { color: '#4f46e5' } },