2026-08-13 11:57:44 +08:00
|
|
|
<script setup lang="ts">
|
2026-09-15 09:58:15 +08:00
|
|
|
import '@/styles/adminDensity.css'
|
2026-08-14 14:27:03 +08:00
|
|
|
import { computed, ref, watch } from 'vue'
|
2026-08-13 11:57:44 +08:00
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
|
import { ArrowLeft, FileText, Pencil } from '@lucide/vue'
|
|
|
|
|
import AdminPage from '@/shared/components/AdminPage.vue'
|
|
|
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
|
|
|
|
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
|
|
|
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|
|
|
|
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
|
|
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
|
|
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
|
|
|
|
import SupplierLicenseDialog from '@/modules/suppliers/components/SupplierLicenseDialog.vue'
|
|
|
|
|
import { getSupplier } from '@/modules/suppliers/api/platformSuppliers'
|
2026-09-16 11:06:36 +08:00
|
|
|
import { formatTruckingScope, categoryLabels, formatCarrierCodes, settlementLabels, statusLabels, type Supplier } from '@/modules/suppliers/types'
|
2026-08-13 11:57:44 +08:00
|
|
|
|
|
|
|
|
const route = useRoute(); const router = useRouter(); const access = useAccessPreviewStore()
|
|
|
|
|
const canView = computed(() => access.can('platform:supplier:list'))
|
|
|
|
|
const supplierId = computed(() => typeof route.params.id === 'string' ? route.params.id : '')
|
|
|
|
|
const supplier = ref<Supplier | null>(null); const loading = ref(false); const error = ref<ApiErrorDetails | null>(null); const licenseOpen = ref(false)
|
2026-08-14 14:27:03 +08:00
|
|
|
let loadSequence = 0
|
2026-08-13 11:57:44 +08:00
|
|
|
const items = computed(() => supplier.value ? [
|
2026-08-27 10:12:20 +08:00
|
|
|
['供应商名称', supplier.value.name], ['供应商类别', categoryLabels[supplier.value.category]], ['可代理船公司', supplier.value.category === 'BOOKING' ? formatCarrierCodes(supplier.value.carrierCodes) : '—'], ['地区', supplier.value.region],
|
2026-09-16 11:06:36 +08:00
|
|
|
...(supplier.value.category === 'TRUCKING' ? [['拖车类型', formatTruckingScope(supplier.value.truckingScope)]] : []),
|
2026-08-13 11:57:44 +08:00
|
|
|
['法人代表', supplier.value.legalRepresentative], ['联系人', supplier.value.contactName], ['联系电话', supplier.value.phone], ['电子邮箱', supplier.value.email || '—'],
|
|
|
|
|
['营业执照号', supplier.value.businessLicenseNo || '—'], ['结算周期', settlementLabels[supplier.value.settlementPeriod]], ['公司地址', supplier.value.address || '—'], ['备注', supplier.value.remark || '—'],
|
|
|
|
|
] : [])
|
2026-08-14 14:27:03 +08:00
|
|
|
async function load() {
|
|
|
|
|
const requestedId = supplierId.value
|
|
|
|
|
const sequence = ++loadSequence
|
|
|
|
|
if (!canView.value || !requestedId) { supplier.value = null; loading.value = false; error.value = null; return }
|
|
|
|
|
supplier.value = null
|
|
|
|
|
loading.value = true
|
|
|
|
|
error.value = null
|
|
|
|
|
try {
|
|
|
|
|
const result = await getSupplier(requestedId)
|
|
|
|
|
if (sequence !== loadSequence || requestedId !== supplierId.value) return
|
|
|
|
|
supplier.value = result
|
|
|
|
|
} catch (cause) {
|
|
|
|
|
if (sequence !== loadSequence || requestedId !== supplierId.value) return
|
|
|
|
|
error.value = getApiErrorDetails(cause, '供应商详情加载失败')
|
|
|
|
|
} finally {
|
|
|
|
|
if (sequence === loadSequence) loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
watch(supplierId, () => { void load() }, { immediate: true })
|
2026-08-13 11:57:44 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-09-15 09:58:15 +08:00
|
|
|
<AdminPage class="admin-density" ><DetailPageLayout class="density-detail-layout surface-panel" aria-label="供应商详情">
|
|
|
|
|
<template #header><div class="density-detail-header"><div class="density-detail-heading"><el-button link class="density-back" @click="router.push({ name: 'supplier-list' })"><ArrowLeft :size="16" />返回列表</el-button><h1 class="density-detail-title">{{ supplier?.name || '供应商详情' }}</h1><StatusBadge v-if="supplier" :status="statusLabels[supplier.status]" /></div><el-button v-if="supplier && access.can('platform:supplier:edit')" type="primary" @click="router.push({ name: 'supplier-edit', params: { id: supplier.id } })"><Pencil :size="16" />编辑</el-button></div></template>
|
2026-08-13 11:57:44 +08:00
|
|
|
<LoadErrorState v-if="!canView" title="暂无供应商查看权限" message="请联系管理员开通供应商查看权限。" :show-retry="false" />
|
|
|
|
|
<LoadErrorState v-else-if="error" title="供应商详情加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
2026-09-15 09:58:15 +08:00
|
|
|
<section v-else v-loading="loading" class="detail-panel">
|
|
|
|
|
<dl v-if="supplier" class="detail-grid density-fields"><div v-for="item in items" :key="item[0]" :class="{ 'wide-field': ['可代理船公司', '公司地址', '备注'].includes(item[0]) }"><dt>{{ item[0] }}</dt><dd>{{ item[1] }}</dd></div></dl>
|
2026-08-13 11:57:44 +08:00
|
|
|
<div v-if="supplier" class="license-row"><div><strong>营业执照</strong><span>{{ supplier.licenseFileId ? '已关联资质文件' : '未上传资质文件' }}</span></div><el-button type="primary" plain @click="licenseOpen = true"><FileText :size="16" />查看营业执照</el-button></div>
|
|
|
|
|
</section>
|
|
|
|
|
<SupplierLicenseDialog v-model="licenseOpen" :supplier="supplier" />
|
|
|
|
|
</DetailPageLayout></AdminPage>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-09-15 09:58:15 +08:00
|
|
|
.detail-actions :deep(.el-button + .el-button) { margin-left: 0; }
|
|
|
|
|
.detail-panel { min-width: 0; }
|
|
|
|
|
.detail-grid .wide-field { grid-column: span 2; }.license-row { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-6); margin-top: var(--admin-space-7); padding: var(--admin-space-6); border: 1px dashed var(--cc-border); border-radius: var(--admin-radius); background: #fafafa; }.license-row strong,.license-row span { display: block; }.license-row span { margin-top: var(--admin-space-1); color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
2026-08-13 11:57:44 +08:00
|
|
|
@media (max-width: 720px) { .detail-header,.license-row { align-items: stretch; flex-direction: column; }.detail-actions { display: grid !important; grid-template-columns: 1fr 1fr; }.detail-actions :deep(.el-button) { min-height: 44px; }.detail-grid { grid-template-columns: 1fr; }.detail-grid div,.detail-grid div:nth-child(3n) { border-right: 0; }.detail-panel { padding: 16px; } }
|
|
|
|
|
</style>
|