feat: 1.封装多行标签展开收起组件; 2.首页开发
This commit is contained in:
205
src/components/expandable-tags/index.vue
Normal file
205
src/components/expandable-tags/index.vue
Normal file
@ -0,0 +1,205 @@
|
||||
<script lang="tsx">
|
||||
import { ref, defineComponent, onMounted, nextTick, watch, h } from 'vue';
|
||||
|
||||
export interface ExpandableTagsProps {
|
||||
// 标签数据数组
|
||||
tags: string[];
|
||||
// 最大显示行数
|
||||
maxLine: number;
|
||||
// 是否可点击标签
|
||||
clickable: boolean;
|
||||
}
|
||||
|
||||
export interface ExpandableTagsEmits {
|
||||
(e: 'tagClick', tag: string, index: number): void;
|
||||
(e: 'expandChange', isExpand: boolean): void;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ExpandableTags',
|
||||
props: {
|
||||
tags: {
|
||||
type: Array as () => string[],
|
||||
default: () => [],
|
||||
},
|
||||
maxLine: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['tagClick', 'expandChange'],
|
||||
setup(props: ExpandableTagsProps, { emit, expose }) {
|
||||
const isExpand = ref(false);
|
||||
const showExpandBtn = ref(false);
|
||||
const displayedTags = ref(props.tags);
|
||||
const hideLength = ref(0);
|
||||
|
||||
// 初始化计算标签显示状态
|
||||
const init = async () => {
|
||||
await nextTick();
|
||||
|
||||
const listCon = document.querySelector('.expandable-tags-container .tag-list') as HTMLElement;
|
||||
if (!listCon) return;
|
||||
|
||||
displayedTags.value = props.tags;
|
||||
|
||||
await nextTick();
|
||||
|
||||
const labels = listCon.querySelectorAll('.tag-item:not(.expand-btn)');
|
||||
if (labels.length === 0) return;
|
||||
|
||||
// 计算单行高度
|
||||
const lineHeight = labels[0].getBoundingClientRect().height;
|
||||
|
||||
// 设置容器最大高度
|
||||
const containerHeight = lineHeight * props.maxLine;
|
||||
listCon.style.maxHeight = `${containerHeight}px`;
|
||||
listCon.style.overflow = 'hidden';
|
||||
|
||||
await nextTick();
|
||||
|
||||
// 检查是否有标签超出容器高度
|
||||
let labelIndex = 0;
|
||||
const listConBottom = listCon.getBoundingClientRect().bottom;
|
||||
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
const _top = labels[i].getBoundingClientRect().top;
|
||||
if (_top >= listConBottom) {
|
||||
// 标签顶部超过容器底部,说明超出
|
||||
showExpandBtn.value = true;
|
||||
labelIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置容器样式
|
||||
listCon.style.maxHeight = '';
|
||||
listCon.style.overflow = '';
|
||||
|
||||
if (!showExpandBtn.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 重新计算可显示的标签数量(考虑展开按钮占用的空间)
|
||||
await nextTick();
|
||||
|
||||
const listConRect = listCon.getBoundingClientRect();
|
||||
const expandBtn = listCon.querySelector('.expand-btn');
|
||||
const expandBtnWidth = expandBtn?.getBoundingClientRect()?.width || 0;
|
||||
|
||||
// 获取实际的column-gap值
|
||||
const computedStyle = window.getComputedStyle(listCon);
|
||||
const columnGap = parseInt(computedStyle.getPropertyValue('column-gap')) || 8;
|
||||
|
||||
// 从超出的位置向前查找,找到能容纳展开按钮的位置
|
||||
for (let i = labelIndex - 1; i >= 0; i--) {
|
||||
const labelRight = labels[i].getBoundingClientRect().right - listConRect.left;
|
||||
// 使用columnGap代替marginRight
|
||||
if (labelRight + columnGap + expandBtnWidth <= listConRect.width) {
|
||||
hideLength.value = i + 1;
|
||||
displayedTags.value = props.tags.slice(0, hideLength.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 切换展开/折叠状态
|
||||
const toggleExpand = () => {
|
||||
isExpand.value = !isExpand.value;
|
||||
|
||||
if (isExpand.value) {
|
||||
displayedTags.value = props.tags;
|
||||
} else {
|
||||
displayedTags.value = props.tags.slice(0, hideLength.value);
|
||||
}
|
||||
|
||||
emit('expandChange', isExpand.value);
|
||||
};
|
||||
|
||||
const handleTagClick = (tag: string, index: number) => {
|
||||
if (props.clickable) {
|
||||
emit('tagClick', tag, index);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
if (!isExpand.value) {
|
||||
showExpandBtn.value = false;
|
||||
nextTick(() => {
|
||||
init();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.tags,
|
||||
() => {
|
||||
isExpand.value = false;
|
||||
showExpandBtn.value = false;
|
||||
nextTick(() => {
|
||||
init();
|
||||
});
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.maxLine,
|
||||
() => {
|
||||
isExpand.value = false;
|
||||
showExpandBtn.value = false;
|
||||
nextTick(() => {
|
||||
init();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div class="expandable-tags-container">
|
||||
<div class={`tag-list ${isExpand.value ? 'expand' : ''}`}>
|
||||
{displayedTags.value.map((tag, index) => (
|
||||
<div
|
||||
key={index}
|
||||
class={`flex items-center h-24px tag-item ${props.clickable ? 'clickable' : ''}`}
|
||||
onClick={() => handleTagClick(tag, index)}
|
||||
>
|
||||
<span class="cts color-#6D4CFE tag-text">{tag}</span>
|
||||
</div>
|
||||
))}
|
||||
{showExpandBtn.value && (
|
||||
<div
|
||||
class="expand-btn flex items-center h-24px cursor-pointer"
|
||||
onClick={(e: Event) => {
|
||||
e.stopPropagation();
|
||||
toggleExpand();
|
||||
}}
|
||||
>
|
||||
<span class="cts mr-2px color-#6D4CFE expand-text">{isExpand.value ? '收起' : '更多'}</span>
|
||||
<icon-down size={12} class="color-#6D4CFE icon" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './style.scss';
|
||||
</style>
|
||||
40
src/components/expandable-tags/style.scss
Normal file
40
src/components/expandable-tags/style.scss
Normal file
@ -0,0 +1,40 @@
|
||||
.expandable-tags-container {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
.cts {
|
||||
font-family: $font-family-regular;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
.tag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
transition: all 0.3s;
|
||||
.tag-item {
|
||||
padding: 0 8px;
|
||||
background-color: #f6f4ff;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
box-shadow: 0 4px 8px 0 rgba(109, 76, 254, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
.expand-btn {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
&.expand {
|
||||
max-height: none !important;
|
||||
.expand-btn {
|
||||
.icon {
|
||||
transform: rotate(180deg);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user