@@ -70,11 +70,11 @@ type ColumnKey =
7070type ColumnSetting = {
7171 key : ColumnKey ;
7272 visible : boolean ;
73- width : number ;
73+ width : number | null ;
7474} ;
7575
7676const PAGE_SIZE = 60 ;
77- const COLUMN_SETTINGS_STORAGE_KEY = "records-column-settings-v1 " ;
77+ const COLUMN_SETTINGS_STORAGE_KEY = "records-column-settings-v2 " ;
7878const DEFAULT_COLUMN_ORDER : ColumnKey [ ] = [
7979 "occurredAt" ,
8080 "model" ,
@@ -106,23 +106,37 @@ const COLUMN_LABELS: Record<ColumnKey, string> = {
106106} ;
107107
108108const DEFAULT_COLUMN_WIDTHS : Record < ColumnKey , number > = {
109- occurredAt : 160 ,
110- model : 240 ,
111- route : 210 ,
112- credentialName : 225 ,
113- provider : 150 ,
114- totalTokens : 130 ,
115- inputTokens : 110 ,
116- outputTokens : 110 ,
117- reasoningTokens : 110 ,
118- cachedTokens : 110 ,
119- cost : 130 ,
120- isError : 100
109+ occurredAt : 140 ,
110+ model : 210 ,
111+ route : 180 ,
112+ credentialName : 180 ,
113+ provider : 130 ,
114+ totalTokens : 115 ,
115+ inputTokens : 95 ,
116+ outputTokens : 95 ,
117+ reasoningTokens : 95 ,
118+ cachedTokens : 95 ,
119+ cost : 110 ,
120+ isError : 90
121121} ;
122122
123+ const FIXED_WIDTH_COLUMNS = new Set < ColumnKey > ( [ "model" , "route" , "credentialName" ] ) ;
124+
123125const COLUMN_MIN_WIDTH = 80 ;
124126const COLUMN_MAX_WIDTH = 420 ;
125127
128+ const NON_FIXED_CONTENT_MIN_WIDTHS : Record < Exclude < ColumnKey , "model" | "route" | "credentialName" > , number > = {
129+ occurredAt : 150 ,
130+ provider : 120 ,
131+ totalTokens : 120 ,
132+ inputTokens : 96 ,
133+ outputTokens : 96 ,
134+ reasoningTokens : 96 ,
135+ cachedTokens : 96 ,
136+ cost : 105 ,
137+ isError : 88
138+ } ;
139+
126140const SORT_FIELD_BY_COLUMN : Partial < Record < ColumnKey , SortField > > = {
127141 occurredAt : "occurredAt" ,
128142 model : "model" ,
@@ -139,7 +153,11 @@ const SORT_FIELD_BY_COLUMN: Partial<Record<ColumnKey, SortField>> = {
139153
140154function normalizeColumnSettings ( raw : unknown ) : ColumnSetting [ ] {
141155 if ( ! Array . isArray ( raw ) ) {
142- return DEFAULT_COLUMN_ORDER . map ( ( key ) => ( { key, visible : true , width : DEFAULT_COLUMN_WIDTHS [ key ] } ) ) ;
156+ return DEFAULT_COLUMN_ORDER . map ( ( key ) => ( {
157+ key,
158+ visible : true ,
159+ width : FIXED_WIDTH_COLUMNS . has ( key ) ? DEFAULT_COLUMN_WIDTHS [ key ] : null
160+ } ) ) ;
143161 }
144162
145163 const seen = new Set < ColumnKey > ( ) ;
@@ -155,7 +173,9 @@ function normalizeColumnSettings(raw: unknown): ColumnSetting[] {
155173 const parsedWidth = Number ( item ?. width ) ;
156174 const width = Number . isFinite ( parsedWidth )
157175 ? Math . min ( COLUMN_MAX_WIDTH , Math . max ( COLUMN_MIN_WIDTH , Math . round ( parsedWidth ) ) )
158- : DEFAULT_COLUMN_WIDTHS [ key as ColumnKey ] ;
176+ : FIXED_WIDTH_COLUMNS . has ( key as ColumnKey )
177+ ? DEFAULT_COLUMN_WIDTHS [ key as ColumnKey ]
178+ : null ;
159179 ordered . push ( {
160180 key : key as ColumnKey ,
161181 visible : item ?. visible !== false ,
@@ -165,7 +185,7 @@ function normalizeColumnSettings(raw: unknown): ColumnSetting[] {
165185
166186 for ( const key of DEFAULT_COLUMN_ORDER ) {
167187 if ( ! seen . has ( key ) ) {
168- ordered . push ( { key, visible : true , width : DEFAULT_COLUMN_WIDTHS [ key ] } ) ;
188+ ordered . push ( { key, visible : true , width : FIXED_WIDTH_COLUMNS . has ( key ) ? DEFAULT_COLUMN_WIDTHS [ key ] : null } ) ;
169189 }
170190 }
171191
@@ -290,11 +310,17 @@ export default function RecordsPage() {
290310 const [ sortField , setSortField ] = useState < SortField > ( "occurredAt" ) ;
291311 const [ sortOrder , setSortOrder ] = useState < SortOrder > ( "desc" ) ;
292312 const [ columnSettings , setColumnSettings ] = useState < ColumnSetting [ ] > (
293- DEFAULT_COLUMN_ORDER . map ( ( key ) => ( { key, visible : true , width : DEFAULT_COLUMN_WIDTHS [ key ] } ) )
313+ DEFAULT_COLUMN_ORDER . map ( ( key ) => ( {
314+ key,
315+ visible : true ,
316+ width : FIXED_WIDTH_COLUMNS . has ( key ) ? DEFAULT_COLUMN_WIDTHS [ key ] : null
317+ } ) )
294318 ) ;
295319 const [ columnSettingsReady , setColumnSettingsReady ] = useState ( false ) ;
296320 const [ columnPanelOpen , setColumnPanelOpen ] = useState ( false ) ;
321+ const [ tableContainerWidth , setTableContainerWidth ] = useState ( 0 ) ;
297322 const columnPanelRef = useRef < HTMLDivElement | null > ( null ) ;
323+ const tableWrapperRef = useRef < HTMLDivElement | null > ( null ) ;
298324 const resizingColumnRef = useRef < { key : ColumnKey ; startX : number ; startWidth : number } | null > ( null ) ;
299325 const draggingColumnRef = useRef < ColumnKey | null > ( null ) ;
300326 const [ dragIndicator , setDragIndicator ] = useState < { key : ColumnKey ; position : "before" | "after" } | null > ( null ) ;
@@ -315,7 +341,9 @@ export default function RecordsPage() {
315341 setColumnSettings ( ( prev ) => {
316342 const next = prev . map ( ( item ) => ( item . key === key ? { ...item , visible : ! item . visible } : item ) ) ;
317343 if ( next . filter ( ( item ) => item . visible ) . length === 0 ) return prev ;
318- return next ;
344+
345+ // 每次列显隐变更后,非关键列重置为 auto,触发一次自适应宽度。
346+ return next . map ( ( item ) => ( FIXED_WIDTH_COLUMNS . has ( item . key ) ? item : { ...item , width : null } ) ) ;
319347 } ) ;
320348 } , [ ] ) ;
321349
@@ -370,22 +398,84 @@ export default function RecordsPage() {
370398 setColumnSettings ( ( prev ) => prev . map ( ( item ) => ( item . key === key ? { ...item , width : nextWidth } : item ) ) ) ;
371399 } , [ ] ) ;
372400
373- const getColumnWidth = useCallback (
374- ( key : ColumnKey ) => columnSettingMap . get ( key ) ?. width ?? DEFAULT_COLUMN_WIDTHS [ key ] ,
401+ const getBaseColumnWidth = useCallback (
402+ ( key : ColumnKey ) => {
403+ const width = columnSettingMap . get ( key ) ?. width ;
404+ if ( width !== undefined && width !== null ) return width ;
405+ return DEFAULT_COLUMN_WIDTHS [ key ] ;
406+ } ,
375407 [ columnSettingMap ]
376408 ) ;
377409
410+ const adaptiveWidthMap = useMemo ( ( ) => {
411+ const result = new Map < ColumnKey , number > ( ) ;
412+ if ( visibleColumns . length === 0 ) return result ;
413+
414+ const fixedColumns = visibleColumns . filter ( ( key ) => FIXED_WIDTH_COLUMNS . has ( key ) ) ;
415+ const nonFixedColumns = visibleColumns . filter ( ( key ) => ! FIXED_WIDTH_COLUMNS . has ( key ) ) ;
416+
417+ let usedWidth = 0 ;
418+
419+ for ( const key of fixedColumns ) {
420+ const width = getBaseColumnWidth ( key ) ;
421+ result . set ( key , width ) ;
422+ usedWidth += width ;
423+ }
424+
425+ const manualNonFixed : ColumnKey [ ] = [ ] ;
426+ const autoNonFixed : Array < Exclude < ColumnKey , "model" | "route" | "credentialName" > > = [ ] ;
427+
428+ for ( const key of nonFixedColumns ) {
429+ const setting = columnSettingMap . get ( key ) ;
430+ if ( setting ?. width !== null && setting ?. width !== undefined ) {
431+ manualNonFixed . push ( key ) ;
432+ } else {
433+ autoNonFixed . push ( key as Exclude < ColumnKey , "model" | "route" | "credentialName" > ) ;
434+ }
435+ }
436+
437+ for ( const key of manualNonFixed ) {
438+ const minRequired = NON_FIXED_CONTENT_MIN_WIDTHS [ key as Exclude < ColumnKey , "model" | "route" | "credentialName" > ] ;
439+ const width = Math . max ( getBaseColumnWidth ( key ) , minRequired ) ;
440+ result . set ( key , width ) ;
441+ usedWidth += width ;
442+ }
443+
444+ if ( autoNonFixed . length === 0 ) {
445+ return result ;
446+ }
447+
448+ const available = tableContainerWidth > 0 ? tableContainerWidth - 8 : 0 ;
449+ const minTotal = autoNonFixed . reduce ( ( sum , key ) => sum + NON_FIXED_CONTENT_MIN_WIDTHS [ key ] , 0 ) ;
450+ const remaining = Math . max ( 0 , available - usedWidth ) ;
451+
452+ if ( remaining <= minTotal || available <= 0 ) {
453+ for ( const key of autoNonFixed ) {
454+ result . set ( key , NON_FIXED_CONTENT_MIN_WIDTHS [ key ] ) ;
455+ }
456+ return result ;
457+ }
458+
459+ const extraPerColumn = Math . floor ( ( remaining - minTotal ) / autoNonFixed . length ) ;
460+ for ( const key of autoNonFixed ) {
461+ result . set ( key , NON_FIXED_CONTENT_MIN_WIDTHS [ key ] + extraPerColumn ) ;
462+ }
463+
464+ return result ;
465+ } , [ visibleColumns , getBaseColumnWidth , columnSettingMap , tableContainerWidth ] ) ;
466+
378467 const beginResizeColumn = useCallback (
379468 ( key : ColumnKey , event : ReactMouseEvent < HTMLDivElement > ) => {
380469 event . preventDefault ( ) ;
381470 event . stopPropagation ( ) ;
382471 const current = columnSettingMap . get ( key ) ;
383472 if ( ! current ) return ;
473+ const currentRenderedWidth = Math . round ( event . currentTarget . parentElement ?. getBoundingClientRect ( ) . width ?? DEFAULT_COLUMN_WIDTHS [ key ] ) ;
384474
385475 resizingColumnRef . current = {
386476 key,
387477 startX : event . clientX ,
388- startWidth : current . width
478+ startWidth : currentRenderedWidth
389479 } ;
390480
391481 const onMouseMove = ( moveEvent : MouseEvent ) => {
@@ -793,7 +883,13 @@ export default function RecordsPage() {
793883 setColumnSettings ( normalizeColumnSettings ( parsed ) ) ;
794884 }
795885 } catch {
796- setColumnSettings ( DEFAULT_COLUMN_ORDER . map ( ( key ) => ( { key, visible : true , width : DEFAULT_COLUMN_WIDTHS [ key ] } ) ) ) ;
886+ setColumnSettings (
887+ DEFAULT_COLUMN_ORDER . map ( ( key ) => ( {
888+ key,
889+ visible : true ,
890+ width : FIXED_WIDTH_COLUMNS . has ( key ) ? DEFAULT_COLUMN_WIDTHS [ key ] : null
891+ } ) )
892+ ) ;
797893 } finally {
798894 setColumnSettingsReady ( true ) ;
799895 }
@@ -816,6 +912,23 @@ export default function RecordsPage() {
816912 return ( ) => document . removeEventListener ( "mousedown" , onDocClick ) ;
817913 } , [ columnPanelOpen ] ) ;
818914
915+ useEffect ( ( ) => {
916+ const element = tableWrapperRef . current ;
917+ if ( ! element ) return ;
918+
919+ const updateWidth = ( ) => {
920+ setTableContainerWidth ( element . clientWidth ) ;
921+ } ;
922+
923+ updateWidth ( ) ;
924+
925+ if ( typeof ResizeObserver === "undefined" ) return ;
926+
927+ const observer = new ResizeObserver ( ( ) => updateWidth ( ) ) ;
928+ observer . observe ( element ) ;
929+ return ( ) => observer . disconnect ( ) ;
930+ } , [ ] ) ;
931+
819932 return (
820933 < main className = "min-h-screen bg-slate-900 px-6 py-8 text-slate-100" >
821934 < header className = "flex flex-col gap-3 md:flex-row md:items-center md:justify-between" >
@@ -1038,17 +1151,13 @@ export default function RecordsPage() {
10381151 onDragOver = { ( event ) => onColumnDragOver ( column . key , event ) }
10391152 onDrop = { ( event ) => onColumnDrop ( column . key , event ) }
10401153 onDragEnd = { onColumnDragEnd }
1041- className = { `flex items-center justify-between rounded-lg border px-2 py-1.5 ${
1042- dragIndicator ?. key === column . key
1043- ? "border-indigo-400/70 bg-indigo-500/10"
1044- : "border-slate-800 bg-slate-950/60"
1045- } relative`}
1154+ className = "relative flex items-center justify-between rounded-lg border border-slate-800 bg-slate-950/60 px-2 py-1.5"
10461155 >
10471156 { dragIndicator ?. key === column . key && dragIndicator . position === "before" ? (
1048- < span className = "pointer-events-none absolute left-2 right-2 - top-1.5 h-px bg-indigo-400 " />
1157+ < span className = "pointer-events-none absolute left-2 right-2 top-0 h-px -translate-y-1/2 bg-indigo-300/90 shadow-[0_0_6px_rgba(129,140,248,0.45)] " />
10491158 ) : null }
10501159 { dragIndicator ?. key === column . key && dragIndicator . position === "after" ? (
1051- < span className = "pointer-events-none absolute left-2 right-2 - bottom-1.5 h-px bg-indigo-400 " />
1160+ < span className = "pointer-events-none absolute left-2 right-2 bottom-0 h-px translate-y-1/2 bg-indigo-300/90 shadow-[0_0_6px_rgba(129,140,248,0.45)] " />
10521161 ) : null }
10531162 < label className = "inline-flex items-center gap-2 text-sm text-slate-200" >
10541163 < span className = "cursor-grab text-slate-500 active:cursor-grabbing" title = "按住拖拽排序" >
@@ -1062,7 +1171,7 @@ export default function RecordsPage() {
10621171 className = "h-4 w-4 rounded border-slate-600 bg-slate-800 text-indigo-500"
10631172 />
10641173 < span > { COLUMN_LABELS [ column . key ] } </ span >
1065- < span className = "text-xs text-slate-500" > { column . width } px</ span >
1174+ < span className = "text-xs text-slate-500" > { column . width ? ` ${ column . width } px` : "auto" } </ span >
10661175 </ label >
10671176 </ div >
10681177 ) ) }
@@ -1076,27 +1185,28 @@ export default function RecordsPage() {
10761185
10771186 < section className = { `mt-5 rounded-2xl bg-slate-800/40 p-4 shadow-sm ring-1 ring-slate-700 ${ loadingEmpty ? "min-h-[100vh]" : "" } ` } >
10781187 { ! loadingEmpty ? (
1079- < div className = "overflow-auto" >
1080- < table className = "min-w-[1460px] w-[99%] mx-auto table-fixed border-separate border-spacing-y-2" >
1188+ < div ref = { tableWrapperRef } className = "overflow-auto" >
1189+ < table className = "min-w-full w-full table-fixed border-separate border-spacing-y-2" >
10811190 < thead className = "sticky top-0 z-10" >
10821191 < tr className = "text-left text-[13px] uppercase tracking-wide text-slate-400" >
10831192 { visibleColumns . map ( ( columnKey ) => {
1084- const width = getColumnWidth ( columnKey ) ;
1193+ const width = adaptiveWidthMap . get ( columnKey ) ?? DEFAULT_COLUMN_WIDTHS [ columnKey ] ;
10851194 return (
10861195 < th
10871196 key = { columnKey }
1088- className = "relative px-3 py-2"
1089- style = { { width : `${ width } px` , minWidth : `${ width } px` } }
1197+ className = "group/col relative px-3 py-2"
1198+ style = { width ? { width : `${ width } px` , minWidth : `${ width } px` } : undefined }
10901199 >
10911200 { renderHeaderByColumn ( columnKey ) }
10921201 < div
10931202 role = "separator"
10941203 aria-orientation = "vertical"
10951204 onMouseDown = { ( event ) => beginResizeColumn ( columnKey , event ) }
1096- className = "group absolute right-0 top-0 h-full w-2 cursor-col-resize select-none"
1205+ className = "group absolute right-0 top-0 h-full w-3 cursor-col-resize select-none opacity-0 pointer-events-none transition-opacity duration-250 group-hover/col:opacity-100 group-hover/col:pointer-events-auto "
10971206 title = "拖拽调整列宽"
10981207 >
1099- < span className = "absolute right-[3px] top-1 bottom-1 w-px rounded bg-slate-500/70 transition-colors group-hover:bg-indigo-400/80" />
1208+ < span className = "absolute right-[4px] top-2 bottom-2 w-px rounded bg-gradient-to-b from-transparent via-slate-400/35 to-transparent opacity-70 transition-all duration-150 group-hover:via-indigo-300/60 group-hover:opacity-100" />
1209+ < span className = "pointer-events-none absolute right-[3px] top-1/2 h-3 w-[3px] -translate-y-1/2 rounded-full bg-slate-400/35 transition-colors duration-150 group-hover:bg-indigo-300/65" />
11001210 </ div >
11011211 </ th >
11021212 ) ;
@@ -1107,15 +1217,25 @@ export default function RecordsPage() {
11071217 { records . map ( ( row ) => (
11081218 < tr
11091219 key = { row . id }
1110- className = "rounded-lg bg-slate-900/70 text-slate-100 shadow-sm ring-1 ring-slate-800 transition hover:ring-1.5 hover:ring-indigo-400/40 hover: shadow-[0_0_24px_rgba(99,102,241,0.18)] h-13 "
1220+ className = "group h-13 rounded-lg bg-slate-900/70 text-slate-100 shadow-sm ring-1 ring-slate-800 transition hover:shadow-[0_0_24px_rgba(99,102,241,0.18)]"
11111221 >
1112- { visibleColumns . map ( ( columnKey ) => {
1113- const width = getColumnWidth ( columnKey ) ;
1222+ { visibleColumns . map ( ( columnKey , index ) => {
1223+ const width = adaptiveWidthMap . get ( columnKey ) ?? DEFAULT_COLUMN_WIDTHS [ columnKey ] ;
1224+ const isFirst = index === 0 ;
1225+ const isLast = index === visibleColumns . length - 1 ;
11141226 return (
11151227 < td
11161228 key = { `${ row . id } -${ columnKey } ` }
1117- className = "px-3 py-3 whitespace-nowrap"
1118- style = { { width : `${ width } px` , minWidth : `${ width } px` } }
1229+ className = { `whitespace-nowrap border-y border-transparent px-3 py-3 transition group-hover:border-indigo-400/40 ${
1230+ isFirst
1231+ ? "rounded-l-lg border-l border-l-transparent group-hover:border-l-indigo-400/40 group-hover:shadow-[-10px_0_16px_-10px_rgba(99,102,241,0.48)]"
1232+ : ""
1233+ } ${
1234+ isLast
1235+ ? "rounded-r-lg border-r border-r-transparent group-hover:border-r-indigo-400/40 group-hover:shadow-[10px_0_16px_-10px_rgba(99,102,241,0.48)]"
1236+ : ""
1237+ } `}
1238+ style = { width ? { width : `${ width } px` , minWidth : `${ width } px` } : undefined }
11191239 >
11201240 { renderCellByColumn ( columnKey , row ) }
11211241 </ td >
0 commit comments