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
@@ -1,4 +1,4 @@
import ElementPlus from 'element-plus'
import ElementPlus, { ElDialog } from 'element-plus'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -12,6 +12,8 @@ import {
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
import DownloadCenterView from './DownloadCenterView.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import { ApiResponseError } from '@/shared/api/http'
vi.mock('@/modules/downloads/api/platformExportTasks', () => ({
downloadPlatformExportTask: vi.fn(),
@@ -58,6 +60,21 @@ describe('DownloadCenterView', () => {
vi.mocked(retryPlatformExportTask).mockResolvedValue({ ...task, status: 'PENDING' })
})
const dialogButton = (label: string) => {
const found = Array.from(document.querySelectorAll<HTMLButtonElement>('.el-dialog__footer button')).find(button => button.textContent?.trim() === label)
expect(found, label).toBeDefined(); return found!
}
const factValue = (label: string) => {
const term = Array.from(document.querySelectorAll('[aria-label="下载任务资料"] dt')).find(node => node.textContent === label)
expect(term, label).toBeDefined(); return term!.nextElementSibling!.textContent
}
async function openTask(value: PlatformExportTask = task) {
vi.mocked(listPlatformExportTasks).mockResolvedValue({ records: [value], total: 1, page: 1, size: 20, pages: 1 })
const wrapper = mount(DownloadCenterView, { attachTo: document.body, global: { plugins: [ElementPlus] } }); await flushPromises()
const open = () => Array.from(document.querySelectorAll<HTMLButtonElement>('.el-table__body .table-actions button')).find(button => button.textContent?.trim() === '详情')!.click()
open(); await flushPromises(); return { wrapper, open }
}
it('lists completed tasks and downloads the backend-generated artifact', async () => {
const pinia = setActivePinia(createPinia())
const auth = useAuthStore()
@@ -290,4 +307,99 @@ describe('DownloadCenterView', () => {
await flushPromises()
expect(document.body.textContent).toContain('12 / 12 行')
})
it('keeps long facts and zero values complete through fullscreen and never uses a requester ID fallback', async () => {
const value: PlatformExportTask = { ...task, id: '9223372036854775807', requestedBy: '9223372036854775806', requestedUsername: null, taskNo: 'EXP-FULL-001', taskName: '完整导出任务名称'.repeat(25), fileName: '完整导出文件名称'.repeat(25) + '.xlsx', processedRows: 0, totalRows: 0, sizeBytes: 0 }
vi.mocked(getPlatformExportTask).mockResolvedValue(value)
const { wrapper } = await openTask(value)
const dialog = wrapper.findComponent(AppDialog)
expect(dialog.props()).toMatchObject({ title: '任务详情 · EXP-FULL-001', width: 'min(720px, calc(100vw - 28px))', fullscreenable: true, showConfirm: false })
expect(document.querySelector('[aria-label="下载任务资料"]')?.textContent).toContain(value.taskName)
expect(factValue('文件名称')).toBe(value.fileName)
expect(factValue('申请人')).toBe('—')
expect(factValue('处理数据')).toBe('0 / 0 行')
expect(factValue('文件大小')).toBe('0 B')
expect(document.body.textContent).not.toContain(value.requestedBy)
expect(document.body.textContent).not.toContain(value.id)
document.querySelector<HTMLButtonElement>('[aria-label="展开全屏"]')!.click(); await flushPromises()
expect(dialog.findComponent(ElDialog).props('fullscreen')).toBe(true)
expect(factValue('文件名称')).toBe(value.fileName)
dialogButton('下载文件').click(); await flushPromises()
expect(downloadPlatformExportTask).toHaveBeenCalledWith(value.id)
})
it.each([
['RUNNING', '处理中', false], ['PENDING', '等待重试', true], ['EXPIRED', '已过期', false],
] as const)('shows %s facts and original retry/download eligibility', async (status,label,retry) => {
const value: PlatformExportTask = { ...task, status, attemptCount: 2, processedRows: 3, totalRows: null, sizeBytes: null, fileName: null, nextRetryAt: '2026-10-20T12:00:00' }
vi.mocked(getPlatformExportTask).mockResolvedValue(value)
await openTask(value)
expect(document.querySelector('.task-summary-heading')?.textContent).toContain(label)
expect(factValue('处理数据')).toBe('3 行'); expect(factValue('文件大小')).toBe('—'); expect(factValue('文件名称')).toBe('—')
expect(dialogButton('下载文件').disabled).toBe(true)
if (retry) {
expect(factValue('下次重试')).not.toBe('—')
dialogButton('立即重试').click(); await flushPromises()
expect(retryPlatformExportTask).toHaveBeenCalledWith(value.id)
} else {
expect(document.querySelector('.el-dialog__footer')?.textContent).not.toContain('重试')
expect(factValue('下次重试')).toBe('—')
}
})
it('preserves the full failure reason when retry fails and refreshes the same task after retry succeeds', async () => {
const failed: PlatformExportTask = { ...task, status: 'FAILED', failureMessage: '完整失败原因'.repeat(90) }
vi.mocked(getPlatformExportTask).mockResolvedValue(failed)
vi.mocked(retryPlatformExportTask).mockRejectedValueOnce(new Error('重试失败')).mockResolvedValueOnce({ ...failed, status: 'PENDING' })
await openTask(failed)
expect(document.querySelector('.task-failure')?.textContent).toContain(failed.failureMessage)
dialogButton('重试').click(); await flushPromises()
expect(document.querySelector('.task-failure')?.textContent).toContain(failed.failureMessage)
expect(dialogButton('重试').disabled).toBe(false)
vi.mocked(listPlatformExportTasks).mockResolvedValue({ records: [{ ...failed, status: 'PENDING', attemptCount: 0, failureMessage: null }], total: 1, page: 1, size: 20, pages: 1 })
dialogButton('重试').click(); await flushPromises()
expect(retryPlatformExportTask).toHaveBeenCalledTimes(2)
expect(document.querySelector('.task-summary-heading')?.textContent).toContain('等待处理')
expect(document.querySelector('.task-failure')).toBeNull()
expect(dialogButton('下载文件').disabled).toBe(true)
})
it('shows a compact detail error with diagnostics and restores actions only after reloading', async () => {
vi.mocked(getPlatformExportTask).mockRejectedValueOnce(new ApiResponseError('详情读取失败', 'A0500', 500, null, 'req-download-detail')).mockResolvedValueOnce(task)
await openTask()
expect(document.querySelector('.el-dialog .load-error-state')?.textContent).toContain('req-download-detail')
expect(document.querySelector('.el-dialog .load-error-state')?.classList.contains('is-compact')).toBe(true)
expect(dialogButton('下载文件').disabled).toBe(true)
const reload = Array.from(document.querySelectorAll<HTMLButtonElement>('.el-dialog button')).find(button => button.textContent?.trim() === '重新加载')!
reload.click(); await flushPromises()
expect(getPlatformExportTask).toHaveBeenCalledTimes(2)
expect(document.querySelector('.el-dialog .load-error-state')).toBeNull()
expect(dialogButton('下载文件').disabled).toBe(false)
})
it('keeps actions disabled while detail is loading and ignores an old response after close and reopen', async () => {
let release!: (value: PlatformExportTask) => void
vi.mocked(getPlatformExportTask).mockImplementationOnce(() => new Promise(resolve => { release = resolve })).mockResolvedValueOnce({ ...task, taskNo: 'EXP-NEW', taskName: '最新任务详情' })
const { wrapper, open } = await openTask()
expect(wrapper.findComponent(AppDialog).props('loading')).toBe(true)
expect(dialogButton('下载文件').disabled).toBe(true)
dialogButton('关闭').click(); await flushPromises()
wrapper.findComponent(AppDialog).vm.$emit('closed'); await flushPromises()
open(); await flushPromises()
expect(wrapper.findComponent(AppDialog).props('title')).toBe('任务详情 · EXP-NEW')
release({ ...task, taskName: '过期响应' }); await flushPromises()
expect(document.querySelector('.task-summary-heading')?.textContent).toContain('最新任务详情')
expect(document.querySelector('.task-summary-heading')?.textContent).not.toContain('过期响应')
expect(wrapper.findComponent(AppDialog).props('loading')).toBe(false)
})
it('shows a missing business number as — without exposing the internal task ID', async () => {
const value: PlatformExportTask = { ...task, id: '9223372036854775807', taskNo: '' }
vi.mocked(getPlatformExportTask).mockResolvedValue(value)
const { wrapper } = await openTask(value)
expect(wrapper.findComponent(AppDialog).props('title')).toBe('任务详情')
expect(factValue('任务编号')).toBe('—')
expect(document.querySelector('.el-dialog')?.textContent).not.toContain(value.id)
})
})