2026-07-10 16:45:55 +08:00
|
|
|
<script setup lang="ts">
|
2026-07-11 10:39:26 +08:00
|
|
|
import { computed, onMounted, ref } from 'vue'
|
|
|
|
|
import { useRoute } from 'vue-router'
|
2026-07-10 16:45:55 +08:00
|
|
|
import PageCard from '@/components/PageCard.vue'
|
2026-07-11 10:39:26 +08:00
|
|
|
import { downloadFileUrl, getDataset, previewDatasetFile } from '@/api/modules/dataset'
|
2026-07-10 16:45:55 +08:00
|
|
|
import { DATASET_TYPE_MAP, STORAGE_MAP } from '@/constants'
|
2026-07-11 10:39:26 +08:00
|
|
|
import type { DatasetFile, DatasetItem } from '@/types'
|
2026-07-10 16:45:55 +08:00
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
const datasetId = route.params.id as string
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
2026-07-11 10:39:26 +08:00
|
|
|
const previewLoading = ref(false)
|
2026-07-10 16:45:55 +08:00
|
|
|
const dataset = ref<DatasetItem | null>(null)
|
2026-07-11 10:39:26 +08:00
|
|
|
const selectedFileId = ref('')
|
2026-07-10 16:45:55 +08:00
|
|
|
const previewContent = ref('')
|
2026-07-11 10:39:26 +08:00
|
|
|
const previewError = ref('')
|
|
|
|
|
let previewRequestId = 0
|
2026-07-10 16:45:55 +08:00
|
|
|
|
|
|
|
|
const files = computed<DatasetFile[]>(() => dataset.value?.files || [])
|
2026-07-11 10:39:26 +08:00
|
|
|
const selectedFile = computed(() => files.value.find((file) => fileKey(file) === selectedFileId.value))
|
|
|
|
|
const previewLines = computed(() => previewContent.value ? previewContent.value.split('\n').slice(0, 100) : [])
|
|
|
|
|
const totalLines = computed(() => previewContent.value ? previewContent.value.split('\n').length : 0)
|
|
|
|
|
const datasetType = computed(() => {
|
|
|
|
|
const value = String(dataset.value?.type || '').toLowerCase()
|
|
|
|
|
return DATASET_TYPE_MAP[value] || dataset.value?.type || '-'
|
|
|
|
|
})
|
|
|
|
|
const storageType = computed(() => {
|
|
|
|
|
const value = dataset.value?.storage_type || ''
|
|
|
|
|
return STORAGE_MAP[value] || value || '-'
|
|
|
|
|
})
|
|
|
|
|
const createdAt = computed(() => dataset.value?.create_time
|
|
|
|
|
? new Date(dataset.value.create_time).toLocaleString('zh-CN')
|
|
|
|
|
: '-')
|
2026-07-10 16:45:55 +08:00
|
|
|
|
2026-07-11 10:39:26 +08:00
|
|
|
function fileKey(file: DatasetFile) {
|
|
|
|
|
return String(file.id || file.name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fileExtension(fileName = '') {
|
|
|
|
|
return fileName.includes('.') ? fileName.split('.').pop()?.toUpperCase() : 'FILE'
|
|
|
|
|
}
|
2026-07-10 16:45:55 +08:00
|
|
|
|
|
|
|
|
async function loadDataset() {
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
dataset.value = await getDataset(datasetId)
|
2026-07-11 10:39:26 +08:00
|
|
|
const firstFile = files.value[0]
|
|
|
|
|
if (firstFile) await selectFile(firstFile)
|
2026-07-10 16:45:55 +08:00
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-11 10:39:26 +08:00
|
|
|
async function selectFile(file: DatasetFile) {
|
|
|
|
|
const nextFileId = fileKey(file)
|
|
|
|
|
selectedFileId.value = nextFileId
|
|
|
|
|
previewContent.value = ''
|
|
|
|
|
previewError.value = ''
|
|
|
|
|
previewLoading.value = true
|
|
|
|
|
const requestId = ++previewRequestId
|
|
|
|
|
|
2026-07-10 16:45:55 +08:00
|
|
|
try {
|
2026-07-11 10:39:26 +08:00
|
|
|
const res = await previewDatasetFile(nextFileId)
|
|
|
|
|
if (requestId === previewRequestId) previewContent.value = res.content || ''
|
2026-07-10 16:45:55 +08:00
|
|
|
} catch {
|
2026-07-11 10:39:26 +08:00
|
|
|
if (requestId === previewRequestId) previewError.value = '内容加载失败,请稍后重试'
|
|
|
|
|
} finally {
|
|
|
|
|
if (requestId === previewRequestId) previewLoading.value = false
|
2026-07-10 16:45:55 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-11 10:39:26 +08:00
|
|
|
function handleDownloadFile(file: DatasetFile) {
|
|
|
|
|
window.open(downloadFileUrl(datasetId, fileKey(file)), '_blank')
|
2026-07-10 16:45:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(loadDataset)
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-07-11 10:39:26 +08:00
|
|
|
<PageCard class="dataset-preview-page" v-loading="loading">
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="page-heading">
|
|
|
|
|
<div class="heading-icon" aria-hidden="true">
|
|
|
|
|
<i class="fa fa-database" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="heading-row">
|
|
|
|
|
<h1>{{ dataset?.name || '数据集预览' }}</h1>
|
|
|
|
|
<el-tag size="small" type="primary">{{ datasetType }}</el-tag>
|
|
|
|
|
<el-tag size="small" type="success">{{ storageType }}</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
<p>{{ dataset?.description || '查看数据集文件及内容样本' }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-07-10 16:45:55 +08:00
|
|
|
</template>
|
|
|
|
|
|
2026-07-11 10:39:26 +08:00
|
|
|
<section class="dataset-overview" aria-label="数据集概览">
|
|
|
|
|
<div class="overview-item">
|
|
|
|
|
<span>数据条数</span>
|
|
|
|
|
<strong>{{ (dataset?.count || 0).toLocaleString() }}</strong>
|
2026-07-10 16:45:55 +08:00
|
|
|
</div>
|
2026-07-11 10:39:26 +08:00
|
|
|
<div class="overview-item">
|
|
|
|
|
<span>数据大小</span>
|
|
|
|
|
<strong>{{ dataset?.size || '-' }}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="overview-item">
|
|
|
|
|
<span>文件数量</span>
|
|
|
|
|
<strong>{{ files.length }}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="overview-item overview-item-wide">
|
|
|
|
|
<span>创建时间</span>
|
|
|
|
|
<strong>{{ createdAt }}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section v-if="files.length" class="preview-workspace" aria-label="数据集文件预览">
|
|
|
|
|
<aside class="file-pane">
|
|
|
|
|
<div class="pane-heading">
|
|
|
|
|
<div>
|
|
|
|
|
<span class="pane-eyebrow">FILES</span>
|
|
|
|
|
<h2>数据文件</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="file-count">{{ files.length }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<nav class="file-list" aria-label="数据文件列表">
|
|
|
|
|
<button
|
|
|
|
|
v-for="file in files"
|
|
|
|
|
:key="fileKey(file)"
|
|
|
|
|
type="button"
|
|
|
|
|
class="file-item"
|
|
|
|
|
:class="{ 'is-active': fileKey(file) === selectedFileId }"
|
|
|
|
|
:aria-current="fileKey(file) === selectedFileId ? 'true' : undefined"
|
|
|
|
|
@click="selectFile(file)"
|
|
|
|
|
>
|
|
|
|
|
<span class="file-type" aria-hidden="true">{{ fileExtension(file.name) }}</span>
|
|
|
|
|
<span class="file-copy">
|
|
|
|
|
<strong :title="file.name">{{ file.name }}</strong>
|
|
|
|
|
<small>{{ file.size || '未知大小' }}</small>
|
|
|
|
|
</span>
|
|
|
|
|
<i v-if="fileKey(file) === selectedFileId" class="fa fa-angle-right" aria-hidden="true" />
|
|
|
|
|
</button>
|
|
|
|
|
</nav>
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
<div class="content-pane">
|
|
|
|
|
<div class="content-toolbar">
|
|
|
|
|
<div class="content-title">
|
|
|
|
|
<span class="file-icon" aria-hidden="true"><i class="fa fa-file-text-o" /></span>
|
|
|
|
|
<div>
|
|
|
|
|
<h2>{{ selectedFile?.name }}</h2>
|
|
|
|
|
<p>
|
|
|
|
|
{{ selectedFile?.size || '未知大小' }}
|
|
|
|
|
<span aria-hidden="true">·</span>
|
|
|
|
|
{{ totalLines.toLocaleString() }} 行
|
|
|
|
|
<span v-if="totalLines > 100" aria-hidden="true">·</span>
|
|
|
|
|
<span v-if="totalLines > 100">显示前 100 行</span>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<el-tooltip content="下载当前文件" placement="top">
|
|
|
|
|
<el-button
|
|
|
|
|
class="download-button"
|
|
|
|
|
aria-label="下载当前文件"
|
|
|
|
|
:disabled="!selectedFile"
|
|
|
|
|
@click="selectedFile && handleDownloadFile(selectedFile)"
|
|
|
|
|
>
|
|
|
|
|
<i class="fa fa-download" />
|
|
|
|
|
下载文件
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="viewer-shell" :aria-busy="previewLoading">
|
|
|
|
|
<div v-if="previewLoading" class="viewer-state">
|
|
|
|
|
<i class="fa fa-circle-o-notch fa-spin" aria-hidden="true" />
|
|
|
|
|
<span>正在加载文件内容…</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-else-if="previewError" class="viewer-state viewer-state-error" role="alert">
|
|
|
|
|
<i class="fa fa-exclamation-circle" aria-hidden="true" />
|
|
|
|
|
<strong>{{ previewError }}</strong>
|
|
|
|
|
<el-button size="small" @click="selectedFile && selectFile(selectedFile)">重新加载</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-else-if="!previewLines.length" class="viewer-state">
|
|
|
|
|
<i class="fa fa-file-o" aria-hidden="true" />
|
|
|
|
|
<span>当前文件暂无可预览内容</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-else class="code-viewer" tabindex="0" :aria-label="`${selectedFile?.name || ''} 文件内容`">
|
|
|
|
|
<div v-for="(line, index) in previewLines" :key="index" class="code-line">
|
|
|
|
|
<span class="line-number" aria-hidden="true">{{ index + 1 }}</span>
|
|
|
|
|
<code>{{ line || ' ' }}</code>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="viewer-footer">
|
|
|
|
|
<span><i class="fa fa-eye" aria-hidden="true" /> 只读预览</span>
|
|
|
|
|
<span>UTF-8</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section v-else-if="!loading" class="empty-files" aria-label="空文件列表">
|
|
|
|
|
<span class="empty-icon" aria-hidden="true"><i class="fa fa-folder-open-o" /></span>
|
|
|
|
|
<h2>暂无数据文件</h2>
|
|
|
|
|
<p>该数据集还没有可供预览的文件。</p>
|
|
|
|
|
</section>
|
2026-07-10 16:45:55 +08:00
|
|
|
</PageCard>
|
|
|
|
|
</template>
|
|
|
|
|
|
2026-07-11 10:39:26 +08:00
|
|
|
<style scoped lang="scss">
|
|
|
|
|
.dataset-preview-page {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-heading {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.heading-icon {
|
|
|
|
|
display: grid;
|
|
|
|
|
flex: 0 0 44px;
|
|
|
|
|
width: 44px;
|
|
|
|
|
height: 44px;
|
|
|
|
|
place-items: center;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
background: linear-gradient(145deg, #eef2ff, #e0e7ff);
|
|
|
|
|
color: var(--primary-color);
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.heading-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.heading-row h1 {
|
|
|
|
|
margin: 0 4px 0 0;
|
|
|
|
|
color: #172033;
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
line-height: 1.35;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-heading p {
|
|
|
|
|
margin: 4px 0 0;
|
|
|
|
|
color: #7c8799;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dataset-overview {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(3, minmax(120px, 0.7fr)) minmax(220px, 1.2fr);
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
padding: 16px 20px;
|
|
|
|
|
border: 1px solid #edf0f5;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
background: #fbfcfe;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.overview-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 5px;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
border-right: 1px solid #e9edf4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.overview-item:first-child {
|
|
|
|
|
padding-left: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.overview-item:last-child {
|
|
|
|
|
padding-right: 0;
|
|
|
|
|
border-right: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.overview-item span {
|
|
|
|
|
color: #8a94a6;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.overview-item strong {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
color: #283347;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
2026-07-10 16:45:55 +08:00
|
|
|
font-weight: 600;
|
2026-07-11 10:39:26 +08:00
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
2026-07-10 16:45:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-11 10:39:26 +08:00
|
|
|
.preview-workspace {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: minmax(240px, 280px) minmax(0, 1fr);
|
|
|
|
|
min-height: 520px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
border: 1px solid #e5eaf2;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
background: #fff;
|
2026-07-10 16:45:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-11 10:39:26 +08:00
|
|
|
.file-pane {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
border-right: 1px solid #e5eaf2;
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pane-heading {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
min-height: 68px;
|
2026-07-10 16:45:55 +08:00
|
|
|
padding: 12px 16px;
|
2026-07-11 10:39:26 +08:00
|
|
|
border-bottom: 1px solid #e5eaf2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pane-eyebrow {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-bottom: 2px;
|
|
|
|
|
color: #9aa4b5;
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
letter-spacing: 1.1px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pane-heading h2,
|
|
|
|
|
.content-title h2 {
|
|
|
|
|
margin: 0;
|
|
|
|
|
color: #263146;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-count {
|
|
|
|
|
display: grid;
|
|
|
|
|
min-width: 26px;
|
|
|
|
|
height: 26px;
|
|
|
|
|
place-items: center;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
background: #e9edf4;
|
|
|
|
|
color: #596579;
|
2026-07-10 16:45:55 +08:00
|
|
|
font-size: 12px;
|
2026-07-11 10:39:26 +08:00
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-list {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
padding: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-height: 58px;
|
|
|
|
|
margin: 0 0 4px;
|
|
|
|
|
padding: 8px 10px;
|
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
border-radius: 9px;
|
|
|
|
|
background: transparent;
|
|
|
|
|
color: #526076;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
text-align: left;
|
|
|
|
|
transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-item:hover {
|
|
|
|
|
border-color: #dce2eb;
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-item:focus-visible {
|
|
|
|
|
outline: 2px solid var(--el-color-primary-light-5);
|
|
|
|
|
outline-offset: 1px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-item.is-active {
|
|
|
|
|
border-color: #cfd5ff;
|
|
|
|
|
background: #fff;
|
|
|
|
|
color: var(--primary-color);
|
|
|
|
|
box-shadow: 0 5px 16px rgba(79, 70, 229, 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-type {
|
|
|
|
|
display: grid;
|
|
|
|
|
flex: 0 0 42px;
|
|
|
|
|
height: 32px;
|
|
|
|
|
place-items: center;
|
|
|
|
|
border-radius: 7px;
|
|
|
|
|
background: #e9edf4;
|
|
|
|
|
color: #68758b;
|
|
|
|
|
font-size: 9px;
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
letter-spacing: 0.35px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-item.is-active .file-type {
|
|
|
|
|
background: #eef2ff;
|
|
|
|
|
color: var(--primary-color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-copy {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex: 1;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 3px;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-copy strong {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-copy small {
|
|
|
|
|
color: #9aa4b5;
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-pane {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-toolbar {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
min-height: 68px;
|
|
|
|
|
padding: 10px 16px;
|
|
|
|
|
border-bottom: 1px solid #e5eaf2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-title {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-title > div {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-title h2 {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-title p {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
margin: 3px 0 0;
|
|
|
|
|
color: #8a94a6;
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-icon {
|
|
|
|
|
display: grid;
|
|
|
|
|
flex: 0 0 34px;
|
|
|
|
|
height: 34px;
|
|
|
|
|
place-items: center;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
background: #f1f5f9;
|
|
|
|
|
color: #69768b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.download-button {
|
|
|
|
|
min-height: 36px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.download-button i {
|
|
|
|
|
margin-right: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.viewer-shell {
|
|
|
|
|
position: relative;
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 400px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
background: #fbfcfe;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.code-viewer {
|
|
|
|
|
height: 100%;
|
|
|
|
|
max-height: 468px;
|
2026-07-10 16:45:55 +08:00
|
|
|
overflow: auto;
|
2026-07-11 10:39:26 +08:00
|
|
|
padding: 12px 0 18px;
|
|
|
|
|
outline: none;
|
2026-07-10 16:45:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-11 10:39:26 +08:00
|
|
|
.code-viewer:focus-visible {
|
|
|
|
|
box-shadow: inset 0 0 0 2px var(--el-color-primary-light-5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.code-line {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 54px minmax(0, 1fr);
|
|
|
|
|
min-height: 25px;
|
|
|
|
|
color: #344054;
|
|
|
|
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
|
2026-07-10 16:45:55 +08:00
|
|
|
font-size: 12px;
|
2026-07-11 10:39:26 +08:00
|
|
|
line-height: 1.65;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.code-line:hover {
|
|
|
|
|
background: #f4f6fa;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.line-number {
|
|
|
|
|
padding-right: 14px;
|
|
|
|
|
border-right: 1px solid #edf0f5;
|
|
|
|
|
color: #a4adbc;
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
text-align: right;
|
|
|
|
|
user-select: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.code-line code {
|
|
|
|
|
display: block;
|
|
|
|
|
min-width: max-content;
|
|
|
|
|
padding: 0 18px;
|
|
|
|
|
white-space: pre;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.viewer-state {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
height: 100%;
|
|
|
|
|
min-height: 400px;
|
|
|
|
|
color: #8b96a8;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.viewer-state > i {
|
|
|
|
|
color: #aeb7c5;
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.viewer-state-error > i,
|
|
|
|
|
.viewer-state-error strong {
|
|
|
|
|
color: #d14343;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.viewer-state-error strong {
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.viewer-footer {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
min-height: 34px;
|
|
|
|
|
padding: 6px 14px;
|
|
|
|
|
border-top: 1px solid #e5eaf2;
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
color: #8a94a6;
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.viewer-footer i {
|
|
|
|
|
margin-right: 5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.empty-files {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
min-height: 420px;
|
|
|
|
|
border: 1px dashed #dce2eb;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
background: #fbfcfe;
|
2026-07-10 16:45:55 +08:00
|
|
|
text-align: center;
|
|
|
|
|
}
|
2026-07-11 10:39:26 +08:00
|
|
|
|
|
|
|
|
.empty-icon {
|
|
|
|
|
display: grid;
|
|
|
|
|
width: 54px;
|
|
|
|
|
height: 54px;
|
|
|
|
|
margin-bottom: 14px;
|
|
|
|
|
place-items: center;
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
background: #eef2f7;
|
|
|
|
|
color: #8a96a8;
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.empty-files h2 {
|
|
|
|
|
margin: 0;
|
|
|
|
|
color: #354056;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.empty-files p {
|
|
|
|
|
margin: 6px 0 0;
|
|
|
|
|
color: #929daf;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
|
.dataset-overview {
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
gap: 16px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.overview-item:nth-child(2) {
|
|
|
|
|
border-right: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.overview-item:nth-child(3) {
|
|
|
|
|
padding-left: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.preview-workspace {
|
|
|
|
|
grid-template-columns: 220px minmax(0, 1fr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 680px) {
|
|
|
|
|
.dataset-overview {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.overview-item {
|
|
|
|
|
padding: 0 0 12px;
|
|
|
|
|
border-right: 0;
|
|
|
|
|
border-bottom: 1px solid #e9edf4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.overview-item:last-child {
|
|
|
|
|
padding-bottom: 0;
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.preview-workspace {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.file-pane {
|
|
|
|
|
max-height: 240px;
|
|
|
|
|
border-right: 0;
|
|
|
|
|
border-bottom: 1px solid #e5eaf2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-toolbar {
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.download-button {
|
|
|
|
|
width: 44px;
|
|
|
|
|
padding: 8px;
|
|
|
|
|
font-size: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.download-button i {
|
|
|
|
|
margin: 0;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
|
|
|
.file-item {
|
|
|
|
|
transition: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-10 16:45:55 +08:00
|
|
|
</style>
|