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
@@ -8,7 +8,7 @@ import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { downloadOceanBookingDocument, invalidateOceanBookingDocument, uploadOceanBookingDocument } from '../api/platformOceanBookings'
import type { OceanBookingDocument, OceanBookingDocumentType, OceanBookingStatus } from '../types'
const props = defineProps<{ bookingId: string; bookingVersion: number; bookingStatus: OceanBookingStatus; documents: OceanBookingDocument[]; canManage: boolean; manageReason?: string }>()
const props = defineProps<{ bookingId: string; bookingNo?: string | null; bookingVersion: number; bookingStatus: OceanBookingStatus; documents: OceanBookingDocument[]; canManage: boolean; manageReason?: string }>()
const emit = defineEmits<{ updated: [documents: OceanBookingDocument[], bookingVersion: number] }>()
const busyType = ref<OceanBookingDocumentType | ''>('')
const busyDocumentId = ref('')
@@ -22,6 +22,7 @@ const editable = computed(() => props.bookingStatus === 'DRAFT' && props.canMana
const validDocuments = computed(() => props.documents.filter((item) => item.valid))
const invalidDocuments = computed(() => props.documents.filter((item) => !item.valid))
function documentDialogTitle(action: string) { const code = props.bookingNo?.trim(); return code ? `${action} · ${code}` : action }
function documentsOfType(type: OceanBookingDocumentType) { return validDocuments.value.filter((item) => item.documentType === type) }
function fileSize(value: number | null) {
if (value == null) return '大小未知'
@@ -60,7 +61,7 @@ async function remove(item: OceanBookingDocument) {
busyDocumentId.value = item.id
error.value = null
try {
await ElMessageBox.confirm(`确认作废“${item.fileName}”?文件历史仍会保留。`, '作废订舱资料', { customClass: 'admin-density-confirm', confirmButtonText: '确认作废', cancelButtonText: '取消', type: 'warning' })
await ElMessageBox.confirm(`确认作废“${item.fileName}”?文件历史仍会保留。`, documentDialogTitle('作废订舱资料'), { customClass: 'admin-density-confirm', confirmButtonText: '确认作废', cancelButtonText: '取消', type: 'warning' })
await invalidate(item)
ElMessage.success('订舱资料已作废')
} catch (cause) {
@@ -84,7 +85,7 @@ async function selectFile(event: Event, type: OceanBookingDocumentType) {
error.value = null
try {
if (previous.length) {
await ElMessageBox.confirm(`将上传“${file.name}”并作废当前 ${previous.length} 份同类型文件,是否继续?`, '替换订舱资料', { customClass: 'admin-density-confirm', confirmButtonText: '确认替换', cancelButtonText: '取消', type: 'warning' })
await ElMessageBox.confirm(`将上传“${file.name}”并作废当前 ${previous.length} 份同类型文件,是否继续?`, documentDialogTitle('替换订舱资料'), { customClass: 'admin-density-confirm', confirmButtonText: '确认替换', cancelButtonText: '取消', type: 'warning' })
}
const uploaded = await uploadOceanBookingDocument(props.bookingId, props.bookingVersion, type, file)
let documents = [...props.documents, uploaded.document]
@@ -115,10 +116,10 @@ async function selectFile(event: Event, type: OceanBookingDocumentType) {
<article v-for="type in types" :key="type.value" class="booking-document-card">
<div class="booking-document-card__heading"><div><h3>{{ type.label }}</h3><p>{{ type.description }}</p><small class="booking-document-limit">任意格式,单文件不超过 50MB</small></div><label v-if="editable" class="booking-document-upload" :class="{ 'is-disabled': Boolean(busyType || busyDocumentId) }"><FileUp :size="15" /><span>{{ busyType === type.value ? '上传处理中' : (documentsOfType(type.value).length ? '替换文件' : '上传文件') }}</span><input type="file" :disabled="Boolean(busyType || busyDocumentId)" @change="selectFile($event, type.value)" /></label></div>
<el-empty v-if="!documentsOfType(type.value).length" description="暂无有效文件" :image-size="48" />
<ul v-else class="booking-document-list"><li v-for="item in documentsOfType(type.value)" :key="item.id"><div><strong :title="item.fileName">{{ item.fileName }}</strong><small>{{ fileSize(item.sizeBytes) }} · {{ item.uploadedByName || item.uploadedBy }} · {{ item.uploadedAt }}</small></div><div class="booking-document-actions density-actions"><el-button class="density-action" size="small" plain :loading="busyDocumentId === item.id" @click="download(item)">下载</el-button><el-button data-tone="danger" class="density-action" v-if="editable" size="small" type="danger" plain :loading="busyDocumentId === item.id" @click="remove(item)">作废</el-button></div></li></ul>
<ul v-else class="booking-document-list"><li v-for="item in documentsOfType(type.value)" :key="item.id"><div><strong :title="item.fileName">{{ item.fileName }}</strong><small>{{ fileSize(item.sizeBytes) }} · {{ item.uploadedByName || '—' }} · {{ item.uploadedAt }}</small></div><div class="booking-document-actions density-actions"><el-button class="density-action" size="small" plain :loading="busyDocumentId === item.id" @click="download(item)">下载</el-button><el-button data-tone="danger" class="density-action" v-if="editable" size="small" type="danger" plain :loading="busyDocumentId === item.id" @click="remove(item)">作废</el-button></div></li></ul>
</article>
</div>
<details v-if="invalidDocuments.length" class="booking-document-history"><summary>查看已作废资料({{ invalidDocuments.length }})</summary><ul><li v-for="item in invalidDocuments" :key="item.id"><span>{{ item.fileName }}</span><small>{{ item.invalidatedByName || item.invalidatedBy || '系统' }} · {{ item.invalidatedAt || '—' }}</small></li></ul></details>
<details v-if="invalidDocuments.length" class="booking-document-history"><summary>查看已作废资料({{ invalidDocuments.length }})</summary><ul><li v-for="item in invalidDocuments" :key="item.id"><span>{{ item.fileName }}</span><small>{{ item.invalidatedByName || '—' }} · {{ item.invalidatedAt || '—' }}</small></li></ul></details>
</section>
</template>