| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- import type { CertRow } from '@/api/pl'
- export const PL_VEHICLE_CERT_PART_HEAD = 'head'
- export const PL_VEHICLE_CERT_PART_TRAILER = 'trailer'
- export const PL_VEHICLE_CERT_MISSING_SUFFIX = '(证书缺失)'
- /** @deprecated 保存不再阻断,仅保留供历史引用 */
- export const PL_VEHICLE_REQUIRED_CERT_NAMES = ['行驶证', '营运证'] as const
- export const PL_SELF_VEHICLE_HEAD_REQUIRED_CERT_NAMES = [
- '行驶证',
- '营运证',
- '保险单'
- ] as const
- export const PL_SELF_VEHICLE_TRAILER_REQUIRED_CERT_NAMES = [
- '行驶证',
- '营运证',
- '保险单',
- '罐检报告'
- ] as const
- export const PL_CARRIER_VEHICLE_HEAD_REQUIRED_CERT_NAMES = ['行驶证', '营运证'] as const
- export const PL_CARRIER_VEHICLE_TRAILER_REQUIRED_CERT_NAMES = ['行驶证', '营运证'] as const
- /** 特殊挂车号:不展示挂车证件槽位、不校验挂车证件 */
- export const PL_HUOLALA_TRAILER_NO = '货拉拉'
- export type PlVehicleCertCheckType = 'self' | 'carrier'
- export function isHuolalaTrailerNo(trailerNo?: string | null): boolean {
- return (trailerNo || '').trim() === PL_HUOLALA_TRAILER_NO
- }
- /** 有挂车号且非货拉拉时展示挂车证件区 */
- export function shouldShowVehicleTrailerCertSlots(trailerNo?: string | null): boolean {
- const text = (trailerNo || '').trim()
- return Boolean(text) && !isHuolalaTrailerNo(text)
- }
- /** 仅山峡自有 + 正常挂车号时强制挂车证件 */
- export function shouldRequireVehicleTrailerCerts(
- vehicleType: PlVehicleCertCheckType,
- trailerNo?: string | null
- ): boolean {
- return vehicleType === 'self' && shouldShowVehicleTrailerCertSlots(trailerNo)
- }
- export const PL_VEHICLE_CERT_NAME_OPTIONS = [
- '行驶证',
- '营运证',
- '道路运输证',
- '驾驶证',
- '押运员证',
- '危化品从业资格证',
- '保险单',
- '罐检报告'
- ]
- export type VehicleCertPart = typeof PL_VEHICLE_CERT_PART_HEAD | typeof PL_VEHICLE_CERT_PART_TRAILER
- export function normalizeVehicleCertPart(part?: string | null): VehicleCertPart {
- return part === PL_VEHICLE_CERT_PART_TRAILER ? PL_VEHICLE_CERT_PART_TRAILER : PL_VEHICLE_CERT_PART_HEAD
- }
- export function splitVehicleCertsByPart<T extends CertRow>(certList: T[] = []) {
- const headCerts: T[] = []
- const trailerCerts: T[] = []
- for (const cert of certList) {
- if (normalizeVehicleCertPart(cert.vehiclePart) === PL_VEHICLE_CERT_PART_TRAILER) {
- trailerCerts.push(cert)
- } else {
- headCerts.push(cert)
- }
- }
- return { headCerts, trailerCerts }
- }
- export function mergeVehicleCertsByPart<T extends CertRow>(
- headCerts: T[],
- trailerCerts: T[],
- hasTrailer: boolean
- ) {
- const normalizedHead = headCerts.map(cert => ({
- ...cert,
- vehiclePart: PL_VEHICLE_CERT_PART_HEAD
- }))
- if (!hasTrailer) {
- return normalizedHead
- }
- const normalizedTrailer = trailerCerts.map(cert => ({
- ...cert,
- vehiclePart: PL_VEHICLE_CERT_PART_TRAILER
- }))
- return [...normalizedHead, ...normalizedTrailer]
- }
- export function isVehicleCertUploaded(cert?: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls'>) {
- return Boolean(cert?.certName?.trim() && cert?.expireDate)
- }
- export function getVehicleHeadRequiredCertNames(
- vehicleType: PlVehicleCertCheckType
- ): readonly string[] {
- return vehicleType === 'carrier'
- ? PL_CARRIER_VEHICLE_HEAD_REQUIRED_CERT_NAMES
- : PL_SELF_VEHICLE_HEAD_REQUIRED_CERT_NAMES
- }
- export function getVehicleTrailerRequiredCertNames(
- vehicleType: PlVehicleCertCheckType
- ): readonly string[] {
- return vehicleType === 'carrier'
- ? PL_CARRIER_VEHICLE_TRAILER_REQUIRED_CERT_NAMES
- : PL_SELF_VEHICLE_TRAILER_REQUIRED_CERT_NAMES
- }
- export function getMissingVehiclePartCertNames(
- certList: CertRow[] = [],
- part: VehicleCertPart,
- vehicleType: PlVehicleCertCheckType
- ): string[] {
- const requiredNames =
- part === PL_VEHICLE_CERT_PART_TRAILER
- ? getVehicleTrailerRequiredCertNames(vehicleType)
- : getVehicleHeadRequiredCertNames(vehicleType)
- return requiredNames.filter(requiredName => {
- const matched = certList.find(cert => cert.certName?.trim() === requiredName)
- return !isVehicleCertUploaded(matched)
- })
- }
- export function getMissingSelfVehiclePartCertNames(
- certList: CertRow[] = [],
- part: VehicleCertPart
- ): string[] {
- return getMissingVehiclePartCertNames(certList, part, 'self')
- }
- export function hasVehicleCertMissing(options: {
- vehicleType: PlVehicleCertCheckType
- certList?: CertRow[]
- trailerNo?: string | null
- }): boolean {
- const { vehicleType, certList = [], trailerNo } = options
- const { headCerts, trailerCerts } = splitVehicleCertsByPart(certList)
- // 三方车辆证件不强制,列表缺失后缀仅对自有生效
- if (vehicleType === 'carrier') {
- return false
- }
- if (
- getMissingVehiclePartCertNames(headCerts, PL_VEHICLE_CERT_PART_HEAD, vehicleType).length > 0
- ) {
- return true
- }
- if (
- shouldRequireVehicleTrailerCerts(vehicleType, trailerNo) &&
- getMissingVehiclePartCertNames(trailerCerts, PL_VEHICLE_CERT_PART_TRAILER, vehicleType)
- .length > 0
- ) {
- return true
- }
- return false
- }
- export function hasSelfVehicleCertMissing(options: {
- certList?: CertRow[]
- trailerNo?: string | null
- }): boolean {
- return hasVehicleCertMissing({ ...options, vehicleType: 'self' })
- }
- export function getVehicleCertMissingSuffix(options: {
- vehicleType: PlVehicleCertCheckType
- certList?: CertRow[]
- trailerNo?: string | null
- }): string {
- return hasVehicleCertMissing(options) ? PL_VEHICLE_CERT_MISSING_SUFFIX : ''
- }
- export function getSelfVehicleCertMissingSuffix(options: {
- certList?: CertRow[]
- trailerNo?: string | null
- }): string {
- return getVehicleCertMissingSuffix({ ...options, vehicleType: 'self' })
- }
- export function getVehiclePartCertMissingSuffix(
- certList: CertRow[] = [],
- part: VehicleCertPart,
- vehicleType?: PlVehicleCertCheckType | null
- ): string {
- if (!vehicleType) return ''
- return getMissingVehiclePartCertNames(certList, part, vehicleType).length > 0
- ? PL_VEHICLE_CERT_MISSING_SUFFIX
- : ''
- }
- export function getSelfVehiclePartCertMissingSuffix(
- certList: CertRow[] = [],
- part: VehicleCertPart
- ): string {
- return getVehiclePartCertMissingSuffix(certList, part, 'self')
- }
- /** @deprecated 保存不再阻断证书完整性,仅校验证书名称非空等基础项 */
- export function validateVehicleRequiredCerts(options: {
- headCerts: CertRow[]
- trailerCerts: CertRow[]
- hasTrailer: boolean
- licensePlate: string
- trailerNo: string
- onError: (message: string) => void
- }) {
- const { headCerts, trailerCerts, hasTrailer } = options
- for (const certs of [headCerts, ...(hasTrailer ? [trailerCerts] : [])]) {
- for (const cert of certs) {
- if (!cert.certName?.trim()) {
- options.onError('证书名称不能为空')
- return false
- }
- }
- }
- return true
- }
- export function filterVehicleCertsForSubmit<T extends CertRow>(
- headCerts: T[],
- trailerCerts: T[],
- hasTrailer: boolean
- ) {
- return mergeVehicleCertsByPart(headCerts, trailerCerts, hasTrailer)
- }
- export function validateVehicleCertNamesNotEmpty(
- certLists: CertRow[][],
- onError: (message: string) => void
- ): boolean {
- for (const certs of certLists) {
- for (const cert of certs) {
- if (!cert.certName?.trim()) {
- onError('证书名称不能为空')
- return false
- }
- }
- }
- return true
- }
- export const PL_VEHICLE_CERT_SHORT_LABELS: Record<string, string> = {
- 行驶证: '行',
- 营运证: '营',
- 保险单: '保',
- 罐检报告: '罐'
- }
- export type VehicleCertBadgeStatus = 'missing' | 'valid' | 'expiring' | 'expired'
- export interface VehicleCertBadgeItem {
- requiredName: string
- shortLabel: string
- status: VehicleCertBadgeStatus
- title: string
- }
- function matchVehicleRequiredCertName(
- actualName: string | undefined,
- requiredName: string
- ): boolean {
- return (actualName?.trim() || '') === requiredName
- }
- function getVehicleCertMissingFieldLabels(
- cert: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls'> | undefined,
- requiredName: string
- ): string[] {
- if (!cert || !matchVehicleRequiredCertName(cert.certName, requiredName)) {
- return ['有效期']
- }
- const missing: string[] = []
- if (!cert.expireDate) missing.push('有效期')
- // 证书附件非必填
- return missing
- }
- function formatVehicleCertBadgeTooltip(
- partLabel: string,
- requiredName: string,
- status: VehicleCertBadgeStatus,
- cert?: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls' | 'remindDays'>
- ): string {
- const prefix = partLabel ? `${partLabel} · ` : ''
- if (status === 'missing') {
- if (!cert || !matchVehicleRequiredCertName(cert.certName, requiredName)) {
- return `${prefix}${requiredName}:缺失(未登记该证书)`
- }
- const missing = getVehicleCertMissingFieldLabels(cert, requiredName)
- return `${prefix}${requiredName}:缺失(缺少${missing.join('、')})`
- }
- const expireDate = cert?.expireDate || '—'
- const expDate = new Date(expireDate)
- const today = new Date()
- today.setHours(0, 0, 0, 0)
- expDate.setHours(0, 0, 0, 0)
- const diffDays = Math.ceil((expDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
- const certNo = cert?.certNo?.trim()
- const certNoText = certNo ? `,证号 ${certNo}` : ''
- if (status === 'expired') {
- return `${prefix}${requiredName}:已过期,有效期 ${expireDate},已过期 ${Math.abs(diffDays)} 天${certNoText}`
- }
- if (status === 'expiring') {
- return `${prefix}${requiredName}:临期,有效期 ${expireDate},剩余 ${diffDays} 天${certNoText}`
- }
- return `${prefix}${requiredName}:正常,有效期 ${expireDate}${certNoText}`
- }
- export function getVehicleCertBadgeStatus(
- cert: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls' | 'remindDays'> | undefined,
- requiredName: string
- ): VehicleCertBadgeStatus {
- if (!isVehicleCertUploaded(cert) || !matchVehicleRequiredCertName(cert?.certName, requiredName)) {
- return 'missing'
- }
- const expireDate = cert?.expireDate
- if (!expireDate) return 'missing'
- const remindDays = cert?.remindDays ?? 30
- const expDate = new Date(expireDate)
- const today = new Date()
- today.setHours(0, 0, 0, 0)
- expDate.setHours(0, 0, 0, 0)
- const diffDays = Math.ceil((expDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
- if (diffDays <= 0) return 'expired'
- if (diffDays <= remindDays) return 'expiring'
- return 'valid'
- }
- export function getVehiclePartRequiredCertBadges(options: {
- vehicleType: PlVehicleCertCheckType
- certList?: CertRow[]
- part: VehicleCertPart
- partLabel?: string
- }): VehicleCertBadgeItem[] {
- const { vehicleType, certList = [], part, partLabel = '' } = options
- const partCerts =
- part === PL_VEHICLE_CERT_PART_TRAILER
- ? splitVehicleCertsByPart(certList).trailerCerts
- : splitVehicleCertsByPart(certList).headCerts
- const requiredNames =
- part === PL_VEHICLE_CERT_PART_TRAILER
- ? getVehicleTrailerRequiredCertNames(vehicleType)
- : getVehicleHeadRequiredCertNames(vehicleType)
- return requiredNames.map(requiredName => {
- const matched = partCerts.find(cert => matchVehicleRequiredCertName(cert.certName, requiredName))
- const status = getVehicleCertBadgeStatus(matched, requiredName)
- const shortLabel = PL_VEHICLE_CERT_SHORT_LABELS[requiredName] || requiredName.slice(0, 1)
- return {
- requiredName,
- shortLabel,
- status,
- title: formatVehicleCertBadgeTooltip(partLabel, requiredName, status, matched)
- }
- })
- }
|