提交
This commit is contained in:
1 parent
ffc89d9b57
commit
c2cba3e668
53 files changed
+11794
-6264
No files matched your search
@@ -426,6 +426,7 @@ const formData = reactive({
|
||||
const createQualificationRules = {
|
||||
businessLicenseUrls: [
|
||||
{
|
||||
required: true,
|
||||
validator: (_rule: unknown, value: string, callback: (error?: Error) => void) => {
|
||||
if (!value || !String(value).trim()) {
|
||||
callback(new Error('请上传营业执照'))
|
||||
@@ -438,6 +439,7 @@ const createQualificationRules = {
|
||||
],
|
||||
roadTransportLicenseUrls: [
|
||||
{
|
||||
required: true,
|
||||
validator: (_rule: unknown, value: string, callback: (error?: Error) => void) => {
|
||||
if (!value || !String(value).trim()) {
|
||||
callback(new Error('请上传道路经营许可证'))
|
||||
@@ -450,6 +452,7 @@ const createQualificationRules = {
|
||||
],
|
||||
transportAgreementUrls: [
|
||||
{
|
||||
required: true,
|
||||
validator: (_rule: unknown, value: string, callback: (error?: Error) => void) => {
|
||||
if (!value || !String(value).trim()) {
|
||||
callback(new Error('请上传运输协议'))
|
||||
@@ -462,10 +465,12 @@ const createQualificationRules = {
|
||||
]
|
||||
}
|
||||
|
||||
const formRules = computed(() => ({
|
||||
const formRules = {
|
||||
carrierName: [{ required: true, message: '请输入承运商名称', trigger: 'blur' }],
|
||||
...(isEdit.value ? {} : createQualificationRules)
|
||||
}))
|
||||
contactName: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
|
||||
contactPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
|
||||
...createQualificationRules
|
||||
}
|
||||
|
||||
function handleCertFieldChange(
|
||||
field: 'businessLicenseUrls' | 'roadTransportLicenseUrls' | 'transportAgreementUrls'
|
||||
@@ -658,29 +663,12 @@ async function handleSubmit() {
|
||||
contactPhone: formData.contactPhone,
|
||||
status: formData.status,
|
||||
remark: formData.remark,
|
||||
...(isEdit.value
|
||||
? {
|
||||
...(formData.businessLicenseUrls
|
||||
? { businessLicenseUrls: formData.businessLicenseUrls }
|
||||
: {}),
|
||||
...(formData.roadTransportLicenseUrls
|
||||
? { roadTransportLicenseUrls: formData.roadTransportLicenseUrls }
|
||||
: {}),
|
||||
...(formData.transportAgreementUrls
|
||||
? { transportAgreementUrls: formData.transportAgreementUrls }
|
||||
: {}),
|
||||
...(formData.transportAgreementExpireDate
|
||||
? { transportAgreementExpireDate: formData.transportAgreementExpireDate }
|
||||
: {})
|
||||
}
|
||||
: {
|
||||
businessLicenseUrls: formData.businessLicenseUrls,
|
||||
roadTransportLicenseUrls: formData.roadTransportLicenseUrls,
|
||||
transportAgreementUrls: formData.transportAgreementUrls,
|
||||
...(formData.transportAgreementExpireDate
|
||||
? { transportAgreementExpireDate: formData.transportAgreementExpireDate }
|
||||
: {})
|
||||
})
|
||||
businessLicenseUrls: formData.businessLicenseUrls,
|
||||
roadTransportLicenseUrls: formData.roadTransportLicenseUrls,
|
||||
transportAgreementUrls: formData.transportAgreementUrls,
|
||||
...(formData.transportAgreementExpireDate
|
||||
? { transportAgreementExpireDate: formData.transportAgreementExpireDate }
|
||||
: {})
|
||||
}
|
||||
if (isEdit.value) {
|
||||
await carrierApi.update(formData.id, payload)
|
||||
|
||||
@@ -24,6 +24,16 @@
|
||||
<el-option label="停用" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="人员归属">
|
||||
<el-select v-model="searchForm.personnelAffiliation" placeholder="全部" clearable>
|
||||
<el-option
|
||||
v-for="option in PL_PERSONNEL_AFFILIATION_OPTIONS"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<div class="filter-actions-end">
|
||||
@@ -112,6 +122,32 @@
|
||||
<el-link type="primary" @click="handleView(row)">{{ row.personnelName }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('personnelAffiliation')"
|
||||
label="人员归属"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getPersonnelAffiliationTagType(row.personnelAffiliation)" size="small">
|
||||
{{ getPersonnelAffiliationLabel(row.personnelAffiliation) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('personnelType')"
|
||||
label="人员类型"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getPersonnelTypeTagType(row.personnelType)" size="small">
|
||||
{{ getPersonnelTypeLabel(row.personnelType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('phoneNo')"
|
||||
prop="phoneNo"
|
||||
@@ -224,6 +260,26 @@
|
||||
<el-form-item label="身份证号" prop="idCardNo">
|
||||
<el-input v-model="formData.idCardNo" placeholder="请输入身份证号" maxlength="32" />
|
||||
</el-form-item>
|
||||
<el-form-item label="人员归属" prop="personnelAffiliation">
|
||||
<el-select v-model="formData.personnelAffiliation" placeholder="请选择人员归属">
|
||||
<el-option
|
||||
v-for="option in PL_PERSONNEL_AFFILIATION_OPTIONS"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="人员类型" prop="personnelType">
|
||||
<el-select v-model="formData.personnelType" placeholder="请选择人员类型">
|
||||
<el-option
|
||||
v-for="option in PL_PERSONNEL_TYPE_OPTIONS"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="请选择">
|
||||
<el-option label="在职" value="1" />
|
||||
@@ -248,6 +304,7 @@
|
||||
title="人员证书"
|
||||
upload-folder="pl/personnel-cert"
|
||||
:cert-name-options="certNameOptions"
|
||||
:required-cert-names="requiredPersonnelCertNames"
|
||||
layout="table"
|
||||
/>
|
||||
</el-form>
|
||||
@@ -311,7 +368,21 @@ import { pickBxPageResult } from '@/utils/http/bx'
|
||||
import { DEFAULT_LIST_PAGE_SIZE } from '@/utils/table/tableConfig'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import { canDeleteByCreator, filterDeletableByCreator, allRowsDeletableByCreator, CREATOR_DELETE_TIP } from '@/utils/creator-delete'
|
||||
import { getPersonnelCertMissingSuffix } from '@/utils/pl-personnel-cert'
|
||||
import {
|
||||
collectRequiredCertSlotErrors,
|
||||
formatRequiredCertErrors,
|
||||
mergeCertListWithRequiredSlots
|
||||
} from '@/utils/pl-cert-slots'
|
||||
import {
|
||||
getPersonnelCertMissingSuffix,
|
||||
getRequiredPersonnelCertNamesByType,
|
||||
getPersonnelTypeLabel,
|
||||
getPersonnelTypeTagType,
|
||||
getPersonnelAffiliationLabel,
|
||||
getPersonnelAffiliationTagType,
|
||||
PL_PERSONNEL_AFFILIATION_OPTIONS,
|
||||
PL_PERSONNEL_TYPE_OPTIONS
|
||||
} from '@/utils/pl-personnel-cert'
|
||||
|
||||
type CertListItem = CertRow & { _key?: string }
|
||||
type CertFormListRef = InstanceType<typeof PlCertFormList>
|
||||
@@ -319,6 +390,8 @@ type CertFormListRef = InstanceType<typeof PlCertFormList>
|
||||
const loading = ref(false)
|
||||
|
||||
const listColumnDefs = [
|
||||
{ prop: 'personnelAffiliation', label: '人员归属' },
|
||||
{ prop: 'personnelType', label: '人员类型' },
|
||||
{ prop: 'phoneNo', label: '手机号' },
|
||||
{ prop: 'age', label: '年龄' },
|
||||
{ prop: 'idCardNo', label: '身份证号' },
|
||||
@@ -359,7 +432,8 @@ const certNameOptions = [
|
||||
|
||||
const searchForm = reactive({
|
||||
q: '',
|
||||
status: ''
|
||||
status: '',
|
||||
personnelAffiliation: ''
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
@@ -374,12 +448,20 @@ const formData = reactive({
|
||||
phoneNo: '',
|
||||
age: null as number | null,
|
||||
idCardNo: '',
|
||||
personnelType: '',
|
||||
personnelAffiliation: 'self',
|
||||
status: '1',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const requiredPersonnelCertNames = computed(() =>
|
||||
getRequiredPersonnelCertNamesByType(formData.personnelType)
|
||||
)
|
||||
|
||||
const formRules = {
|
||||
personnelName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
personnelAffiliation: [{ required: true, message: '请选择人员归属', trigger: 'change' }],
|
||||
personnelType: [{ required: true, message: '请选择人员类型', trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
}
|
||||
|
||||
@@ -410,7 +492,7 @@ const viewHeroMeta = computed(() => {
|
||||
const viewDialogTitle = computed(() => {
|
||||
const name = viewDetail.value?.personnelName || '人员详情'
|
||||
if (!viewDetail.value) return name
|
||||
return name + getPersonnelCertMissingSuffix(viewDetail.value.certList || [])
|
||||
return name + getPersonnelCertMissingSuffix(viewDetail.value.certList || [], undefined, viewDetail.value.personnelType)
|
||||
})
|
||||
|
||||
const viewSections = computed<ViewSection[]>(() => {
|
||||
@@ -423,6 +505,16 @@ const viewSections = computed<ViewSection[]>(() => {
|
||||
fields: [
|
||||
{ label: '姓名', value: d.personnelName },
|
||||
{ label: '手机号', value: d.phoneNo || '—' },
|
||||
{
|
||||
label: '人员归属',
|
||||
value: getPersonnelAffiliationLabel(d.personnelAffiliation),
|
||||
tagType: getPersonnelAffiliationTagType(d.personnelAffiliation)
|
||||
},
|
||||
{
|
||||
label: '人员类型',
|
||||
value: getPersonnelTypeLabel(d.personnelType),
|
||||
tagType: getPersonnelTypeTagType(d.personnelType)
|
||||
},
|
||||
{ label: '年龄', value: d.age ?? '—' },
|
||||
{ label: '身份证号', value: maskIdCard(d.idCardNo) },
|
||||
{
|
||||
@@ -443,7 +535,7 @@ const viewSections = computed<ViewSection[]>(() => {
|
||||
tableMode: 'triple'
|
||||
},
|
||||
{
|
||||
title: `人员证书${viewDetail.value ? getPersonnelCertMissingSuffix(viewDetail.value.certList || []) : ''}`,
|
||||
title: `人员证书${viewDetail.value ? getPersonnelCertMissingSuffix(viewDetail.value.certList || [], undefined, viewDetail.value.personnelType) : ''}`,
|
||||
fields: [],
|
||||
slot: 'certs'
|
||||
}
|
||||
@@ -451,15 +543,39 @@ const viewSections = computed<ViewSection[]>(() => {
|
||||
})
|
||||
|
||||
function validateCerts(): boolean {
|
||||
if (!formData.personnelType) {
|
||||
ElMessage.warning('请选择人员类型')
|
||||
return false
|
||||
}
|
||||
const errors = collectRequiredCertSlotErrors(
|
||||
certList.value,
|
||||
requiredPersonnelCertNames.value
|
||||
)
|
||||
if (errors.length) {
|
||||
ElMessage.warning(formatRequiredCertErrors(errors))
|
||||
return false
|
||||
}
|
||||
for (const cert of certList.value) {
|
||||
if (!cert.certName?.trim()) {
|
||||
ElMessage.warning('证书名称不能为空')
|
||||
if (!cert._required && !cert.certName?.trim()) {
|
||||
ElMessage.warning('其他证书名称不能为空')
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
watch(
|
||||
() => formData.personnelType,
|
||||
() => {
|
||||
if (!formData.personnelType) return
|
||||
certList.value = mergeCertListWithRequiredSlots(
|
||||
certList.value,
|
||||
getRequiredPersonnelCertNamesByType(formData.personnelType)
|
||||
)
|
||||
certFormListRef.value?.refreshRequiredSlots?.()
|
||||
}
|
||||
)
|
||||
|
||||
function resetCertList() {
|
||||
certList.value = []
|
||||
certFormListRef.value?.clearUploadRefs()
|
||||
@@ -471,6 +587,7 @@ async function fetchData() {
|
||||
const res = await personnelApi.list({
|
||||
q: searchForm.q || undefined,
|
||||
status: searchForm.status || undefined,
|
||||
personnelAffiliation: searchForm.personnelAffiliation || undefined,
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize
|
||||
})
|
||||
@@ -492,6 +609,7 @@ function handleSearch() {
|
||||
function handleReset() {
|
||||
searchForm.q = ''
|
||||
searchForm.status = ''
|
||||
searchForm.personnelAffiliation = ''
|
||||
selectedRows.value = []
|
||||
pagination.page = 1
|
||||
fetchData()
|
||||
@@ -548,6 +666,8 @@ function handleAdd() {
|
||||
phoneNo: '',
|
||||
age: null,
|
||||
idCardNo: '',
|
||||
personnelType: '',
|
||||
personnelAffiliation: 'self',
|
||||
status: '1',
|
||||
remark: ''
|
||||
})
|
||||
@@ -583,19 +703,24 @@ async function handleEdit(row: Personnel) {
|
||||
phoneNo: detail.phoneNo || '',
|
||||
age: detail.age ?? null,
|
||||
idCardNo: detail.idCardNo || '',
|
||||
personnelType: detail.personnelType || 'both',
|
||||
personnelAffiliation: detail.personnelAffiliation || 'self',
|
||||
status: detail.status,
|
||||
remark: detail.remark || ''
|
||||
})
|
||||
certList.value = (detail.certList || []).map(c => ({
|
||||
_key: c.id || c.certId || `cert-${Math.random().toString(36).slice(2, 10)}`,
|
||||
certId: c.id || c.certId,
|
||||
certName: c.certName,
|
||||
certNo: c.certNo,
|
||||
expireDate: c.expireDate,
|
||||
remindDays: c.remindDays ?? 30,
|
||||
attachmentUrls: c.attachmentUrls || ''
|
||||
}))
|
||||
certFormListRef.value?.clearUploadRefs()
|
||||
certList.value = mergeCertListWithRequiredSlots(
|
||||
(detail.certList || []).map(c => ({
|
||||
_key: c.id || c.certId || `cert-${Math.random().toString(36).slice(2, 10)}`,
|
||||
certId: c.id || c.certId,
|
||||
certName: c.certName,
|
||||
certNo: c.certNo,
|
||||
expireDate: c.expireDate,
|
||||
remindDays: c.remindDays ?? 30,
|
||||
attachmentUrls: c.attachmentUrls || ''
|
||||
})),
|
||||
getRequiredPersonnelCertNamesByType(detail.personnelType || 'both')
|
||||
)
|
||||
certFormListRef.value?.refreshRequiredSlots?.()
|
||||
} catch {
|
||||
ElMessage.error('加载人员详情失败')
|
||||
}
|
||||
@@ -616,6 +741,8 @@ async function handleSubmit() {
|
||||
phoneNo: formData.phoneNo || undefined,
|
||||
age: formData.age ?? undefined,
|
||||
idCardNo: formData.idCardNo || undefined,
|
||||
personnelType: formData.personnelType,
|
||||
personnelAffiliation: formData.personnelAffiliation,
|
||||
status: formData.status,
|
||||
remark: formData.remark || undefined,
|
||||
certList: certList.value.map(c => ({
|
||||
@@ -664,7 +791,8 @@ async function handleDelete(row: Personnel) {
|
||||
useDbInstantSearch(
|
||||
() => ({
|
||||
q: searchForm.q,
|
||||
status: searchForm.status
|
||||
status: searchForm.status,
|
||||
personnelAffiliation: searchForm.personnelAffiliation
|
||||
}),
|
||||
handleSearch
|
||||
)
|
||||
|
||||
@@ -148,36 +148,6 @@
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('planType')"
|
||||
prop="planType"
|
||||
label="单据类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getPlanTypeTagType(row.planType)" size="small">
|
||||
{{ getPlanTypeLabel(row.planType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('customerName')"
|
||||
prop="customerName"
|
||||
label="客户名称"
|
||||
min-width="180"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column v-if="isColumnVisible('products')" label="产品信息" min-width="240">
|
||||
<template #default="{ row }">
|
||||
<PlPlanListProductCell :products="row.products" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isColumnVisible('planAttachments')" label="计划附件" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<PlPlanListCertCell :products="row.products" compact hide-product />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('archiveStatus')"
|
||||
prop="archiveStatus"
|
||||
@@ -217,6 +187,36 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('planType')"
|
||||
prop="planType"
|
||||
label="单据类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getPlanTypeTagType(row.planType)" size="small">
|
||||
{{ getPlanTypeLabel(row.planType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('customerName')"
|
||||
prop="customerName"
|
||||
label="客户名称"
|
||||
min-width="180"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column v-if="isColumnVisible('products')" label="产品信息" min-width="240">
|
||||
<template #default="{ row }">
|
||||
<PlPlanListProductCell :products="row.products" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isColumnVisible('planAttachments')" label="计划附件" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<PlPlanListCertCell :products="row.products" compact hide-product />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('licensePlate')"
|
||||
prop="licensePlate"
|
||||
@@ -313,6 +313,11 @@ import {
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import { planApi, type Plan } from '@/api/pl'
|
||||
import {
|
||||
buildMissingHazardCertReadyMessage,
|
||||
enrichPlanProductsHazardFromCrm
|
||||
} from '@/composables/usePlToxicProductCertCheck'
|
||||
import { pickBxData } from '@/utils/http/bx'
|
||||
import PlPlanArchiveDialog from '@/components/business/pl-plan-archive-dialog/index.vue'
|
||||
import PlPlanFormDialog from '@/components/business/pl-plan-form-dialog/index.vue'
|
||||
import PlPlanListCertCell from '@/components/business/pl-plan-list-cert-cell/index.vue'
|
||||
@@ -347,13 +352,13 @@ const planList = ref<Plan[]>([])
|
||||
|
||||
const listColumnDefs = [
|
||||
{ prop: 'planDate', label: '计划日期' },
|
||||
{ prop: 'archiveStatus', label: '归档状态' },
|
||||
{ prop: 'followStatus', label: '跟进状态' },
|
||||
{ prop: 'transportStatus', label: '运输状态' },
|
||||
{ prop: 'planType', label: '单据类型' },
|
||||
{ prop: 'customerName', label: '客户名称' },
|
||||
{ prop: 'products', label: '产品信息' },
|
||||
{ prop: 'planAttachments', label: '计划附件' },
|
||||
{ prop: 'archiveStatus', label: '归档状态' },
|
||||
{ prop: 'followStatus', label: '跟进状态' },
|
||||
{ prop: 'transportStatus', label: '运输状态' },
|
||||
{ prop: 'licensePlate', label: '车牌号' },
|
||||
{ prop: 'personnelSummary', label: '人员' },
|
||||
{ prop: 'followUserName', label: '跟进人', defaultVisible: false }
|
||||
@@ -530,7 +535,34 @@ function handleEdit(row: Plan) {
|
||||
formDialogVisible.value = true
|
||||
}
|
||||
|
||||
function handleArchive(row: Plan) {
|
||||
async function handleArchive(row: Plan) {
|
||||
if (row.archiveStatus === 'archived') {
|
||||
archivePlanId.value = row.id
|
||||
archiveDialogVisible.value = true
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await planApi.get(row.id)
|
||||
const data = pickBxData<Plan>(res)
|
||||
if (!data?.id) {
|
||||
throw new Error('运输计划数据无效')
|
||||
}
|
||||
const rawProducts = Array.isArray(data.products) ? data.products : []
|
||||
const enrichedProducts = await enrichPlanProductsHazardFromCrm(rawProducts)
|
||||
const certMessage = buildMissingHazardCertReadyMessage(data, enrichedProducts)
|
||||
if (certMessage) {
|
||||
await ElMessageBox.alert(`${certMessage},请至【危化品证上传】页面操作。`, '无法归档', {
|
||||
type: 'warning'
|
||||
})
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : '加载运输计划失败'
|
||||
ElMessage.error(message.includes('加载') ? message : `加载运输计划失败:${message}`)
|
||||
return
|
||||
}
|
||||
|
||||
archivePlanId.value = row.id
|
||||
archiveDialogVisible.value = true
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.transportStatus === 'arranged' ? 'success' : 'info'" size="small">
|
||||
<el-tag :type="row.transportStatus === 'arranged' ? 'success' : 'danger'" size="small">
|
||||
{{ row.transportStatus === 'arranged' ? '已安排' : '待安排' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
@@ -316,6 +316,7 @@
|
||||
upload-folder="pl/vehicle-cert"
|
||||
:cert-name-options="certNameOptions"
|
||||
layout="table"
|
||||
enforce-self-vehicle-required
|
||||
/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -362,7 +363,8 @@
|
||||
:trailer-no="viewDetail?.trailerNo"
|
||||
:vehicle-id="viewDetail?.id"
|
||||
:personnel-list="viewPersonnelList"
|
||||
check-self-vehicle-certs
|
||||
vehicle-cert-check-type="self"
|
||||
check-linked-personnel-certs
|
||||
cert-layout="table"
|
||||
/>
|
||||
</template>
|
||||
@@ -403,12 +405,16 @@ import {
|
||||
getPlVehicleStatusTagType,
|
||||
normalizePlPersonnelSummaryDisplay
|
||||
} from '@/utils/pl-display'
|
||||
import {
|
||||
collectSelfVehicleRequiredCertErrors,
|
||||
ensureSelfVehicleCertSlots,
|
||||
formatRequiredCertErrors
|
||||
} from '@/utils/pl-cert-slots'
|
||||
import {
|
||||
PL_VEHICLE_CERT_NAME_OPTIONS,
|
||||
splitVehicleCertsByPart,
|
||||
getSelfVehicleCertMissingSuffix,
|
||||
validateVehicleCertNamesNotEmpty
|
||||
getSelfVehicleCertMissingSuffix
|
||||
} from '@/utils/pl-vehicle-cert'
|
||||
import { validatePersonnelTypeRoleCompat, validatePersonnelAffiliationVehicleCompat } from '@/utils/pl-personnel-cert'
|
||||
import { canDeleteByCreator, filterDeletableByCreator, allRowsDeletableByCreator, CREATOR_DELETE_TIP } from '@/utils/creator-delete'
|
||||
|
||||
const VEHICLE_TYPE_SELF = 'self'
|
||||
@@ -628,7 +634,7 @@ function resolvePersonnelRows(rows: VehiclePersonnelRow[]) {
|
||||
}
|
||||
|
||||
async function loadPersonnelOptions() {
|
||||
const res = await personnelApi.list({ status: '1', pageSize: 100 })
|
||||
const res = await personnelApi.list({ status: '1', pageSize: 100, personnelAffiliation: 'self' })
|
||||
personnelOptions.value = pickBxList<Personnel>(res)
|
||||
}
|
||||
|
||||
@@ -682,14 +688,29 @@ function validateSubTables(): boolean {
|
||||
ElMessage.warning('请选择人员类型')
|
||||
return false
|
||||
}
|
||||
const person = personnelOptions.value.find(p => p.id === row.personnelId)
|
||||
if (
|
||||
!validatePersonnelTypeRoleCompat(person?.personnelType, row.role, message =>
|
||||
ElMessage.warning(message)
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
!validatePersonnelAffiliationVehicleCompat(person?.personnelAffiliation, 'self', message =>
|
||||
ElMessage.warning(message)
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const hasTrailer = Boolean(formData.trailerNo?.trim())
|
||||
const { headCerts, trailerCerts } = splitVehicleCertsByPart(certList.value)
|
||||
return validateVehicleCertNamesNotEmpty(
|
||||
[headCerts, ...(hasTrailer ? [trailerCerts] : [])],
|
||||
message => ElMessage.warning(message)
|
||||
)
|
||||
const certErrors = collectSelfVehicleRequiredCertErrors(certList.value, formData.trailerNo)
|
||||
if (certErrors.length) {
|
||||
ElMessage.warning(formatRequiredCertErrors(certErrors))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
@@ -794,6 +815,7 @@ function handleAdd() {
|
||||
remark: ''
|
||||
})
|
||||
resetSubTables()
|
||||
certList.value = ensureSelfVehicleCertSlots([], formData.trailerNo)
|
||||
loadPersonnelOptions()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
@@ -845,17 +867,20 @@ async function handleEdit(row: Vehicle) {
|
||||
age: p.age ?? null
|
||||
}))
|
||||
)
|
||||
certList.value = (detail.certList || []).map(c => ({
|
||||
_key: c.id || c.certId || `cert-${Math.random().toString(36).slice(2, 10)}`,
|
||||
certId: c.id || c.certId,
|
||||
vehiclePart: c.vehiclePart,
|
||||
certName: c.certName,
|
||||
certNo: c.certNo,
|
||||
expireDate: c.expireDate,
|
||||
remindDays: c.remindDays ?? 30,
|
||||
attachmentUrls: c.attachmentUrls || ''
|
||||
}))
|
||||
certFormListRef.value?.clearUploadRefs()
|
||||
certList.value = ensureSelfVehicleCertSlots(
|
||||
(detail.certList || []).map(c => ({
|
||||
_key: c.id || c.certId || `cert-${Math.random().toString(36).slice(2, 10)}`,
|
||||
certId: c.id || c.certId,
|
||||
vehiclePart: c.vehiclePart,
|
||||
certName: c.certName,
|
||||
certNo: c.certNo,
|
||||
expireDate: c.expireDate,
|
||||
remindDays: c.remindDays ?? 30,
|
||||
attachmentUrls: c.attachmentUrls || ''
|
||||
})),
|
||||
formData.trailerNo
|
||||
)
|
||||
certFormListRef.value?.refreshRequiredSlots?.()
|
||||
} catch {
|
||||
dialogVisible.value = false
|
||||
ElMessage.error('加载车辆详情失败')
|
||||
|
||||
@@ -335,6 +335,8 @@
|
||||
upload-folder="pl/vehicle-cert"
|
||||
:cert-name-options="certNameOptions"
|
||||
layout="table"
|
||||
:enforce-self-vehicle-required="formData.vehicleType === 'self'"
|
||||
:enforce-carrier-vehicle-required="formData.vehicleType === 'carrier'"
|
||||
/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -381,7 +383,10 @@
|
||||
:trailer-no="viewDetail?.trailerNo"
|
||||
:vehicle-id="viewDetail?.id"
|
||||
:personnel-list="viewPersonnelList"
|
||||
:check-self-vehicle-certs="viewDetail?.vehicleType === 'self'"
|
||||
:vehicle-cert-check-type="viewVehicleCertCheckType"
|
||||
:check-linked-personnel-certs="
|
||||
viewDetail?.vehicleType === 'self' || viewDetail?.vehicleType === 'carrier'
|
||||
"
|
||||
cert-layout="table"
|
||||
/>
|
||||
</template>
|
||||
@@ -416,12 +421,18 @@ import { pickBxList, pickBxPageResult } from '@/utils/http/bx'
|
||||
import { DEFAULT_LIST_PAGE_SIZE } from '@/utils/table/tableConfig'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import { formatPlVehiclePlateDisplay, normalizePlPersonnelSummaryDisplay } from '@/utils/pl-display'
|
||||
import {
|
||||
collectCarrierVehicleRequiredCertErrors,
|
||||
collectSelfVehicleRequiredCertErrors,
|
||||
ensureVehicleCertSlots,
|
||||
formatRequiredCertErrors
|
||||
} from '@/utils/pl-cert-slots'
|
||||
import {
|
||||
PL_VEHICLE_CERT_NAME_OPTIONS,
|
||||
splitVehicleCertsByPart,
|
||||
getSelfVehicleCertMissingSuffix,
|
||||
validateVehicleCertNamesNotEmpty
|
||||
getVehicleCertMissingSuffix,
|
||||
type PlVehicleCertCheckType
|
||||
} from '@/utils/pl-vehicle-cert'
|
||||
import { validatePersonnelTypeRoleCompat, validatePersonnelAffiliationVehicleCompat } from '@/utils/pl-personnel-cert'
|
||||
import { canDeleteByCreator, filterDeletableByCreator, allRowsDeletableByCreator, CREATOR_DELETE_TIP } from '@/utils/creator-delete'
|
||||
|
||||
type CertListItem = CertRow & { _key?: string }
|
||||
@@ -534,12 +545,19 @@ const viewHeroMeta = computed(() => {
|
||||
return parts.join(' · ')
|
||||
})
|
||||
|
||||
const viewVehicleCertCheckType = computed<PlVehicleCertCheckType | undefined>(() => {
|
||||
const type = viewDetail.value?.vehicleType
|
||||
return type === 'self' || type === 'carrier' ? type : undefined
|
||||
})
|
||||
|
||||
const viewDialogTitle = computed(() => {
|
||||
const plate = viewDetail.value?.licensePlate || '车辆详情'
|
||||
if (!viewDetail.value || viewDetail.value.vehicleType !== 'self') return plate
|
||||
const checkType = viewVehicleCertCheckType.value
|
||||
if (!viewDetail.value || !checkType) return plate
|
||||
return (
|
||||
plate +
|
||||
getSelfVehicleCertMissingSuffix({
|
||||
getVehicleCertMissingSuffix({
|
||||
vehicleType: checkType,
|
||||
certList: viewDetail.value.certList,
|
||||
trailerNo: viewDetail.value.trailerNo
|
||||
})
|
||||
@@ -682,7 +700,13 @@ function resolvePersonnelRows(rows: VehiclePersonnelRow[]) {
|
||||
}
|
||||
|
||||
async function loadPersonnelOptions() {
|
||||
const res = await personnelApi.list({ status: '1', pageSize: 100 })
|
||||
const affiliation =
|
||||
formData.vehicleType === 'carrier' ? 'carrier' : formData.vehicleType === 'self' ? 'self' : undefined
|
||||
const res = await personnelApi.list({
|
||||
status: '1',
|
||||
pageSize: 100,
|
||||
personnelAffiliation: affiliation
|
||||
})
|
||||
personnelOptions.value = pickBxList<Personnel>(res)
|
||||
}
|
||||
|
||||
@@ -736,14 +760,41 @@ function validateSubTables(): boolean {
|
||||
ElMessage.warning('请选择人员类型')
|
||||
return false
|
||||
}
|
||||
if (formData.vehicleType === 'self' || formData.vehicleType === 'carrier') {
|
||||
const person = personnelOptions.value.find(p => p.id === row.personnelId)
|
||||
if (
|
||||
!validatePersonnelTypeRoleCompat(person?.personnelType, row.role, message =>
|
||||
ElMessage.warning(message)
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
!validatePersonnelAffiliationVehicleCompat(
|
||||
person?.personnelAffiliation,
|
||||
formData.vehicleType,
|
||||
message => ElMessage.warning(message)
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hasTrailer = Boolean(formData.trailerNo?.trim())
|
||||
const { headCerts, trailerCerts } = splitVehicleCertsByPart(certList.value)
|
||||
return validateVehicleCertNamesNotEmpty(
|
||||
[headCerts, ...(hasTrailer ? [trailerCerts] : [])],
|
||||
message => ElMessage.warning(message)
|
||||
)
|
||||
if (formData.vehicleType === 'self') {
|
||||
const certErrors = collectSelfVehicleRequiredCertErrors(certList.value, formData.trailerNo)
|
||||
if (certErrors.length) {
|
||||
ElMessage.warning(formatRequiredCertErrors(certErrors))
|
||||
return false
|
||||
}
|
||||
} else if (formData.vehicleType === 'carrier') {
|
||||
const certErrors = collectCarrierVehicleRequiredCertErrors(certList.value, formData.trailerNo)
|
||||
if (certErrors.length) {
|
||||
ElMessage.warning(formatRequiredCertErrors(certErrors))
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
@@ -850,6 +901,7 @@ function handleAdd() {
|
||||
remark: ''
|
||||
})
|
||||
resetSubTables()
|
||||
certList.value = ensureVehicleCertSlots([], formData.trailerNo, formData.vehicleType as PlVehicleCertCheckType)
|
||||
loadPersonnelOptions()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
@@ -898,7 +950,7 @@ async function handleEdit(row: Vehicle) {
|
||||
age: p.age ?? null
|
||||
}))
|
||||
)
|
||||
certList.value = (detail.certList || []).map(c => ({
|
||||
const mappedCerts = (detail.certList || []).map(c => ({
|
||||
_key: c.id || c.certId || `cert-${Math.random().toString(36).slice(2, 10)}`,
|
||||
certId: c.id || c.certId,
|
||||
vehiclePart: c.vehiclePart,
|
||||
@@ -908,7 +960,15 @@ async function handleEdit(row: Vehicle) {
|
||||
remindDays: c.remindDays ?? 30,
|
||||
attachmentUrls: c.attachmentUrls || ''
|
||||
}))
|
||||
certFormListRef.value?.clearUploadRefs()
|
||||
certList.value =
|
||||
detail.vehicleType === 'self' || detail.vehicleType === 'carrier'
|
||||
? ensureVehicleCertSlots(
|
||||
mappedCerts,
|
||||
formData.trailerNo,
|
||||
detail.vehicleType as PlVehicleCertCheckType
|
||||
)
|
||||
: mappedCerts
|
||||
certFormListRef.value?.refreshRequiredSlots?.()
|
||||
} finally {
|
||||
dialogLoading.value = false
|
||||
}
|
||||
@@ -993,6 +1053,22 @@ async function handleDelete(row: Vehicle) {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => formData.vehicleType,
|
||||
async type => {
|
||||
if (!dialogVisible.value) return
|
||||
await loadPersonnelOptions()
|
||||
if (type === 'self' || type === 'carrier') {
|
||||
certList.value = ensureVehicleCertSlots(
|
||||
certList.value,
|
||||
formData.trailerNo,
|
||||
type as PlVehicleCertCheckType
|
||||
)
|
||||
certFormListRef.value?.refreshRequiredSlots?.()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
useDbInstantSearch(
|
||||
() => ({
|
||||
q: searchForm.q,
|
||||
|
||||
Reference in new issue
Block a user