@@ -120,10 +120,10 @@ export default function AccessPage() {
return < div className = "v2-access-page v2-access-page-v3" >
< header className = "v2-access-heading" > < div > < h2 > 车 辆 接 入 管 理 < / h2 > < p > 只 展 示 车 辆 真 实 存 在 的 来 源 、 在 线 健 康 和 待 维 护 资 料 ; 未 接 业 务 系 统 前 不 推 断 应 接 协 议 < / p > < / div > < div > < span > 数 据 时 间 { summary ? . asOf ? formatAccessTime ( summary . asOf ) : '—' } < / span > < button type = "button" onClick = { ( ) = > void refresh ( ) } > < IconRefresh / > 刷 新 < / button > < / div > < / header >
< button type = "button" className = "v2-mobile-filter-toggle" aria-expanded = { ! filtersCollapsed } onClick = { ( ) = > setFiltersCollapsed ( ( value ) = > ! value ) } > < span > < b > 筛 选 条 件 < / b > < small > { Object . values ( criteria ) . filter ( Boolean ) . length ? ` 已启用 ${ Object . values ( criteria ) . filter ( Boolean ) . length } 项 ` : '全部主车辆' } < / small > < / span > < em > { filtersCollapsed ? '展开' : '收起' } < / em > < / button >
< form className = { ` v2-access-filter-v3 ${ filtersCollapsed ? ' is-mobile-collapsed' : '' } ` } onSubmit = { submit } > < label className = "is-search" > < span > 车 辆 < / span > < div > < IconSearch / > < input aria-label = "车辆" value = { draft . keyword } onChange = { ( event ) = > setDraft ( { . . . draft , keyword : event.target.value } ) } placeholder = "车牌 / VIN" / > < / div > < / label > < label > < span > 接 入 状 态 < / span > < select aria-label = "接入状态" value = { draft . connectionState } onChange = { ( event ) = > setDraft ( { . . . draft , connectionState : event.target.value } ) } > < option value = "" > 全 部 状 态 < / option > < option value = "attention" > 需 关 注 < / option > < option value = "healthy" > 已 接 来 源 正 常 < / option > < option value = "incomplete " > 资 料 待 维 护 < / option > < option value = "degraded" > 部 分 来 源 异 常 < / option > < option value = "offline" > 已 接 来 源 离 线 < / option > < option value = "not_connected" > 尚 无 来 源 < / option > < / select > < / label > < label > < span > 真 实 协 议 < / span > < select aria-label = "关注协议" value = { draft . protocol } onChange = { ( event ) = > setDraft ( { . . . draft , protocol : event.target.value } ) } > < option value = "" > 全 部 协 议 < / option > { PROTOCOLS . map ( ( item ) = > < option key = { item } > { item } < / option > ) } < / select > < / label > < label > < span > 车 辆 品 牌 < / span > < select aria-label = "车辆品牌" value = { draft . oem } onChange = { ( event ) = > setDraft ( { . . . draft , oem : event.target.value } ) } > < option value = "" > 全 部 品 牌 < / option > { summary ? . oems . map ( ( item ) = > < option key = { item . name } > { item . name } < / option > ) } < / select > < / label > < button className = "v2-primary-button" type = "submit" > 查 询 < / button > < button className = "v2-secondary-button" type = "button" onClick = { ( ) = > apply ( EMPTY_FILTERS ) } > 重 置 < / button > < / form >
< form className = { ` v2-access-filter-v3 ${ filtersCollapsed ? ' is-mobile-collapsed' : '' } ` } onSubmit = { submit } > < label className = "is-search" > < span > 车 辆 < / span > < div > < IconSearch / > < input aria-label = "车辆" value = { draft . keyword } onChange = { ( event ) = > setDraft ( { . . . draft , keyword : event.target.value } ) } placeholder = "车牌 / VIN" / > < / div > < / label > < label > < span > 接 入 状 态 < / span > < select aria-label = "接入状态" value = { draft . connectionState } onChange = { ( event ) = > setDraft ( { . . . draft , connectionState : event.target.value } ) } > < option value = "" > 全 部 状 态 < / option > < option value = "attention" > 需 关 注 < / option > < option value = "healthy" > 已 接 来 源 正 常 < / option > < option value = "master_data " > 资 料 待 维 护 < / option > < option value = "degraded" > 部 分 来 源 异 常 < / option > < option value = "offline" > 已 接 来 源 离 线 < / option > < option value = "not_connected" > 尚 无 来 源 < / option > < / select > < / label > < label > < span > 真 实 协 议 < / span > < select aria-label = "关注协议" value = { draft . protocol } onChange = { ( event ) = > setDraft ( { . . . draft , protocol : event.target.value } ) } > < option value = "" > 全 部 协 议 < / option > { PROTOCOLS . map ( ( item ) = > < option key = { item } > { item } < / option > ) } < / select > < / label > < label > < span > 车 辆 品 牌 < / span > < select aria-label = "车辆品牌" value = { draft . oem } onChange = { ( event ) = > setDraft ( { . . . draft , oem : event.target.value } ) } > < option value = "" > 全 部 品 牌 < / option > { summary ? . oems . map ( ( item ) = > < option key = { item . name } > { item . name } < / option > ) } < / select > < / label > < button className = "v2-primary-button" type = "submit" > 查 询 < / button > < button className = "v2-secondary-button" type = "button" onClick = { ( ) = > apply ( EMPTY_FILTERS ) } > 重 置 < / button > < / form >
{ summaryQuery . isError ? < InlineError message = { summaryQuery . error instanceof Error ? summaryQuery . error . message : '接入汇总读取失败' } onRetry = { ( ) = > summaryQuery . refetch ( ) } / > : null }
< section className = "v2-access-kpis-v3" > { [
[ '主车辆' , summary ? . totalVehicles ? ? 0 , 'all' , '' ] , [ '需关注' , Math . max ( 0 , ( summary ? . totalVehicles ? ? 0 ) - ( summary ? . healthyVehicles ? ? 0 ) ) , 'attention' , 'attention' ] , [ '资料待维护' , summary ? . i ncompleteVehicles ? ? 0 , 'incomplete' , 'incomplete ' ] , [ '尚无来源' , summary ? . neverReported ? ? 0 , 'never' , 'not_connected' ]
[ '主车辆' , summary ? . totalVehicles ? ? 0 , 'all' , '' ] , [ '需关注' , Math . max ( 0 , ( summary ? . totalVehicles ? ? 0 ) - ( summary ? . healthyVehicles ? ? 0 ) ) , 'attention' , 'attention' ] , [ '资料待维护' , summary ? . masterDataI ncompleteVehicles ? ? 0 , 'incomplete' , 'master_data ' ] , [ '尚无来源' , summary ? . neverReported ? ? 0 , 'never' , 'not_connected' ]
] . map ( ( [ label , value , tone , connectionState ] ) = > < button key = { String ( label ) } className = { ` is- ${ tone } ` } type = "button" onClick = { ( ) = > apply ( { . . . criteria , connectionState : String ( connectionState ) } ) } > < small > { label } < / small > < strong > { Number ( value ) . toLocaleString ( 'zh-CN' ) } < / strong > { label === '主车辆' ? < em > 车 辆 主 档 < / em > : null } < / button > ) } < / section >
{ vehiclesQuery . isError ? < InlineError message = { vehiclesQuery . error instanceof Error ? vehiclesQuery . error . message : '接入车辆读取失败' } onRetry = { ( ) = > vehiclesQuery . refetch ( ) } / > : null }
< div className = { ` v2-access-workspace-v3 ${ selected ? 'is-inspector-open' : '' } ` } > < section className = "v2-access-table-v3" > < header > < div className = "v2-access-table-title" > < strong > 车 辆 真 实 接 入 来 源 < / strong > < span > 缺 席 协 议 只 表 示 “ 当 前 未 发 现 ” , 不 会 被 推 断 成 应 接 缺 失 ; 时 间 为 各 来 源 最 后 接 收 时 间 < / span > < / div > < div className = "v2-access-table-actions" > < ProtocolCoverage summary = { summary } / > < button type = "button" onClick = { ( ) = > downloadRows ( rows ) } disabled = { ! rows . length } > < IconDownload / > 导 出 当 前 页 < / button > < / div > < / header > < div className = "v2-access-table-scroll-v3" > { mobileLayout ? < div className = "v2-access-mobile-list" > { rows . map ( ( row ) = > < button type = "button" key = { row . vin } className = { selected ? . vin === row . vin ? 'is-selected' : '' } onClick = { ( ) = > setSelectedVIN ( row . vin ) } > < header > < div > < strong > { row . plate || '未绑定车牌' } < / strong > < span > { row . vin } < / span > < / div > < ConnectionState row = { row } / > < / header > < p > { row . oem || '品牌未维护' } · { row . model || row . company || '车型未维护' } < / p > < div > { PROTOCOLS . map ( ( protocol ) = > < ProtocolState key = { protocol } status = { statusByProtocol ( row , protocol ) } / > ) } < / div > < / button > ) } < / div > : < table > < thead > < tr > < th > 车 辆 < / th > < th > 品 牌 / 车 型 < / th > < th > 真 实 来 源 < / th > { PROTOCOLS . map ( ( item ) = > < th key = { item } > { item } < / th > ) } < th > 综 合 状 态 < / th > < / tr > < / thead > < tbody > { rows . map ( ( row ) = > < tr key = { row . vin } data-testid = { ` access-row- ${ row . vin } ` } tabIndex = { 0 } className = { selected ? . vin === row . vin ? 'is-selected' : '' } onClick = { ( ) = > setSelectedVIN ( row . vin ) } onKeyDown = { ( event ) = > { if ( event . key === 'Enter' || event . key === ' ' ) setSelectedVIN ( row . vin ) ; } } > < td > < strong > { row . plate || '未绑定车牌' } < / strong > < span > { row . vin } < / span > < / td > < td > < strong > { row . oem || '品牌未维护' } < / strong > < span > { row . model || row . company || '车型未维护' } < / span > < / td > < td > < b > { row . actualProtocols . length } 个 < / b > < span > { row . actualProtocols . join ( ' / ' ) || '尚无来源' } < / span > < / td > { PROTOCOLS . map ( ( protocol ) = > < td key = { protocol } > < ProtocolState status = { statusByProtocol ( row , protocol ) } / > < / td > ) } < td > < ConnectionState row = { row } / > < / td > < / tr > ) } < / tbody > < / table > } { vehiclesQuery . isFetching ? < div className = "v2-access-loading" > < i / > 正 在 更 新 车 辆 接 入 状 态 … < / div > : null } { ! vehiclesQuery . isFetching && ! rows . length ? < div className = "v2-access-empty" > 当 前 筛 选 条 件 没 有 车 辆 < / div > : null } < / div > < footer > < span > 第 { page } / { totalPages } 页 , 共 { ( vehiclesQuery . data ? . total ? ? 0 ) . toLocaleString ( 'zh-CN' ) } 辆 主 车 辆 < / span > < div > < button type = "button" disabled = { page <= 1 } onClick = { ( ) = > setOffset ( Math . max ( 0 , offset - limit ) ) } > 上 一 页 < / button > < button type = "button" disabled = { page >= totalPages } onClick = { ( ) = > setOffset ( offset + limit ) } > 下 一 页 < / button > < select aria-label = "每页数量" value = { limit } onChange = { ( event ) = > { setLimit ( Number ( event . target . value ) ) ; setOffset ( 0 ) ; } } > < option value = "20" > 20 辆 / 页 < / option > < option value = "50" > 50 辆 / 页 < / option > < option value = "100" > 100 辆 / 页 < / option > < / select > < / div > < / footer > < / section > { selected ? < VehicleInspector row = { selected } onClose = { ( ) = > setSelectedVIN ( '' ) } / > : null } < / div >