Skip to content

Commit 92598ef

Browse files
fix: keep app tags on one line (#12563)
1 parent 748ceca commit 92598ef

1 file changed

Lines changed: 119 additions & 19 deletions

File tree

  • frontend/src/views/app-store/components

frontend/src/views/app-store/components/tag.vue

Lines changed: 119 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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>
@@ -11,10 +11,10 @@
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>
@@ -29,9 +29,22 @@
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';
3548
import { getAppTags } from '@/api/modules/app';
3649
import { App } from '@/api/interface/app';
3750
@@ -47,16 +60,51 @@ const activeTag = ref('all');
4760
const tags = ref<App.Tag[]>([]);
4861
const moreTag = ref('');
4962
const visibleTagCount = ref(7);
63+
const allMeasureRef = ref();
64+
const moreMeasureRef = ref();
5065
const emit = defineEmits(['change']);
66+
const measureTagRefs: Record<string, any> = {};
5167
5268
const visibleTags = computed(() => tags.value.slice(0, visibleTagCount.value));
5369
const 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
5577
const 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
62110
const changeTag = (key: string) => {
@@ -68,27 +116,54 @@ const changeTag = (key: string) => {
68116
} else {
69117
moreTag.value = '';
70118
}
119+
nextTick(() => {
120+
calculateVisibleTagCount();
121+
});
71122
};
72123
73124
const 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

Comments
 (0)