This commit is contained in:
2026-08-10 16:24:54 +08:00
parent e119b48662
commit 820432a381
52 changed files with 1015 additions and 705 deletions
@@ -86,4 +86,25 @@ describe('CarrierRatesView price-book source contract', () => {
expect(queryToolbarSource).toContain('flex-wrap: wrap')
expect(source).toContain('@media (max-width: 720px)')
})
it('keeps upload actions in the table toolbar instead of the query toolbar', () => {
const queryToolbarEnd = source.indexOf('/>', source.indexOf('<QueryToolbar'))
const queryToolbarBlock = source.slice(source.indexOf('<QueryToolbar'), queryToolbarEnd + 2)
expect(queryToolbarBlock).not.toContain('#actions')
expect(source).toContain('<template #toolbar-actions><el-button v-if="canEdit" type="primary" @click="openUpload">')
expect(source).not.toContain('#toolbar-start')
expect(source).toContain('<template #pagination-left>共 {{ rows.length }} 个版本')
})
it('keeps list spacing and error state mutually exclusive', () => {
expect(source).toContain('<template v-if="loadError" #state>')
expect(source).toContain('<section v-if="!loadError" class="surface-panel table-panel"')
expect(source).not.toContain('<template #state>')
})
it('keeps both row actions directly visible without a premature more menu', () => {
expect(source).toContain('<Eye :size="15" />{{ detailActionLabel')
expect(source).toContain('<Download :size="15" />原文件')
expect(source).not.toContain('MoreHorizontal')
})
})
@@ -1,14 +1,17 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { AlertTriangle, Download, Eye, FileSpreadsheet, FileUp, Ship, Warehouse } from '@lucide/vue'
import { AlertTriangle, Download, Eye, FileSpreadsheet, FileUp, RefreshCw, Ship, Warehouse } from '@lucide/vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { listAllPlatformWarehouses, type PlatformWarehouseResource } from '@/modules/warehouses'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AdminPage from '@/shared/components/AdminPage.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import AppDrawer from '@/shared/components/AppDrawer.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import DetailPageLayout from '@/shared/components/DetailPageLayout.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 {
@@ -593,30 +596,31 @@ onMounted(() => {
</script>
<template>
<div class="business-page price-book-page">
<QueryToolbar
v-model="filters"
ariaLabel="船期价格表筛选"
:fields="queryFields"
preference-key="price-book-query"
@search="applyQuery"
>
<template #actions>
<el-button v-if="canEdit" type="primary" @click="openUpload"><FileUp :size="16" />上传价格表</el-button>
<AdminPage as="div" class="price-book-page" spacing="normal">
<ListPageLayout aria-label="承运商报价版本管理">
<template #toolbar>
<QueryToolbar
v-model="filters"
class="surface-panel"
ariaLabel="船期价格表筛选"
:fields="queryFields"
preference-key="price-book-query"
@search="applyQuery"
/>
</template>
</QueryToolbar>
<LoadErrorState
v-if="loadError"
title="船期价格表版本加载失败"
:message="loadErrorMessage"
:loading="loading"
@retry="loadPriceBooks()"
>
<template #details><ApiErrorMeta :details="loadError" /></template>
</LoadErrorState>
<template v-if="loadError" #state>
<LoadErrorState
title="船期价格表版本加载失败"
:message="loadErrorMessage"
:loading="loading"
@retry="loadPriceBooks()"
>
<template #details><ApiErrorMeta :details="loadError" /></template>
</LoadErrorState>
</template>
<section class="surface-panel table-panel" aria-live="polite">
<section v-if="!loadError" class="surface-panel table-panel" aria-live="polite">
<DataTable
v-model:page="page"
v-model:page-size="pageSize"
@@ -630,7 +634,7 @@ onMounted(() => {
aria-label="船期价格表版本列表"
empty-text="暂无船期价格表版本"
>
<template #toolbar-start><div><h2>价格表版本</h2><p>共 {{ rows.length }} 个版本 · {{ activeCount }} 个生效中 · {{ pendingCount }} 个待确认</p></div></template>
<template #toolbar-actions><el-button v-if="canEdit" type="primary" @click="openUpload"><FileUp :size="16" />上传价格表</el-button></template>
<template #actions="{ row }">
<div class="table-actions">
<el-button link type="primary" @click="openDetail(row as PlatformPriceBookVersion)"><Eye :size="15" />{{ detailActionLabel(row as PlatformPriceBookVersion) }}</el-button>
@@ -647,8 +651,10 @@ onMounted(() => {
<template #contents="{ row }"><div class="metric-cell"><strong>{{ (row as PlatformPriceBookVersion).channelCount }}</strong><span>渠道</span><strong>{{ (row as PlatformPriceBookVersion).rateCount }}</strong><span>价格行</span></div></template>
<template #issues="{ row }"><div class="issue-counts"><span :class="{ 'is-blocking': (row as PlatformPriceBookVersion).blockingErrorCount }">阻断 {{ (row as PlatformPriceBookVersion).blockingErrorCount }}</span><span>警告 {{ (row as PlatformPriceBookVersion).warningCount }}</span></div></template>
<template #empty><el-empty description="暂无船期价格表版本" /></template>
<template #pagination-left>共 {{ rows.length }} 个版本 · {{ activeCount }} 个生效中 · {{ pendingCount }} 个待确认</template>
</DataTable>
</section>
</section>
</ListPageLayout>
<AppDialog
v-model="uploadOpen"
@@ -690,16 +696,16 @@ onMounted(() => {
<strong>价格表详情暂时无法加载</strong>
<p>{{ detailError.message }}</p>
<ApiErrorMeta :details="detailError" />
<el-button type="primary" @click="selectedVersionId && openDetail({ id: selectedVersionId } as PlatformPriceBookVersion)">重新加载</el-button>
<el-button type="primary" @click="selectedVersionId && openDetail({ id: selectedVersionId } as PlatformPriceBookVersion)"><RefreshCw :size="16" />重新加载</el-button>
</section>
<div v-else-if="detail" class="detail-content">
<header class="detail-summary">
<DetailPageLayout v-else-if="detail" class="detail-content" aria-label="船期价格表版本详情">
<template #header><header class="detail-summary">
<div class="detail-summary__title"><FileSpreadsheet :size="22" /><div><h3>{{ detail.version.originalFileName }}</h3><p>版本 V{{ detail.version.versionNo }} · 上传于 {{ formatDateTime(detail.version.createdAt) }}</p></div></div>
<div class="detail-summary__actions"><el-tag :type="priceBookStatusMeta(detail.version.status).type" effect="light">{{ priceBookStatusMeta(detail.version.status).label }}</el-tag><el-button :loading="downloadingIds.has(detail.version.id)" @click="downloadSource(detail.version)"><Download :size="16" />下载原文件</el-button></div>
</header>
</header></template>
<section class="detail-section version-facts">
<template #summary><section class="detail-section version-facts">
<dl class="detail-grid">
<div><dt>解析渠道</dt><dd>{{ detail.version.channelCount }}</dd></div>
<div><dt>解析价格行</dt><dd>{{ detail.version.rateCount }}</dd></div>
@@ -707,7 +713,7 @@ onMounted(() => {
<div><dt>警告</dt><dd>{{ detail.version.warningCount }}</dd></div>
<div><dt>实际生效区间</dt><dd>{{ formatEffectivePeriod(detail.version) }}</dd></div>
</dl>
</section>
</section></template>
<section v-if="isPendingDetail && canEdit" class="detail-section mapping-section">
<header class="detail-section__heading"><div><h3><Warehouse :size="17" />仓库映射</h3><p>来源编码必须匹配启用的平台公共仓库编码。</p></div></header>
@@ -722,7 +728,7 @@ onMounted(() => {
<div v-if="mappingWarehousesError" class="mapping-load-error" role="alert">
<div><strong>平台仓库候选加载失败</strong><p>{{ mappingWarehousesError.message }}</p><ApiErrorMeta :details="mappingWarehousesError" /></div>
<el-button :loading="mappingWarehousesLoading" @click="loadMappingWarehouses(true)">重新加载</el-button>
<el-button :loading="mappingWarehousesLoading" @click="loadMappingWarehouses(true)"><RefreshCw :size="16" />重新加载</el-button>
</div>
<div v-else-if="mappingWarehousesLoaded" class="mapping-groups">
<section class="mapping-group">
@@ -789,9 +795,9 @@ onMounted(() => {
</el-table>
</template>
</section>
</div>
</DetailPageLayout>
</AppDrawer>
</div>
</AdminPage>
</template>
<style scoped>