feat: 1.封装多行标签展开收起组件; 2.首页开发

This commit is contained in:
rd
2025-08-20 10:16:59 +08:00
parent ac2de33d72
commit 08c8488b67
4 changed files with 279 additions and 3 deletions

View 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>

View 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;
}
}
}
}
}

View File

@ -1,7 +1,8 @@
<script lang="jsx"> <script lang="tsx">
import HistoryConversationDrawer from './components/history-conversation-drawer'; import HistoryConversationDrawer from './components/history-conversation-drawer';
import { Sender } from 'ant-design-x-vue'; import { Sender } from 'ant-design-x-vue';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import ExpandableTags from '@/components/expandable-tags';
import { useSharedDataStore } from '@/stores/modules/share-data'; import { useSharedDataStore } from '@/stores/modules/share-data';
@ -17,7 +18,30 @@ export default {
}; };
const handleSearch = () => { const handleSearch = () => {
message.info('handleSearch'); message.info('handleSearch----' + searchValue.value);
};
const tagList = [
'人工智能',
'人工智能与应用',
'行业分析与市场数据',
'标签标签标签标签标签标签标签',
'标签标签标签标签标签标签标签',
'标签标签标签标签标签标签标签',
'标签标签标签标签标签标签标签',
'标签A',
'啊啊啊',
'宝宝贝贝',
'微信',
'吧啊啊',
'哦哦哦哦哦哦哦哦',
'人工智能',
'人工智能与应用',
];
const handleTagClick = (tag: string) => {
searchValue.value = tag;
handleSearch();
}; };
onMounted(() => { onMounted(() => {
@ -56,7 +80,8 @@ export default {
</div> </div>
)} )}
/> />
<p class="cts">可以试试这样下发任务</p> <p class="cts mb-6px">可以试试这样下发任务</p>
<ExpandableTags tags={tagList} clickable onTagClick={handleTagClick} />
</div> </div>
</div> </div>

View File

@ -31,6 +31,12 @@
linear-gradient(113deg, #6d4cfe 0%, #b93bf0 100%); linear-gradient(113deg, #6d4cfe 0%, #b93bf0 100%);
} }
} }
:deep(.expandable-tags-container) {
.tag-list {
row-gap: 6px;
column-gap: 12px;
}
}
} }
.history-conversation-btn { .history-conversation-btn {
border-radius: 8px 0 0 8px; border-radius: 8px 0 0 8px;