11<template >
2- <div ref =" containerRef" class =" flex gap-2" >
2+ <div ref =" containerRef" class =" tag-container flex gap-2" >
33 <el-check-tag :checked =" activeTag == 'all'" @click =" changeTag('all')" >
44 {{ $t('app.all') }}
55 </el-check-tag >
1111 >
1212 {{ item.name }}
1313 </el-check-tag >
14- <div class =" inline" v-if =" hiddenTags.length > 0" >
14+ <div class =" more-tag inline" v-if =" hiddenTags.length > 0" >
1515 <el-dropdown >
1616 <el-check-tag :checked =" moreTag !== ''" >
17- {{ moreTag == '' ? $t('tabs.more') : getTagValue(moreTag) }}
17+ {{ moreLabel }}
1818 <el-icon :size =" 10" >
1919 <arrow-down />
2020 </el-icon >
2929 </el-dropdown >
3030 </div >
3131 </div >
32+ <div class =" tag-measurements" aria-hidden =" true" >
33+ <el-check-tag ref =" allMeasureRef" >{{ $t('app.all') }}</el-check-tag >
34+ <el-check-tag v-for =" item in tags" :key =" `measure-${item.key}`" :ref =" (el) => setMeasureTagRef(item.key, el)" >
35+ {{ item.name }}
36+ </el-check-tag >
37+ <el-check-tag ref =" moreMeasureRef" >
38+ {{ moreLabel }}
39+ <el-icon :size =" 10" >
40+ <arrow-down />
41+ </el-icon >
42+ </el-check-tag >
43+ </div >
3244</template >
3345
3446<script lang="ts" setup>
47+ import i18n from ' @/lang' ;
3548import { getAppTags } from ' @/api/modules/app' ;
3649import { App } from ' @/api/interface/app' ;
3750
@@ -47,16 +60,51 @@ const activeTag = ref('all');
4760const tags = ref <App .Tag []>([]);
4861const moreTag = ref (' ' );
4962const visibleTagCount = ref (7 );
63+ const allMeasureRef = ref ();
64+ const moreMeasureRef = ref ();
5065const emit = defineEmits ([' change' ]);
66+ const measureTagRefs: Record <string , any > = {};
5167
5268const visibleTags = computed (() => tags .value .slice (0 , visibleTagCount .value ));
5369const hiddenTags = computed (() => tags .value .slice (visibleTagCount .value ));
70+ const moreLabel = computed (() => {
71+ if (moreTag .value === ' ' ) {
72+ return i18n .global .t (' tabs.more' );
73+ }
74+ return getTagValue (moreTag .value ) || i18n .global .t (' tabs.more' );
75+ });
5476
5577const getTagValue = (key : string ) => {
5678 const tag = tags .value .find ((tag ) => tag .key === key );
5779 if (tag ) {
5880 return tag .name ;
5981 }
82+ return ' ' ;
83+ };
84+
85+ const setMeasureTagRef = (key : string , el : any ) => {
86+ if (el ) {
87+ measureTagRefs [key ] = el ;
88+ return ;
89+ }
90+ delete measureTagRefs [key ];
91+ };
92+
93+ const getElementWidth = (target : any ) => {
94+ const el = target ?.$el || target ;
95+ if (! (el instanceof HTMLElement )) {
96+ return 0 ;
97+ }
98+ return el .getBoundingClientRect ().width ;
99+ };
100+
101+ const getContainerGap = () => {
102+ if (! containerRef .value ) {
103+ return 0 ;
104+ }
105+ const styles = window .getComputedStyle (containerRef .value );
106+ const gap = Number .parseFloat (styles .columnGap || styles .gap || ' 0' );
107+ return Number .isNaN (gap ) ? 0 : gap ;
60108};
61109
62110const changeTag = (key : string ) => {
@@ -68,27 +116,54 @@ const changeTag = (key: string) => {
68116 } else {
69117 moreTag .value = ' ' ;
70118 }
119+ nextTick (() => {
120+ calculateVisibleTagCount ();
121+ });
71122};
72123
73124const calculateVisibleTagCount = () => {
74125 if (! containerRef .value ) return ;
75126
76- const containerWidth = containerRef .value .offsetWidth ;
77-
78- if (containerWidth >= 1800 ) {
79- visibleTagCount .value = 18 ;
80- } else if (containerWidth >= 1400 ) {
81- visibleTagCount .value = 15 ;
82- } else if (containerWidth >= 1200 ) {
83- visibleTagCount .value = 12 ;
84- } else if (containerWidth >= 992 ) {
85- visibleTagCount .value = 8 ;
86- } else if (containerWidth >= 768 ) {
87- visibleTagCount .value = 6 ;
88- } else if (containerWidth >= 576 ) {
89- visibleTagCount .value = 4 ;
90- } else {
91- visibleTagCount .value = 2 ;
127+ const safeReserve = 8 ;
128+ const containerWidth = containerRef .value .getBoundingClientRect ().width - safeReserve ;
129+ const gap = getContainerGap ();
130+ const allWidth = getElementWidth (allMeasureRef .value );
131+ const moreWidth = getElementWidth (moreMeasureRef .value );
132+
133+ if (! containerWidth || ! allWidth ) {
134+ return ;
135+ }
136+
137+ let usedWidth = allWidth ;
138+ let count = 0 ;
139+
140+ for (let i = 0 ; i < tags .value .length ; i ++ ) {
141+ const currentWidth = getElementWidth (measureTagRefs [tags .value [i ].key ]);
142+ if (! currentWidth ) {
143+ continue ;
144+ }
145+
146+ const hasRemainingTags = i < tags .value .length - 1 ;
147+ const nextWidth = usedWidth + gap + currentWidth + (hasRemainingTags ? gap + moreWidth : 0 );
148+
149+ if (nextWidth <= containerWidth ) {
150+ usedWidth += gap + currentWidth ;
151+ count ++ ;
152+ continue ;
153+ }
154+
155+ break ;
156+ }
157+
158+ visibleTagCount .value = count ;
159+
160+ const activeIndex = tags .value .findIndex ((tag ) => tag .key === activeTag .value );
161+ const expectedMoreTag = activeIndex >= visibleTagCount .value ? activeTag .value : ' ' ;
162+ if (expectedMoreTag !== moreTag .value ) {
163+ moreTag .value = expectedMoreTag ;
164+ nextTick (() => {
165+ calculateVisibleTagCount ();
166+ });
92167 }
93168};
94169
@@ -134,6 +209,31 @@ onBeforeUnmount(() => {
134209 </script >
135210
136211<style lang="scss" scoped>
212+ .tag-container {
213+ flex-wrap : nowrap ;
214+ align-items : center ;
215+ overflow : hidden ;
216+ }
217+
218+ .more-tag {
219+ flex-shrink : 0 ;
220+ }
221+
222+ .tag-measurements {
223+ position : absolute ;
224+ visibility : hidden ;
225+ pointer-events : none ;
226+ overflow : hidden ;
227+ white-space : nowrap ;
228+ height : 0 ;
229+ }
230+
231+ .tag-container :deep(.el-check-tag ),
232+ .tag-measurements :deep(.el-check-tag ) {
233+ flex-shrink : 0 ;
234+ white-space : nowrap ;
235+ }
236+
137237.el-check-tag.el-check-tag--primary.is-checked {
138238 background-color : var (--el-color-info-light-9 ) !important ;
139239}
0 commit comments