1515 </div >
1616
1717 <div class =" panel-block-list" >
18- <div class =" list-header" :style = " gridStyle " >
18+ <div class =" list-header" >
1919 <span class =" header-cell" >名称/账号</span >
20- <div
21- class =" col-resize-handle"
22- @mousedown =" startColResize(0, $event)"
23- ></div >
2420 <span class =" header-cell" >发言</span >
25- <div
26- class =" col-resize-handle"
27- @mousedown =" startColResize(1, $event)"
28- ></div >
2921 <span class =" header-cell" >身份</span >
30- <div
31- class =" col-resize-handle"
32- @mousedown =" startColResize(2, $event)"
33- ></div >
3422 <span class =" header-cell" >染色</span >
3523 </div >
3624
3725 <div
3826 v-for =" item in identityList"
3927 :key =" item.id"
4028 class =" identity-item"
41- :style =" gridStyle"
4229 >
4330 <div class =" col-name" @dblclick =" startEdit(item.id)" >
4431 <input
5340 <span class =" text" >{{ item.id }}</span >
5441 </div >
5542 </div >
56- <div class =" col-divider" ></div >
5743 <div class =" col-count" >
5844 <span class =" count" >{{ item.msgCount }}</span >
5945 </div >
60- <div class =" col-divider" ></div >
6146
6247 <div class =" col-role" >
6348 <select
7661 <option value =" unknown" >其他</option >
7762 </select >
7863 </div >
79- <div class =" col-divider" ></div >
8064
8165 <div class =" col-color" >
8266 <div
10690</template >
10791
10892<script setup lang="ts">
109- import { ref , reactive , computed , watch } from ' vue' ;
93+ import { ref , computed } from ' vue' ;
11094import { IdCard , UserRound } from ' @lucide/vue' ;
11195import { useStyleStore } from ' @/stores/styleStore' ;
11296import { useLogStore } from ' @/stores/logStore' ;
113- import { useUiStore , PANEL_MAX_WIDTH } from ' @/stores/uiStore' ;
11497import { useHistoryStore } from ' @/stores/historyStore' ;
11598import { useLogEditorStore } from ' @/stores/editorStore' ;
11699import type { RoleType } from ' @/types/log' ;
@@ -132,7 +115,6 @@ interface IdentityListItem {
132115
133116const styleStore = useStyleStore ();
134117const logStore = useLogStore ();
135- const uiStore = useUiStore ();
136118const historyStore = useHistoryStore ();
137119const logEditorStore = useLogEditorStore ();
138120const windowStore = useWindowStore ();
@@ -251,198 +233,20 @@ function getRoleFromSelectEvent(event: Event): RoleType {
251233const vFocus = {
252234 mounted : (el : HTMLElement ) => el .focus (),
253235};
254-
255- // --- 列宽拖拽 ---
256- // 4 列总宽始终 = 面板可用宽度,拖手柄是相邻两列此消彼长
257- // Grid 7 格: 名称 | handle | 发言 | handle | 身份 | handle | 染色
258-
259- const MIN_COL_WIDTH = 30 ;
260- const HANDLE_COUNT = 3 ;
261- const HANDLE_WIDTH = 4 ; // 与 CSS .col-resize-handle / .col-divider 的 width 一致
262- const ROW_PADDING_X = 8 ; // 与 CSS .list-header / .identity-item 的 padding 水平值一致
263- const GRID_OVERHEAD = HANDLE_COUNT * HANDLE_WIDTH + ROW_PADDING_X * 2 ;
264-
265- // 身份列的最小宽度:需容纳最宽选项 "主持人"(3 CJK + select padding/arrow)
266- const MIN_ROLE_WIDTH = 65 ;
267- // 染色列固定宽度(色块 20px + 余量)
268- const COLOR_COL_WIDTH = 36 ;
269-
270- /** 面板可用于 4 列的净宽度 */
271- function availableColSpace(): number {
272- return uiStore .leftPanelWidth - GRID_OVERHEAD ;
273- }
274-
275- /** 列总宽 */
276- function totalColWidth(): number {
277- return colWidths .reduce ((s , w ) => s + w , 0 );
278- }
279-
280- /** 列总宽 + 开销 = 面板所需最小宽度 */
281- function requiredPanelWidth(): number {
282- return totalColWidth () + GRID_OVERHEAD ;
283- }
284-
285- /** 用隐藏 canvas 测量文本像素宽度 */
286- const measureCanvas = document .createElement (' canvas' );
287- function measureText(text : string , font : string ): number {
288- const ctx = measureCanvas .getContext (' 2d' )! ;
289- ctx .font = font ;
290- return Math .ceil (ctx .measureText (text ).width );
291- }
292-
293- /** 根据当前数据计算各列理想宽度 */
294- function calcIdealColWidths(
295- items : IdentityListItem [],
296- ): [number , number , number , number ] {
297- if (items .length === 0 ) {
298- return [80 , 40 , MIN_ROLE_WIDTH , COLOR_COL_WIDTH ];
299- }
300-
301- // 名称列:最长名称文字宽度 + 余量
302- const nameFont = ' 13px sans-serif' ;
303- const maxNameW = items .reduce (
304- (max , item ) => Math .max (max , measureText (item .id , nameFont )),
305- 0 ,
306- );
307- const nameCol = Math .max (MIN_COL_WIDTH , maxNameW + 8 );
308-
309- // 发言列:最大数字宽度 + badge padding
310- const countFont = ' 10px sans-serif' ;
311- const maxCountW = items .reduce (
312- (max , item ) =>
313- Math .max (max , measureText (String (item .msgCount ), countFont )),
314- 0 ,
315- );
316- const countCol = Math .max (MIN_COL_WIDTH , maxCountW + 16 ); // badge padding 2×6 + 余量
317-
318- // 身份列:固定最小宽度(select 下拉框需要容纳最宽选项)
319- const roleCol = MIN_ROLE_WIDTH ;
320-
321- return [nameCol , countCol , roleCol , COLOR_COL_WIDTH ];
322- }
323-
324- // 初始化列宽
325- const colWidths = reactive ([80 , 40 , MIN_ROLE_WIDTH , COLOR_COL_WIDTH ]);
326-
327- // 数据变化时自动调整列宽和侧边栏
328- watch (
329- identityList ,
330- (items ) => {
331- const [nameW, countW, roleW, colorW] = calcIdealColWidths (items );
332- const idealTotal = nameW + countW + roleW + colorW ;
333- const available = availableColSpace ();
334-
335- if (idealTotal <= available ) {
336- // 空间足够:各列用理想宽度,名称列吃掉剩余
337- colWidths [1 ] = countW ;
338- colWidths [2 ] = roleW ;
339- colWidths [3 ] = colorW ;
340- colWidths [0 ] = available - countW - roleW - colorW ;
341- } else {
342- // 空间不够:优先保持当前用户设定的侧边栏宽度
343- colWidths [1 ] = countW ;
344- colWidths [2 ] = roleW ;
345- colWidths [3 ] = colorW ;
346- const remainingForName = available - (countW + roleW + colorW );
347-
348- if (remainingForName >= MIN_COL_WIDTH ) {
349- // 只要还能满足最小宽度,就直接在内部压缩名称列,不推侧边栏
350- colWidths [0 ] = remainingForName ;
351- } else {
352- colWidths [0 ] = MIN_COL_WIDTH ;
353- const minimalNeeded =
354- MIN_COL_WIDTH + countW + roleW + colorW + GRID_OVERHEAD ;
355- if (uiStore .leftPanelWidth < minimalNeeded ) {
356- uiStore .leftPanelWidth = Math .min (
357- PANEL_MAX_WIDTH ,
358- minimalNeeded ,
359- );
360- }
361- }
362- }
363- },
364- { immediate: true },
365- );
366-
367- const gridStyle = computed (() => ({
368- gridTemplateColumns: colWidths
369- .map ((w ) => ` ${w }px ` )
370- .join (` ${HANDLE_WIDTH }px ` ),
371- }));
372-
373- /** 拖拽列手柄:左列和右列此消彼长,触底后推宽侧边栏 */
374- function startColResize(colIndex : number , e : MouseEvent ) {
375- e .preventDefault ();
376- const startX = e .clientX ;
377- const startLeft = colWidths [colIndex ];
378- const startRight = colWidths [colIndex + 1 ];
379- const startPanelWidth = uiStore .leftPanelWidth ;
380-
381- // 身份列(index 2)有更高的最小宽度
382- const minLeft = MIN_COL_WIDTH ;
383- const minRight = colIndex + 1 === 2 ? MIN_ROLE_WIDTH : MIN_COL_WIDTH ;
384-
385- function onMouseMove(ev : MouseEvent ) {
386- const delta = ev .clientX - startX ;
387- const newLeft = Math .max (minLeft , startLeft + delta );
388- const newRight = startRight - delta ;
389-
390- if (newRight >= minRight ) {
391- colWidths [colIndex ] = newLeft ;
392- colWidths [colIndex + 1 ] = newRight ;
393- } else {
394- // 右列触底,推宽侧边栏
395- colWidths [colIndex ] = newLeft ;
396- colWidths [colIndex + 1 ] = minRight ;
397- const needed = requiredPanelWidth ();
398- uiStore .leftPanelWidth = Math .min (
399- PANEL_MAX_WIDTH ,
400- Math .max (startPanelWidth , needed ),
401- );
402- }
403-
404- // 反向拖时缩回侧边栏
405- if (delta < 0 && uiStore .leftPanelWidth > startPanelWidth ) {
406- const needed = requiredPanelWidth ();
407- if (needed < uiStore .leftPanelWidth ) {
408- uiStore .leftPanelWidth = Math .max (startPanelWidth , needed );
409- }
410- }
411- }
412-
413- function onMouseUp() {
414- document .removeEventListener (' mousemove' , onMouseMove );
415- document .removeEventListener (' mouseup' , onMouseUp );
416- document .body .style .cursor = ' ' ;
417- document .body .style .userSelect = ' ' ;
418- }
419-
420- document .body .style .cursor = ' col-resize' ;
421- document .body .style .userSelect = ' none' ;
422- document .addEventListener (' mousemove' , onMouseMove );
423- document .addEventListener (' mouseup' , onMouseUp );
424- }
425-
426- // 侧边栏宽度变化时,名称列(第一列)吸收差值
427- watch (
428- () => uiStore .leftPanelWidth ,
429- () => {
430- const diff = availableColSpace () - totalColWidth ();
431- if (diff !== 0 ) {
432- colWidths [0 ] = Math .max (MIN_COL_WIDTH , colWidths [0 ] + diff );
433- }
434- },
435- );
436236 </script >
437237
438238<style scoped>
439- /* --- 共享 grid 布局 (7格: col handle col handle col handle col) --- */
440239.list-header ,
441240.identity-item {
442241 display : grid ;
443- /* gridTemplateColumns 由 :style="gridStyle" 动态设置 */
242+ grid-template-columns :
243+ minmax (0 , 1fr )
244+ clamp (36px , 14% , 56px )
245+ clamp (58px , 24% , 82px )
246+ clamp (34px , 12% , 44px );
444247 align-items : center ;
445- padding : 4px 8px ;
248+ column-gap : 8px ;
249+ padding : 4px 10px ;
446250}
447251
448252.list-header {
@@ -470,37 +274,6 @@ watch(
470274 background-color : var (--hover-bg );
471275}
472276
473- /* --- 列拖拽手柄 (表头) --- */
474- .col-resize-handle {
475- width : 4px ;
476- height : 100% ;
477- cursor : col-resize ;
478- position : relative ;
479- }
480-
481- .col-resize-handle ::after {
482- content : ' ' ;
483- position : absolute ;
484- top : 25% ;
485- bottom : 25% ;
486- left : 1px ;
487- width : 1px ;
488- background-color : var (--border-color );
489- transition : background-color 0.15s ;
490- }
491-
492- .col-resize-handle :hover ::after {
493- left : 0 ;
494- width : 4px ;
495- background-color : var (--active-accent );
496- border-radius : 2px ;
497- }
498-
499- /* --- 数据行列分隔 (纯视觉对齐, 不可拖) --- */
500- .col-divider {
501- width : 4px ;
502- }
503-
504277/* --- 各列样式 --- */
505278.col-name {
506279 display : flex ;
@@ -514,15 +287,18 @@ watch(
514287 align-items : center ;
515288 justify-content : center ;
516289 overflow : hidden ;
290+ min-width : 0 ;
517291}
518292
519293.col-role {
520294 overflow : hidden ;
295+ min-width : 0 ;
521296}
522297
523298.col-color {
524299 display : flex ;
525300 justify-content : center ;
301+ min-width : 0 ;
526302}
527303
528304.name-display {
@@ -543,11 +319,15 @@ watch(
543319
544320.count {
545321 font-size : 10px ;
322+ min-width : 0 ;
323+ max-width : 100% ;
546324 padding : 2px 6px ;
547325 background : var (--bg-secondary );
548326 border-radius : 10px ;
549327 color : var (--text-muted );
550328 white-space : nowrap ;
329+ overflow : hidden ;
330+ text-overflow : ellipsis ;
551331}
552332
553333.name-input {
0 commit comments