@@ -112,9 +112,10 @@ export default function AccessPage() {
const apply = ( next : Filters ) = > { setDraft ( next ) ; setCriteria ( next ) ; setOffset ( 0 ) ; setSelectedVIN ( '' ) ; syncURL ( next ) ; } ;
const apply = ( next : Filters ) = > { setDraft ( next ) ; setCriteria ( next ) ; setOffset ( 0 ) ; setSelectedVIN ( '' ) ; syncURL ( next ) ; } ;
const submit = ( event : FormEvent ) = > { event . preventDefault ( ) ; apply ( draft ) ; } ;
const submit = ( event : FormEvent ) = > { event . preventDefault ( ) ; apply ( draft ) ; } ;
const page = Math . floor ( offset / limit ) + 1 ; const totalPages = Math . max ( 1 , Math . ceil ( ( vehiclesQuery . data ? . total ? ? 0 ) / limit ) ) ; const summary = summaryQuery . data ;
const page = Math . floor ( offset / limit ) + 1 ; const totalPages = Math . max ( 1 , Math . ceil ( ( vehiclesQuery . data ? . total ? ? 0 ) / limit ) ) ; const summary = summaryQuery . data ;
const refresh = ( ) = > Promise . all ( [ summaryQuery . refetch ( ) , vehiclesQuery . refetch ( ) , unresolvedQuery . refetch ( ) , thresholdQuery . refetch ( ) ] ) ;
return < div className = "v2-access-page v2-access-page-v3" >
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 Promise . all ( [ summaryQuery . refetch ( ) , vehiclesQuery . refetch ( ) ] ) } > < IconRefresh / > 刷 新 < / button > < / div > < / header >
< 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 >
< form className = "v2-access-filter-v3" 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 . filter ( ( item ) = > item . name !== '未维护' ) . 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" 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 . filter ( ( item ) = > item . name !== '未维护' ) . 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 }
{ summaryQuery . isError ? < InlineError message = { summaryQuery . error instanceof Error ? summaryQuery . error . message : '接入汇总读取失败' } onRetry = { ( ) = > summaryQuery . refetch ( ) } / > : null }
< section className = "v2-access-kpis-v3" > { [
< section className = "v2-access-kpis-v3" > { [
@@ -122,7 +123,9 @@ export default function AccessPage() {
] . 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 >
] . 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 }
{ 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" > < 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 . expectedProtocols . length } 项 < / b > < span > { row . expectedProtocols . 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 >
< 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" > < 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 . expectedProtocols . length } 项 < / b > < span > { row . expectedProtocols . 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 >
{ unresolvedQuery . isError ? < InlineError message = { unresolvedQuery . error instanceof Error ? unresolvedQuery . error . message : '待绑定身份读取失败' } onRetry = { ( ) = > unresolvedQuery . refetch ( ) } / > : null }
< IdentityQueue items = { unresolvedQuery . data ? . items ? ? [ ] } total = { unresolvedQuery . data ? . total ? ? 0 } / >
< IdentityQueue items = { unresolvedQuery . data ? . items ? ? [ ] } total = { unresolvedQuery . data ? . total ? ? 0 } / >
{ thresholdQuery . isError ? < InlineError message = { thresholdQuery . error instanceof Error ? thresholdQuery . error . message : '接入阈值读取失败' } onRetry = { ( ) = > thresholdQuery . refetch ( ) } / > : null }
< ThresholdSettings config = { thresholdQuery . data } draft = { thresholdDraft } editable = { editable } saving = { updateThreshold . isPending } error = { updateThreshold . error instanceof Error ? updateThreshold.error.message : undefined } onChange = { setThresholdDraft } onSave = { ( ) = > thresholdDraft && updateThreshold . mutate ( thresholdDraft ) } / >
< ThresholdSettings config = { thresholdQuery . data } draft = { thresholdDraft } editable = { editable } saving = { updateThreshold . isPending } error = { updateThreshold . error instanceof Error ? updateThreshold.error.message : undefined } onChange = { setThresholdDraft } onSave = { ( ) = > thresholdDraft && updateThreshold . mutate ( thresholdDraft ) } / >
< / div > ;
< / div > ;
}
}