style(admin): unify dialog layouts and dense readonly presentation

This commit is contained in:
2026-10-20 10:11:31 +08:00
parent 7ff7c9914f
commit 3267679758
214 changed files with 10931 additions and 2654 deletions
@@ -16,6 +16,7 @@ import AdminPage from '@/shared/components/AdminPage.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
@@ -306,7 +307,7 @@ onBeforeUnmount(() => {
<template #type="{ row }">{{ typeLabel((row as PlatformExportTask).exportType) }}</template>
<template #status="{ row }"><el-tag :type="taskStatusMeta(row as PlatformExportTask).type" effect="light">{{ taskStatusMeta(row as PlatformExportTask).label }}</el-tag></template>
<template #progress="{ row }"><span>{{ (row as PlatformExportTask).processedRows }}<template v-if="(row as PlatformExportTask).totalRows !== null"> / {{ (row as PlatformExportTask).totalRows }}</template></span><small v-if="(row as PlatformExportTask).sizeBytes !== null">{{ formatSize((row as PlatformExportTask).sizeBytes) }}</small></template>
<template #requester="{ row }">{{ (row as PlatformExportTask).requestedUsername || (row as PlatformExportTask).requestedBy }}</template>
<template #requester="{ row }">{{ (row as PlatformExportTask).requestedUsername || '—' }}</template>
<template #requestedAt="{ row }">{{ formatDate((row as PlatformExportTask).requestedAt) }}</template>
<template #completedAt="{ row }">{{ formatDate((row as PlatformExportTask).completedAt) }}</template>
<template #expiresAt="{ row }">{{ formatDate((row as PlatformExportTask).expiresAt) }}</template>
@@ -315,30 +316,24 @@ onBeforeUnmount(() => {
<AppDialog class="admin-density-dialog"
v-model="detailOpen"
:title="detailTask ? `任务详情 · ${detailTask.taskNo}` : '任务详情'"
:title="detailTask?.taskNo?.trim() ? `任务详情 · ${detailTask.taskNo.trim()}` : '任务详情'"
width="min(720px, calc(100vw - 28px))"
fullscreenable
:loading="detailLoading"
:show-confirm="false"
destroy-on-close
@closed="closeDetail"
>
<section v-if="detailError" class="detail-load-error" role="alert">
<strong>任务详情暂时无法加载</strong>
<p>{{ detailError.message }}</p>
<ApiErrorMeta :details="detailError" />
<el-button type="primary" @click="detailTask && openDetail(detailTask)">重新加载</el-button>
</section>
<div v-else-if="detailTask" class="task-detail">
<div class="task-detail-summary">
<div>
<h3>{{ detailTask.taskName }}</h3>
<p>{{ typeLabel(detailTask.exportType) }} · {{ detailTask.format }}</p>
</div>
<el-tag :type="taskStatusMeta(detailTask).type" effect="light">{{ taskStatusMeta(detailTask).label }}</el-tag>
</div>
<LoadErrorState v-if="detailError" compact title="任务详情暂时无法加载" :message="detailError.message" :loading="detailLoading" @retry="detailTask && openDetail(detailTask)">
<template #details><ApiErrorMeta :details="detailError" /></template>
</LoadErrorState>
<div v-else-if="detailTask" class="density-dialog-sections">
<section class="density-dialog-section" data-tone="summary" aria-label="下载任务资料">
<h3 class="density-dialog-section__title task-summary-heading"><span>{{ detailTask.taskName || '—' }}</span><el-tag :type="taskStatusMeta(detailTask).type" effect="light">{{ taskStatusMeta(detailTask).label }}</el-tag></h3>
<p class="density-dialog-note">{{ typeLabel(detailTask.exportType) }} · {{ detailTask.format }}</p>
<dl class="task-detail-grid density-fields">
<div><dt>任务编号</dt><dd>{{ detailTask.taskNo }}</dd></div>
<div><dt>申请人</dt><dd>{{ detailTask.requestedUsername || detailTask.requestedBy }}</dd></div>
<div><dt>任务编号</dt><dd>{{ detailTask.taskNo || '—' }}</dd></div>
<div><dt>申请人</dt><dd>{{ detailTask.requestedUsername || '—' }}</dd></div>
<div><dt>申请时间</dt><dd>{{ formatDate(detailTask.requestedAt) }}</dd></div>
<div><dt>开始时间</dt><dd>{{ formatDate(detailTask.startedAt) }}</dd></div>
<div><dt>完成时间</dt><dd>{{ formatDate(detailTask.completedAt) }}</dd></div>
@@ -349,23 +344,21 @@ onBeforeUnmount(() => {
<div><dt>文件大小</dt><dd>{{ formatSize(detailTask.sizeBytes) || '—' }}</dd></div>
<div class="is-wide"><dt>文件名称</dt><dd>{{ detailTask.fileName || '—' }}</dd></div>
</dl>
<section v-if="detailTask.failureMessage" class="task-failure" role="status">
<strong>失败原因</strong>
<p>{{ detailTask.failureMessage }}</p>
</section>
<el-alert v-if="detailTask.failureMessage" class="task-failure" role="status" title="失败原因" :description="detailTask.failureMessage" type="error" :closable="false" show-icon />
</div>
<template #footer="{ close }">
<div class="app-dialog__footer">
<div class="density-actions density-actions--footer">
<el-button @click="close">关闭</el-button>
<el-button
v-if="detailTask && canRetry(detailTask)"
v-if="!detailLoading && !detailError && detailTask && canRetry(detailTask)"
:loading="retryingId === detailTask.id"
@click="retryTask(detailTask)"
>{{ retryLabel(detailTask) }}</el-button>
<el-button
type="primary"
:loading="downloadingId === detailTask?.id"
:disabled="detailTask?.status !== 'SUCCEEDED'"
:disabled="detailLoading || Boolean(detailError) || detailTask?.status !== 'SUCCEEDED'"
@click="downloadDetailTask"
>下载文件</el-button>
</div>
@@ -378,6 +371,8 @@ onBeforeUnmount(() => {
<style scoped>
.task-detail-grid.density-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.task-detail-grid .is-wide { grid-column: 1 / -1; }
.task-summary-heading { justify-content: space-between; }
.task-summary-heading > span { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.download-center-page { min-width: 0; }
.download-error { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-6); }
.download-error { padding: var(--admin-space-6); }
@@ -386,17 +381,6 @@ onBeforeUnmount(() => {
.download-sync-warning { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-5); padding: var(--admin-space-4) var(--admin-space-6); border-bottom: 1px solid #fedf89; background: #fffaeb; color: #93370d; font-size: var(--admin-font-caption); }
.failure-cell { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.failure-message { color: var(--cc-danger); }
.detail-load-error { display: flex; min-height: 220px; align-items: flex-start; justify-content: center; flex-direction: column; }
.detail-load-error strong { color: var(--cc-danger); font-size: var(--admin-font-section); }
.detail-load-error p { margin: var(--admin-space-2) 0; color: var(--cc-text); font-size: var(--admin-font-body); }
.detail-load-error :deep(.el-button) { margin-top: var(--admin-space-6); }
.task-detail { min-width: 0; }
.task-detail-summary { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--admin-space-6); padding-bottom: var(--admin-space-6); border-bottom: 1px solid var(--cc-border); }
.task-detail-summary h3 { margin: 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: var(--admin-font-section); }
.task-detail-summary p { margin: var(--admin-space-2) 0 0; color: var(--cc-muted); font-size: var(--admin-font-caption); }
.task-failure { margin-top: var(--admin-space-6); padding: var(--admin-space-5); border: 1px solid color-mix(in srgb, var(--cc-danger) 24%, var(--cc-border)); border-radius: var(--admin-radius); background: color-mix(in srgb, var(--cc-danger) 5%, #fff); }
.task-failure strong { color: var(--cc-danger); font-size: var(--admin-font-body); }
.task-failure p { margin: var(--admin-space-2) 0 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: var(--admin-font-body); line-height: 1.6; }
small { display: block; margin-top: 3px; color: var(--cc-muted); font-size: var(--admin-font-caption); }
@media (max-width: 720px) {
.download-error, .download-sync-warning { align-items: stretch; flex-direction: column; }
@@ -404,9 +388,7 @@ small { display: block; margin-top: 3px; color: var(--cc-muted); font-size: var(
.table-actions :deep(.el-button) { padding-right: 8px; padding-left: 8px; }
}
@media (max-width: 480px) {
.task-detail-summary { flex-direction: column; }
.task-detail-grid { grid-template-columns: 1fr; }
.task-detail-grid > div:nth-child(odd):not(.is-wide) { border-right: 0; }
.task-detail-grid .is-wide { grid-column: 1; }
}
</style>