2025-06-16 14:42:26 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view>
|
2025-06-17 11:18:39 +08:00
|
|
|
|
<topHeader ref="topHeaderRef" @search="search"></topHeader>
|
2025-09-04 16:50:20 +08:00
|
|
|
|
<div class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px">
|
2025-06-30 18:37:27 +08:00
|
|
|
|
<div class="title-row">
|
|
|
|
|
|
<span class="title mr-4px">行业词云</span>
|
2025-09-04 16:10:44 +08:00
|
|
|
|
<Tooltip>
|
|
|
|
|
|
<template #title>基于行业内内容提取的高频词汇。</template>
|
2025-09-25 15:26:42 +08:00
|
|
|
|
<SvgIcon name="xt-question-circle" size="16" class="!color-#737478" />
|
2025-09-04 16:10:44 +08:00
|
|
|
|
</Tooltip>
|
2025-06-30 18:37:27 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-07-01 16:39:47 +08:00
|
|
|
|
<div class="multi-row-tag-cloud h-472px">
|
|
|
|
|
|
<NoData v-if="tagRows.length === 0" text="暂无数据" />
|
2025-06-16 14:42:26 +08:00
|
|
|
|
<!-- 动态生成多行标签 -->
|
2025-07-01 16:39:47 +08:00
|
|
|
|
<template v-else>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="(row, rowIndex) in tagRows"
|
|
|
|
|
|
:key="rowIndex"
|
|
|
|
|
|
class="tag-row"
|
|
|
|
|
|
:style="{ justifyContent: row.align || 'center' }"
|
2025-06-16 14:42:26 +08:00
|
|
|
|
>
|
2025-09-05 13:51:33 +08:00
|
|
|
|
<Tag
|
2025-07-01 16:39:47 +08:00
|
|
|
|
v-for="(tag, tagIndex) in row.tags"
|
|
|
|
|
|
:key="tagIndex"
|
|
|
|
|
|
class="cursor-pointer"
|
|
|
|
|
|
:style="{
|
|
|
|
|
|
fontSize: `${getTagStyle(rowIndex, tagIndex, row.tags.length).fontSize}px`,
|
|
|
|
|
|
height: `${getTagStyle(rowIndex, tagIndex, row.tags.length).lineHeight}px`,
|
|
|
|
|
|
color: '#6d4cfe',
|
|
|
|
|
|
backgroundColor: '#F0EDFF',
|
|
|
|
|
|
margin: '0 12px 12px 0',
|
|
|
|
|
|
transition: 'all 0.3s',
|
|
|
|
|
|
paddingLeft: `${getPaddingLeft(rowIndex, tagIndex)}px`,
|
|
|
|
|
|
paddingRight: `${getPaddingLeft(rowIndex, tagIndex)}px`,
|
|
|
|
|
|
borderRadius: '32px',
|
|
|
|
|
|
fontWeight: 400,
|
|
|
|
|
|
}"
|
|
|
|
|
|
@mouseenter="hoverTag = tag"
|
|
|
|
|
|
@mouseleave="hoverTag = null"
|
|
|
|
|
|
>
|
2025-09-04 16:10:44 +08:00
|
|
|
|
<Tooltip :title="`性价比:${Number(tag.rate * 100)}%`" placement="topLeft">
|
2025-09-04 16:50:20 +08:00
|
|
|
|
<span>{{ tag.term }}</span>
|
2025-09-04 16:10:44 +08:00
|
|
|
|
</Tooltip>
|
2025-09-05 13:51:33 +08:00
|
|
|
|
</Tag>
|
2025-07-01 16:39:47 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2025-06-16 14:42:26 +08:00
|
|
|
|
</div>
|
2025-09-04 16:50:20 +08:00
|
|
|
|
</div>
|
2025-06-16 14:42:26 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import topHeader from './topHeader.vue';
|
|
|
|
|
|
import { ref, computed } from 'vue';
|
|
|
|
|
|
import { fetchindustryTerms } from '@/api/all/index';
|
2025-09-05 13:51:33 +08:00
|
|
|
|
import { Tooltip, Tag } from 'ant-design-vue';
|
2025-06-16 14:42:26 +08:00
|
|
|
|
|
|
|
|
|
|
const topHeaderRef = ref();
|
|
|
|
|
|
// 从topHeader获取统一的状态
|
|
|
|
|
|
const selectedIndustry = computed(() => topHeaderRef.value?.selectedIndustry);
|
|
|
|
|
|
const selectedSubCategory = computed(() => topHeaderRef.value?.selectedSubCategory);
|
|
|
|
|
|
const selectedTimePeriod = computed(() => topHeaderRef.value?.selectedTimePeriod);
|
2025-06-17 11:18:39 +08:00
|
|
|
|
// 监听筛选条件变化
|
|
|
|
|
|
watch([selectedTimePeriod, selectedSubCategory], () => {
|
|
|
|
|
|
getIndustryTerms();
|
|
|
|
|
|
});
|
|
|
|
|
|
const search = () => {
|
|
|
|
|
|
getIndustryTerms();
|
|
|
|
|
|
};
|
|
|
|
|
|
watch(selectedIndustry, () => {
|
|
|
|
|
|
selectedSubCategory.value = 0;
|
|
|
|
|
|
getIndustryTerms();
|
|
|
|
|
|
});
|
2025-06-16 14:42:26 +08:00
|
|
|
|
|
2025-06-17 11:18:39 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
getIndustryTerms();
|
|
|
|
|
|
});
|
2025-06-16 14:42:26 +08:00
|
|
|
|
const getIndustryTerms = async () => {
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
industry_id: selectedIndustry.value,
|
|
|
|
|
|
time_dimension: selectedTimePeriod.value,
|
|
|
|
|
|
};
|
2025-06-21 16:57:01 +08:00
|
|
|
|
if (selectedIndustry.value == undefined) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-06-20 02:07:38 -04:00
|
|
|
|
if (selectedSubCategory.value !== 0) {
|
2025-06-17 11:18:39 +08:00
|
|
|
|
params['industry_id'] = selectedSubCategory.value;
|
|
|
|
|
|
}
|
2025-06-16 14:42:26 +08:00
|
|
|
|
const res = await fetchindustryTerms(params);
|
2025-06-21 16:57:01 +08:00
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
|
// 这里需要根据API返回的数据结构处理成tagRows需要的格式
|
|
|
|
|
|
tagRows.value = processTagData(res.data.slice(0, 70));
|
|
|
|
|
|
}
|
2025-06-16 14:42:26 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 标签数据(按行分组)
|
2025-07-01 16:39:47 +08:00
|
|
|
|
const tagRows = ref([]);
|
2025-06-16 14:42:26 +08:00
|
|
|
|
const hoverTag = ref(null);
|
|
|
|
|
|
|
2025-06-20 02:07:38 -04:00
|
|
|
|
const lineHeightStart = [28, 52, 58, 72, 58, 52, 28]; // 7行时
|
|
|
|
|
|
const fontSizeStart = [12, 26, 28, 38, 28, 26, 12]; // 7行时
|
|
|
|
|
|
const stepLineHeight = 10; // 行高每步递减
|
|
|
|
|
|
const stepFontSize = 6; // 字号每步递减
|
2025-06-16 14:42:26 +08:00
|
|
|
|
|
2025-06-20 02:07:38 -04:00
|
|
|
|
function getTagStyle(rowIndex, tagIndex, tagsLength) {
|
|
|
|
|
|
// 计算当前行的中间起点
|
|
|
|
|
|
const center = Math.floor(tagsLength / 2);
|
|
|
|
|
|
const distance = Math.abs(tagIndex - center);
|
|
|
|
|
|
|
|
|
|
|
|
// 递减,最小值保护
|
|
|
|
|
|
const lineHeight = Math.max(lineHeightStart[rowIndex] - stepLineHeight * distance, 28);
|
|
|
|
|
|
const fontSize = Math.max(fontSizeStart[rowIndex] - stepFontSize * distance, 12);
|
|
|
|
|
|
|
|
|
|
|
|
return { fontSize, lineHeight };
|
|
|
|
|
|
}
|
2025-06-16 14:42:26 +08:00
|
|
|
|
|
|
|
|
|
|
const getPaddingLeft = (rowIndex, tagIndex) => {
|
|
|
|
|
|
const centerRow = Math.floor(tagRows.value.length / 2);
|
|
|
|
|
|
const distance = Math.abs(rowIndex - centerRow);
|
|
|
|
|
|
return 30 - distance * 8 + (tagIndex % 2) * 5; // 基础24px,行距影响4px,微调差异2px
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getPadding = (rowIndex, tagIndex) => {
|
|
|
|
|
|
const centerRow = Math.floor(tagRows.value.length / 2);
|
|
|
|
|
|
const distance = Math.abs(rowIndex - centerRow);
|
|
|
|
|
|
return 28 - distance * 8 + (tagIndex % 2) * 5; // 基础24px,行距影响4px,微调差异2px
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理API返回数据为tagRows格式
|
|
|
|
|
|
const processTagData = (apiData) => {
|
2025-06-20 02:07:38 -04:00
|
|
|
|
// 计算应该分几行,最多7行
|
|
|
|
|
|
const maxRows = 7;
|
|
|
|
|
|
const rowCount = Math.min(Math.ceil(apiData.length / 7), maxRows);
|
|
|
|
|
|
|
|
|
|
|
|
// 均分数据到rowCount行
|
|
|
|
|
|
const baseCount = Math.floor(apiData.length / rowCount);
|
|
|
|
|
|
let remain = apiData.length % rowCount;
|
|
|
|
|
|
const rows = [];
|
|
|
|
|
|
let start = 0;
|
|
|
|
|
|
for (let i = 0; i < rowCount; i++) {
|
|
|
|
|
|
let count = baseCount + (remain > 0 ? 1 : 0);
|
|
|
|
|
|
if (remain > 0) remain--;
|
|
|
|
|
|
rows.push(apiData.slice(start, start + count));
|
|
|
|
|
|
start += count;
|
2025-06-16 14:42:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-20 02:07:38 -04:00
|
|
|
|
// 居中分布,从中间往两侧
|
|
|
|
|
|
const result = [];
|
|
|
|
|
|
const center = Math.floor(rowCount / 2);
|
|
|
|
|
|
for (let i = 0; i < rowCount; i++) {
|
|
|
|
|
|
const pos = i % 2 === 0 ? center + Math.floor(i / 2) : center - Math.ceil(i / 2);
|
|
|
|
|
|
result[pos] = { tags: rows[i], align: 'center' };
|
2025-06-16 14:42:26 +08:00
|
|
|
|
}
|
2025-06-20 02:07:38 -04:00
|
|
|
|
return result;
|
2025-06-16 14:42:26 +08:00
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
2025-07-11 16:50:48 +08:00
|
|
|
|
<style scoped lang="scss">
|
2025-06-16 14:42:26 +08:00
|
|
|
|
/* 自定义样式 */
|
|
|
|
|
|
:deep(.arco-table-th) {
|
|
|
|
|
|
background-color: var(--color-fill-2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.arco-table-tr):hover {
|
|
|
|
|
|
background-color: var(--color-fill-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
:deep(.arco-btn-outline) {
|
|
|
|
|
|
color: #6d4cfe !important;
|
|
|
|
|
|
border-color: #6d4cfe !important;
|
|
|
|
|
|
}
|
2025-09-03 16:42:05 +08:00
|
|
|
|
:deep(.ant-modal-body) {
|
2025-06-16 14:42:26 +08:00
|
|
|
|
padding: 0px;
|
|
|
|
|
|
}
|
2025-06-20 02:07:38 -04:00
|
|
|
|
|
2025-06-16 14:42:26 +08:00
|
|
|
|
.multi-row-tag-cloud {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: 20px;
|
2025-06-20 02:07:38 -04:00
|
|
|
|
.tag-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
// flex-wrap: wrap;
|
|
|
|
|
|
:deep(.arco-tag) {
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
border-radius: 32px;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
// min-width: 100px;
|
|
|
|
|
|
max-width: 200px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background-color: #ffffff !important;
|
|
|
|
|
|
color: #6d4cfe !important;
|
|
|
|
|
|
box-shadow: 0px 4px 12px 0px #0000001a;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-16 14:42:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 悬停放大效果 */
|
2025-09-05 13:51:33 +08:00
|
|
|
|
.ant-tag:hover {
|
2025-06-16 14:42:26 +08:00
|
|
|
|
transform: scale(1.1);
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
.pop-btn {
|
|
|
|
|
|
background: #fff !important;
|
|
|
|
|
|
border-color: #fff !important;
|
|
|
|
|
|
color: #737478 !important;
|
|
|
|
|
|
margin-left: -5px;
|
|
|
|
|
|
}
|
2025-06-30 18:37:27 +08:00
|
|
|
|
.title-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
height: 64px;
|
|
|
|
|
|
// padding: 10px 0 2px 0;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
.title {
|
|
|
|
|
|
color: var(--Text-1, #211f24);
|
2025-07-11 16:50:48 +08:00
|
|
|
|
font-family: $font-family-medium;
|
2025-06-30 18:37:27 +08:00
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
line-height: 24px; /* 150% */
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-16 14:42:26 +08:00
|
|
|
|
</style>
|