This commit is contained in:
zlwu committed 2026-06-29 11:15:39 +08:00
1 parent 493f8d6756
commit d84f4b944c
406 files changed
+58954 -10376

No files matched your search

@@ -116,7 +116,14 @@
<DictTag dict-type="crm_contract_status" :value="row.status" />
</template>
</el-table-column>
<el-table-column prop="amount" label="合同金额" min-width="120" align="right" header-align="center">
<el-table-column
v-if="!hideAmount"
prop="amount"
label="合同金额"
min-width="120"
align="right"
header-align="center"
>
<template #default="{ row }">
<span class="money-display">{{ formatMoneyDisplay(row.amount) }}</span>
</template>
@@ -168,9 +175,15 @@ import DictTag from '@/components/DictTag/index.vue'
import ListPagination from '@/components/ListPagination/index.vue'
import { formatMoneyDisplay } from '@/utils/money-format'
const props = defineProps<{
visible: boolean
}>()
const props = withDefaults(
defineProps<{
visible: boolean
hideAmount?: boolean
}>(),
{
hideAmount: false
}
)
const emit = defineEmits<{
'update:visible': [value: boolean]
@@ -16,6 +16,34 @@
<section class="carrier-view-top">
<h3 class="carrier-view-block-title">承运商信息</h3>
<ViewKvTable v-if="carrier" :fields="carrierViewFields" mode="triple" />
<h3 class="carrier-view-block-title carrier-view-attachment-title">资质附件</h3>
<div v-if="carrier" class="carrier-view-attachments form-grid-2">
<div class="carrier-view-attachment-item">
<div class="carrier-view-attachment-label">营业执照</div>
<AttachmentUploadField
:model-value="carrier.businessLicenseUrls || ''"
disabled
folder="pl/carrier/license"
/>
</div>
<div class="carrier-view-attachment-item">
<div class="carrier-view-attachment-label">道路经营许可证</div>
<AttachmentUploadField
:model-value="carrier.roadTransportLicenseUrls || ''"
disabled
folder="pl/carrier/permit"
/>
</div>
<div class="carrier-view-attachment-item form-col-span-2">
<div class="carrier-view-attachment-label">运输协议</div>
<AttachmentUploadField
:model-value="carrier.transportAgreementUrls || ''"
disabled
folder="pl/carrier/agreement"
/>
</div>
</div>
</section>
<section class="carrier-view-bottom">
@@ -50,6 +78,7 @@
<script setup lang="ts">
import { Close } from '@element-plus/icons-vue'
import { carrierApi, vehicleApi, type Carrier } from '@/api/pl'
import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
import PlVehicleViewPanel from '@/components/business/pl-vehicle-view-panel/index.vue'
import ViewKvTable from '@/components/business/system-view-dialog/ViewKvTable.vue'
import type { ViewField } from '@/components/business/system-view-dialog/types'
@@ -58,7 +87,12 @@ import {
type VehicleViewBundle
} from '@/composables/usePlVehicleViewDetail'
import { formatDateTime } from '@/utils/format'
import { getPlCarrierStatusLabel, getPlCarrierStatusTagType } from '@/utils/pl-display'
import {
getPlCarrierAuditStatusLabel,
getPlCarrierAuditStatusTagType,
getPlCarrierStatusLabel,
getPlCarrierStatusTagType
} from '@/utils/pl-display'
const props = defineProps<{
visible: boolean
@@ -92,7 +126,29 @@ const carrierViewFields = computed<ViewField[]>(() => {
value: getPlCarrierStatusLabel(c.status),
tagType: getPlCarrierStatusTagType(c.status)
},
{
label: '审核状态',
value: getPlCarrierAuditStatusLabel(c.auditStatus),
tagType: getPlCarrierAuditStatusTagType(c.auditStatus)
},
{
label: '审核时间',
value: c.auditTime ? formatDateTime(c.auditTime) : '—'
},
{
label: '协议有效期',
value: c.transportAgreementExpired
? '协议已过期'
: c.transportAgreementExpireDate || '—',
tagType: c.transportAgreementExpired ? 'danger' : undefined
},
{
label: '资质状态',
value: c.qualificationComplete ? '已齐全' : '资质不全',
tagType: c.qualificationComplete ? 'success' : 'warning'
},
{ label: '创建时间', value: formatDateTime(c.createTime) },
{ label: '审核备注', value: c.auditRemark || '—', span: 'full' },
{ label: '备注', value: c.remark || '—', span: 'full' }
]
})
@@ -155,6 +211,22 @@ function handleClosed() {
border-bottom: 1px solid #e2e8f0;
}
.carrier-view-attachment-title {
margin-top: 20px;
}
.carrier-view-attachments {
display: grid;
gap: 16px;
}
.carrier-view-attachment-label {
margin-bottom: 8px;
font-size: 11px;
font-weight: 500;
color: #1e293b;
}
.carrier-view-empty {
margin: 0;
font-size: 13px;
@@ -78,9 +78,6 @@
@change="freightCalc.markFreightAmountTouched()"
/>
</el-form-item>
<el-form-item label="是否开票">
<el-switch v-model="formData.hasInvoice" :active-value="1" :inactive-value="0" />
</el-form-item>
</template>
</div>
</section>
@@ -146,8 +143,7 @@ const productRows = ref<PlanFormProductRow[]>([])
const formData = reactive({
hasFreight: 0,
distanceKm: null as number | null,
freightType: 'by_weight',
hasInvoice: 0
freightType: 'by_weight'
})
const hasFreightRef = computed(() => formData.hasFreight)
@@ -205,8 +201,7 @@ function resetState() {
Object.assign(formData, {
hasFreight: 0,
distanceKm: null,
freightType: 'by_weight',
hasInvoice: 0
freightType: 'by_weight'
})
freightCalc.resetFreightCalc()
}
@@ -227,7 +222,6 @@ async function applyFreightPrefill(data: Plan) {
const transport = pickBxData<Transport>(transportRes)
formData.distanceKm = transport.distanceKm != null ? Number(transport.distanceKm) : null
formData.freightType = transport.freightType || 'by_weight'
formData.hasInvoice = transport.hasInvoice ?? 0
if (transport.transportQty != null) {
freightCalc.transportQty.value = Number(transport.transportQty)
@@ -311,8 +305,7 @@ async function handleSubmit() {
transportQty:
formData.freightType === 'by_weight'
? freightCalc.transportQty.value ?? undefined
: undefined,
hasInvoice: formData.hasInvoice
: undefined
}
: {})
}
@@ -65,6 +65,14 @@
<section v-else class="pl-plan-form-section">
<h3 class="pl-plan-form-section-title">{{ isRearrange ? '调整运输' : '运输安排' }}</h3>
<el-alert
v-if="rearrangeBlocked"
type="warning"
:closable="false"
show-icon
:title="rearrangeBlockedMessage"
class="pl-arrange-block-alert"
/>
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
<h4 class="pl-plan-subtitle">车辆信息</h4>
<div class="form-grid-4">
@@ -100,9 +108,7 @@
</el-select>
</el-form-item>
<el-form-item label="运输公司">
<el-input v-if="isSelfVehicle" :model-value="SELF_CARRIER_NAME" readonly />
<el-select
v-else
v-model="formData.carrierId"
placeholder="请选择运输公司"
filterable
@@ -230,9 +236,6 @@
@change="freightCalc.markFreightAmountTouched()"
/>
</el-form-item>
<el-form-item label="是否开票">
<el-switch v-model="formData.hasInvoice" :active-value="1" :inactive-value="0" />
</el-form-item>
</div>
<h4 class="pl-plan-subtitle">安排项</h4>
@@ -346,7 +349,7 @@
<el-button @click="innerVisible = false">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
<el-button type="primary" :loading="submitLoading" :disabled="rearrangeBlocked" @click="handleSubmit">
<el-icon><Check /></el-icon>{{ isRearrange ? '确认调整' : '确认安排' }}
</el-button>
</template>
@@ -384,8 +387,6 @@ import { isTonProductUnit } from '@/utils/crm-product-display'
import { formatDateTime } from '@/utils/format'
import { pickBxData, pickBxList } from '@/utils/http/bx'
const SELF_CARRIER_NAME = '安庆市山峡化工有限公司'
const dictStore = useDictStore()
const props = defineProps<{
@@ -440,6 +441,8 @@ const vehicleBundle = ref<VehicleViewBundle | null>(null)
const vehicleViewVisible = ref(false)
const hasHistoricalDibang = ref(false)
const hasHistoricalSiteLinks = ref(false)
const rearrangeBlocked = ref(false)
const rearrangeBlockedMessage = ref('')
const formData = reactive({
vehicleId: '',
@@ -454,7 +457,6 @@ const formData = reactive({
hasFreight: 0,
distanceKm: null as number | null,
freightType: 'by_weight',
hasInvoice: 0,
remark: '',
generateDibangPlan: false
})
@@ -523,10 +525,8 @@ const freightCalc = usePlFreightCalc({
})
const selectedVehicle = computed(() => vehicleList.value.find(item => item.id === formData.vehicleId))
const isSelfVehicle = computed(() => selectedVehicle.value?.vehicleType === 'self')
const summaryCarrierName = computed(() => {
if (isSelfVehicle.value) return SELF_CARRIER_NAME
const carrier = carrierList.value.find(item => item.id === formData.carrierId)
return carrier?.carrierName || vehicleBundle.value?.detail?.carrierName || ''
})
@@ -565,12 +565,6 @@ function formatDriverLabel(d: VehiclePersonnelRow) {
return `${d.personnelName}${phone}`
}
function resolveSelfCarrierId() {
if (!Array.isArray(carrierList.value)) return
const matched = carrierList.value.find(item => item.carrierName === SELF_CARRIER_NAME)
formData.carrierId = matched?.id || formData.carrierId || ''
}
function applyVehiclePersonnel(personnelList: VehiclePersonnelRow[], preferredDriverId?: string) {
vehicleDriverList.value = personnelList.filter(p => p.role === 'driver' && p.personnelId)
if (preferredDriverId && vehicleDriverList.value.some(d => d.personnelId === preferredDriverId)) {
@@ -632,7 +626,6 @@ async function applyPrefillFromPlan(data: Plan) {
formData.remark = transport.remark || formData.remark
formData.distanceKm = transport.distanceKm != null ? Number(transport.distanceKm) : null
formData.freightType = transport.freightType || 'by_weight'
formData.hasInvoice = transport.hasInvoice ?? 0
prefilledDriverId.value = transport.driverId || prefilledDriverId.value
if (transport.transportQty != null) {
@@ -662,6 +655,9 @@ function resetState() {
vehicleBundle.value = null
vehicleViewVisible.value = false
hasHistoricalDibang.value = false
hasHistoricalSiteLinks.value = false
rearrangeBlocked.value = false
rearrangeBlockedMessage.value = ''
Object.assign(formData, {
vehicleId: '',
driverId: '',
@@ -675,7 +671,6 @@ function resetState() {
hasFreight: 0,
distanceKm: null,
freightType: 'by_weight',
hasInvoice: 0,
remark: '',
generateDibangPlan: false
})
@@ -685,13 +680,47 @@ function resetState() {
async function loadMasterData() {
const [vehicleRes, carrierRes] = await Promise.all([
vehicleApi.list({ vehicleStatus: 'available' }),
vehicleApi.list({ vehicleStatus: 'available', excludeIneligibleCarrier: true }),
carrierApi.list({ status: '1' })
])
vehicleList.value = pickBxList<Vehicle>(vehicleRes)
carrierList.value = pickBxList<Carrier>(carrierRes)
}
async function updateRearrangeBlocked() {
rearrangeBlocked.value = false
rearrangeBlockedMessage.value = ''
if (!isRearrange.value) return
const detail = vehicleBundle.value?.detail
const carrierId = detail?.carrierId || formData.carrierId || selectedVehicle.value?.carrierId
if (!carrierId) return
let carrier = carrierList.value.find(c => c.id === carrierId)
if (!carrier) {
try {
const res = await carrierApi.get(carrierId)
carrier = pickBxData<Carrier>(res)
} catch {
return
}
}
if (!carrier?.qualificationComplete) {
rearrangeBlocked.value = true
rearrangeBlockedMessage.value = '该承运商资质附件不完整,请先补全后再调整运输'
return
}
if (carrier.auditStatus !== 'approved') {
rearrangeBlocked.value = true
rearrangeBlockedMessage.value = '该承运商尚未审核通过,请先完成审核后再调整运输'
return
}
if (carrier.transportAgreementExpired) {
rearrangeBlocked.value = true
rearrangeBlockedMessage.value = '该承运商运输协议已过期,请先更新协议后再调整运输'
}
}
async function handleVehicleChange(vehicleId: string, preferredDriverId?: string) {
if (!vehicleId) {
formData.driverId = ''
@@ -704,19 +733,20 @@ async function handleVehicleChange(vehicleId: string, preferredDriverId?: string
const vehicle = Array.isArray(vehicleList.value)
? vehicleList.value.find(v => v.id === vehicleId)
: undefined
if (vehicle?.vehicleType === 'self') {
resolveSelfCarrierId()
} else if (vehicle?.carrierId) {
if (vehicle?.carrierId) {
formData.carrierId = vehicle.carrierId
} else {
formData.carrierId = ''
}
vehicleLoading.value = true
try {
vehicleBundle.value = await loadVehicleViewBundle(vehicleId)
if (vehicleBundle.value.detail?.vehicleType === 'self') {
resolveSelfCarrierId()
if (vehicleBundle.value.detail?.carrierId) {
formData.carrierId = vehicleBundle.value.detail.carrierId
}
applyVehiclePersonnel(vehicleBundle.value.personnelList || [], preferredDriverId)
await updateRearrangeBlocked()
} finally {
vehicleLoading.value = false
}
@@ -753,6 +783,10 @@ function handleClosed() {
}
async function handleSubmit() {
if (rearrangeBlocked.value) {
ElMessage.warning(rearrangeBlockedMessage.value || '当前承运商不可调整运输')
return
}
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
@@ -815,7 +849,6 @@ async function handleSubmit() {
formData.hasFreight === 1
? freightCalc.calculatedFreight.value || freightCalc.freightAmount.value || undefined
: undefined,
hasInvoice: formData.hasInvoice,
remark: formData.remark || undefined,
generateDibangPlan: formData.generateDibangPlan
}
@@ -858,6 +891,10 @@ async function handleSubmit() {
padding-right: 4px;
}
.pl-arrange-block-alert {
margin-bottom: 12px;
}
.pl-plan-subtitle {
margin: 16px 0 10px;
font-size: 13px;
@@ -50,15 +50,6 @@
<span v-else>{{ row.actualQty ?? '—' }}</span>
</template>
</el-table-column>
<el-table-column label="含税单价(元)" min-width="120" align="right">
<template #default="{ row }">{{ formatMoneyDisplay(row.taxPrice) }}</template>
</el-table-column>
<el-table-column label="含税总价(元)" min-width="120" align="right">
<template #default="{ row }">{{ formatMoneyDisplay(row.taxSubtotal) }}</template>
</el-table-column>
<el-table-column label="税费(元)" min-width="100" align="right">
<template #default="{ row }">{{ formatMoneyDisplay(row.taxAmount) }}</template>
</el-table-column>
<el-table-column prop="packageSpec" label="包装规格" min-width="110" show-overflow-tooltip />
<el-table-column label="送货方式" min-width="100" align="center">
<template #default="{ row }">
@@ -89,7 +80,6 @@ import type { PlanFormProductRow } from '@/composables/usePlContractToPlan'
import { getDeliveryMethodLabel } from '@/composables/usePlContractToPlan'
import PlProductNameHazardCell from '@/components/business/pl-product-name-hazard-cell/index.vue'
import DictTag from '@/components/DictTag/index.vue'
import { formatMoneyDisplay } from '@/utils/money-format'
defineProps<{
products: PlanFormProductRow[]
@@ -100,9 +100,7 @@
</el-select>
</el-form-item>
<el-form-item label="运输公司">
<el-input v-if="isSelfVehicle" :model-value="SELF_CARRIER_NAME" readonly />
<el-select
v-else
v-model="formData.carrierId"
placeholder="请选择运输公司"
filterable
@@ -222,9 +220,6 @@
@change="freightCalc.markFreightAmountTouched()"
/>
</el-form-item>
<el-form-item label="是否开票">
<el-switch v-model="formData.hasInvoice" :active-value="1" :inactive-value="0" />
</el-form-item>
</template>
</div>
</el-form>
@@ -258,8 +253,8 @@
</div>
</template>
<ContractSelector v-model:visible="contractSelectorVisible" @confirm="handleContractConfirm" />
<ContractDetailDialog v-if="contractDetailDialogActive" ref="contractDetailRef" />
<ContractSelector v-model:visible="contractSelectorVisible" hide-amount @confirm="handleContractConfirm" />
<ContractDetailDialog v-if="contractDetailDialogActive" ref="contractDetailRef" hide-product-price />
<PlVehicleViewDialog
v-model:visible="vehicleViewVisible"
:vehicle-id="formData.vehicleId"
@@ -296,8 +291,6 @@ const ContractDetailDialog = defineAsyncComponent(
() => import('@/views/crm/contract/components/ContractDetailDialog.vue')
)
const SELF_CARRIER_NAME = '安庆市山峡化工有限公司'
const props = defineProps<{
visible: boolean
planId?: string
@@ -388,7 +381,6 @@ const formData = reactive({
carrierId: '',
distanceKm: null as number | null,
freightType: 'by_weight',
hasInvoice: 0,
remark: '',
products: [] as PlanFormProductRow[]
})
@@ -414,13 +406,8 @@ const freightUnitLabel = computed(() => {
})
const selectedVehicle = computed(() => vehicleList.value.find(item => item.id === formData.vehicleId))
const isSelfVehicle = computed(
() =>
selectedVehicle.value?.vehicleType === 'self' || vehicleBundle.value?.detail?.vehicleType === 'self'
)
const summaryCarrierName = computed(() => {
if (isSelfVehicle.value) return SELF_CARRIER_NAME
const carrier = carrierList.value.find(item => item.id === formData.carrierId)
if (carrier?.carrierName) return carrier.carrierName
return vehicleBundle.value?.detail?.carrierName || ''
@@ -445,12 +432,6 @@ function buildContractBasicFields(contract: ContractVO): ViewField[] {
value: settlementMethod ? dictStore.getLabel('crm_settlement_method', settlementMethod) : '—',
tagType: settlementMethod ? dictStore.getTagType('crm_settlement_method', settlementMethod) : undefined,
long: false
},
{
label: '合同金额(元)',
value: formatMoneyDisplay(contract.amount),
money: true,
long: false
}
]
}
@@ -470,11 +451,6 @@ const contractProductRows = computed<PlanFormProductRow[]>(() =>
mapContractProducts(contractSnapshot.value?.products)
)
function resolveSelfCarrierId() {
const matched = carrierList.value.find(item => item.carrierName === SELF_CARRIER_NAME)
formData.carrierId = matched?.id || ''
}
function enrichProductsFromContract(products: PlanFormProductRow[], contract?: ContractVO | null) {
if (!contract?.products?.length) return products
const contractMap = new Map(
@@ -556,8 +532,7 @@ function buildTransportBlock(options?: { allowClear?: boolean }) {
if (!options?.allowClear) return undefined
return {
vehicleId: '',
freightType: formData.freightType,
hasInvoice: formData.hasInvoice
freightType: formData.freightType
}
}
return {
@@ -570,8 +545,7 @@ function buildTransportBlock(options?: { allowClear?: boolean }) {
formData.freightType === 'by_weight' ? freightCalc.transportQty.value ?? undefined : undefined,
freightPrice:
formData.freightType === 'by_weight' ? freightCalc.freightPrice.value ?? undefined : undefined,
freightAmount: freightCalc.freightAmount.value ?? undefined,
hasInvoice: formData.hasInvoice
freightAmount: freightCalc.freightAmount.value ?? undefined
}
}
@@ -622,7 +596,6 @@ async function restoreFreightFromTransport(plan: Plan) {
const transport = (transportRes.data ?? transportRes) as {
distanceKm?: number
freightType?: string
hasInvoice?: number
freightPrice?: number
transportQty?: number
freightAmount?: number
@@ -631,7 +604,6 @@ async function restoreFreightFromTransport(plan: Plan) {
}
formData.distanceKm = transport.distanceKm != null ? Number(transport.distanceKm) : null
formData.freightType = transport.freightType || 'by_weight'
formData.hasInvoice = transport.hasInvoice ?? 0
if (transport.carrierId) {
formData.carrierId = transport.carrierId
}
@@ -689,7 +661,6 @@ function resetForm() {
carrierId: '',
distanceKm: null,
freightType: 'by_weight',
hasInvoice: 0,
remark: '',
products: []
})
@@ -821,7 +792,7 @@ function handleClosed() {
async function loadMasterData() {
try {
const [vehicleRes, carrierRes] = await Promise.all([
vehicleApi.list({ vehicleStatus: 'available' }),
vehicleApi.list({ vehicleStatus: 'available', excludeIneligibleCarrier: true }),
carrierApi.list({ status: '1' })
])
vehicleList.value = (vehicleRes.data ?? vehicleRes) || []
@@ -877,17 +848,17 @@ async function handleVehicleChange(vehicleId: string) {
}
const vehicle = vehicleList.value.find(item => item.id === vehicleId)
if (vehicle?.vehicleType === 'self') {
resolveSelfCarrierId()
} else if (vehicle?.carrierId) {
if (vehicle?.carrierId) {
formData.carrierId = vehicle.carrierId
} else {
formData.carrierId = ''
}
vehicleLoading.value = true
try {
vehicleBundle.value = await loadVehicleViewBundle(vehicleId)
if (vehicleBundle.value.detail?.vehicleType === 'self') {
resolveSelfCarrierId()
if (vehicleBundle.value.detail?.carrierId) {
formData.carrierId = vehicleBundle.value.detail.carrierId
}
resolveDefaultDriver(vehicleBundle.value.personnelList || [])
} finally {
@@ -341,7 +341,7 @@
</el-tab-pane>
</el-tabs>
<ContractDetailDialog v-if="contractDetailDialogActive" ref="contractDetailRef" />
<ContractDetailDialog v-if="contractDetailDialogActive" ref="contractDetailRef" hide-product-price />
<PlVehicleViewDialog
v-model:visible="vehicleViewVisible"
:vehicle-id="plan?.vehicleId"
@@ -376,6 +376,7 @@
import {
formatPlanProductListText,
getPlFreightTypeLabel,
getPlHasInvoiceLabelFromSettlement,
getPlPlanCompanyLabel,
getPlPlanTypeLabel,
getPlYesNoLabel,
@@ -388,8 +389,6 @@
() => import('@/views/crm/contract/components/ContractDetailDialog.vue')
)
const SELF_CARRIER_NAME = '安庆市山峡化工有限公司'
const dictStore = useDictStore()
const props = withDefaults(
@@ -409,6 +408,7 @@
plan,
products,
transport,
freightSettlement,
vehicleBundle,
contractSnapshot,
dibangRows,
@@ -516,7 +516,6 @@
if (transport.value?.carrierName) return transport.value.carrierName
const vehicle = vehicleBundle.value?.detail
if (vehicle?.carrierName) return vehicle.carrierName
if (vehicle?.vehicleType === 'self') return SELF_CARRIER_NAME
return ''
})
@@ -608,7 +607,7 @@
}
fields.push(
{ label: '运费金额(元)', value: displayFreightAmount.value, money: true },
{ label: '是否开票', value: getPlYesNoLabel(transport.value?.hasInvoice) }
{ label: '是否开票', value: getPlHasInvoiceLabelFromSettlement(freightSettlement.value) }
)
return fields
})
@@ -637,8 +636,7 @@
value: c.startDate && c.endDate ? `${c.startDate} ~ ${c.endDate}` : '—',
long: false
},
{ label: '结算方式', value: settlementLabel },
{ label: '合同金额(元)', value: formatMoneyDisplay(c.amount), money: true }
{ label: '结算方式', value: settlementLabel }
]
})
@@ -0,0 +1,144 @@
<template>
<div class="vehicle-cert-part-tabs">
<el-tabs v-if="hasTrailer" v-model="activePart" class="vehicle-cert-part-tabs__tabs">
<el-tab-pane :label="headTabLabel" name="head">
<PlCertFormList
ref="headCertFormRef"
v-model="headCertList"
:title="headTabLabel"
:upload-folder="uploadFolder"
:cert-name-options="certNameOptions"
/>
</el-tab-pane>
<el-tab-pane :label="trailerTabLabel" name="trailer">
<PlCertFormList
ref="trailerCertFormRef"
v-model="trailerCertList"
:title="trailerTabLabel"
:upload-folder="uploadFolder"
:cert-name-options="certNameOptions"
/>
</el-tab-pane>
</el-tabs>
<PlCertFormList
v-else
ref="headCertFormRef"
v-model="headCertList"
:title="title"
:upload-folder="uploadFolder"
:cert-name-options="certNameOptions"
/>
</div>
</template>
<script setup lang="ts">
import PlCertFormList from '@/components/business/pl-cert-form-list/index.vue'
import type { CertRow } from '@/api/pl'
import {
PL_VEHICLE_CERT_PART_HEAD,
PL_VEHICLE_CERT_PART_TRAILER,
mergeVehicleCertsByPart,
splitVehicleCertsByPart
} from '@/utils/pl-vehicle-cert'
type CertRowWithKey = CertRow & { _key?: string }
type CertFormListRef = InstanceType<typeof PlCertFormList>
const props = withDefaults(
defineProps<{
licensePlate?: string
trailerNo?: string
uploadFolder: string
certNameOptions?: string[]
title?: string
}>(),
{
licensePlate: '',
trailerNo: '',
certNameOptions: () => [],
title: '车辆证书'
}
)
const certList = defineModel<CertRowWithKey[]>('modelValue', { default: () => [] })
const activePart = ref(PL_VEHICLE_CERT_PART_HEAD)
const headCertFormRef = ref<CertFormListRef>()
const trailerCertFormRef = ref<CertFormListRef>()
const hasTrailer = computed(() => Boolean(props.trailerNo?.trim()))
const headTabLabel = computed(() => props.licensePlate?.trim() || '主车')
const trailerTabLabel = computed(() => props.trailerNo?.trim() || '挂车')
const headCertList = computed<CertRowWithKey[]>({
get() {
return splitVehicleCertsByPart(certList.value).headCerts
},
set(value) {
syncCertList(value, trailerCertList.value)
}
})
const trailerCertList = computed<CertRowWithKey[]>({
get() {
return splitVehicleCertsByPart(certList.value).trailerCerts
},
set(value) {
syncCertList(headCertList.value, value)
}
})
function syncCertList(headCerts: CertRowWithKey[], trailerCerts: CertRowWithKey[]) {
certList.value = mergeVehicleCertsByPart(headCerts, trailerCerts, hasTrailer.value)
}
watch(hasTrailer, value => {
if (!value) {
activePart.value = PL_VEHICLE_CERT_PART_HEAD
certList.value = mergeVehicleCertsByPart(
splitVehicleCertsByPart(certList.value).headCerts,
[],
false
)
trailerCertFormRef.value?.clearUploadRefs()
return
}
if (activePart.value !== PL_VEHICLE_CERT_PART_TRAILER) {
activePart.value = PL_VEHICLE_CERT_PART_HEAD
}
})
function hasUploading() {
return Boolean(
headCertFormRef.value?.hasUploading?.() || trailerCertFormRef.value?.hasUploading?.()
)
}
function clearUploadRefs() {
headCertFormRef.value?.clearUploadRefs()
trailerCertFormRef.value?.clearUploadRefs()
}
defineExpose({
hasUploading,
clearUploadRefs
})
</script>
<style scoped lang="scss">
.vehicle-cert-part-tabs {
grid-column: 1 / -1;
}
.vehicle-cert-part-tabs__tabs {
:deep(.el-tabs__header) {
margin-bottom: 0;
}
:deep(.cert-form-section) {
margin-top: 12px;
}
}
</style>
@@ -0,0 +1,53 @@
<template>
<div class="vehicle-cert-part-view">
<el-tabs v-if="hasTrailer" v-model="activePart" class="vehicle-cert-part-view__tabs">
<el-tab-pane :label="headTabLabel" name="head">
<PlCertViewList :cert-list="headCertList" />
</el-tab-pane>
<el-tab-pane :label="trailerTabLabel" name="trailer">
<PlCertViewList :cert-list="trailerCertList" />
</el-tab-pane>
</el-tabs>
<PlCertViewList v-else :cert-list="headCertList" />
</div>
</template>
<script setup lang="ts">
import PlCertViewList from '@/components/business/pl-cert-view-list/index.vue'
import type { CertRead } from '@/api/pl'
import {
PL_VEHICLE_CERT_PART_HEAD,
splitVehicleCertsByPart
} from '@/utils/pl-vehicle-cert'
const props = withDefaults(
defineProps<{
certList?: CertRead[]
licensePlate?: string
trailerNo?: string
}>(),
{
certList: () => [],
licensePlate: '',
trailerNo: ''
}
)
const activePart = ref(PL_VEHICLE_CERT_PART_HEAD)
const hasTrailer = computed(() => Boolean(props.trailerNo?.trim()))
const headTabLabel = computed(() => props.licensePlate?.trim() || '主车')
const trailerTabLabel = computed(() => props.trailerNo?.trim() || '挂车')
const headCertList = computed(() => splitVehicleCertsByPart(props.certList).headCerts)
const trailerCertList = computed(() => splitVehicleCertsByPart(props.certList).trailerCerts)
</script>
<style scoped lang="scss">
.vehicle-cert-part-view__tabs {
:deep(.el-tabs__header) {
margin-bottom: 12px;
}
}
</style>
@@ -1,7 +1,11 @@
<template>
<el-tabs v-model="activeTab" class="vehicle-detail-tabs">
<el-tab-pane label="车辆证书" name="vehicle-cert">
<PlCertViewList :cert-list="certList" />
<PlVehicleCertPartView
:cert-list="certList"
:license-plate="licensePlate"
:trailer-no="trailerNo"
/>
</el-tab-pane>
<el-tab-pane
v-for="(person, index) in personnelList"
@@ -15,7 +19,7 @@
</template>
<script setup lang="ts">
import PlCertViewList from '@/components/business/pl-cert-view-list/index.vue'
import PlVehicleCertPartView from '@/components/business/pl-vehicle-cert-part-view/index.vue'
import PersonnelViewPanel from '@/components/business/pl-vehicle-personnel-view-list/PersonnelViewPanel.vue'
import type { CertRead } from '@/api/pl'
import type { VehiclePersonnelViewRow } from '@/components/business/pl-vehicle-personnel-view-list/index.vue'
@@ -23,10 +27,14 @@ import type { VehiclePersonnelViewRow } from '@/components/business/pl-vehicle-p
const props = withDefaults(
defineProps<{
certList?: CertRead[]
licensePlate?: string
trailerNo?: string
personnelList?: VehiclePersonnelViewRow[]
}>(),
{
certList: () => [],
licensePlate: '',
trailerNo: '',
personnelList: () => []
}
)