词云组件
效果图:

组件代码:
<template> <div class="post"> <div class="portal-title flex-h justify-between"> <div class="flex-h"> <img class="icon" :src="iconSrc" alt="" /> <div class="name">{{ title }}</div> </div> </div> <!-- 顶部词云 --> <div class="data-container"> <div v-for="(item, index) in dataItems" :key="index" class="data-item" :style="getItemStyle(index)"> <img v-if="index < 3" :src="getCrownIcon(index)" alt="icon" class="crown-icon" /> <span>{{ item.postName }}</span> </div> </div> </div> </template> <script lang="ts" name="" setup> import { ref, onMounted, watch } from "vue"; // 获取皇冠图标的路径 // 1 const goldIcon = "https:///word-icon1.png"; //2 const silverIcon = "https:///word-icon2.png"; // 3 const bronzeIcon = "https:///word-icon3.png"; const getCrownIcon = (index: number): string => { switch (index) { case 0: return goldIcon; case 1: return silverIcon; case 2: return bronzeIcon; default: return ""; } }; // 定义父组件传递的 props const props = defineProps({ // 组件标题 title: { type: String, default: "三维师像矩阵", }, // 标题图标 iconSrc: { type: String, default: "https:///mine/icon-threed.png", }, dataSource: { type: Array, required: true, default: () => [], }, }); const dataItems = ref<any>([]); const updateDataItems = (dataSource: any) => { // 遍历从后端返回的 data,提取 `postName` 和 `enrollmentCount` const newData = dataSource.map((item: any) => ({ postName: item.postName, enrollmentCount: item.enrollmentCount, })); newData.sort((a: any, b: any) => Number(b.enrollmentCount) - Number(a.enrollmentCount)); // 将新的数据赋值给 `dataItems` dataItems.value = newData; }; // 监听 dataSource 变化 watch( () => props.dataSource, (newdataSource) => { if (newdataSource && Array.isArray(newdataSource)) { updateDataItems(newdataSource); } }, { immediate: true }, // 立即执行 ); onMounted(() => { // 确保 props.dataSource 中的数据已经传递过来 if (props.dataSource && Array.isArray(props.dataSource)) { updateDataItems(props.dataSource); } }); /// 用于存储已生成的数据项位置,避免重叠 const positions: { top: number; left: number }[] = [ { top: 24, left: 38 }, { top: 46, left: 26 }, { top: 55, left: 59 }, { top: 3, left: 54 }, { top: 14, left: 75 }, { top: 40, left: 74 }, { top: 80, left: 69 }, { top: 75, left: 42 }, { top: 80, left: 7 }, { top: 64, left: 15 }, { top: 33, left: 2 }, { top: 12, left: 17 }, ]; // 检查两个位置是否重叠(阈值调整为更大,确保不会重叠) const isOverlap = (top: number, left: number) => { const threshold = 19; // 允许的最小距离阈值,单位:百分比 return positions.some((pos) => Math.abs(pos.top - top) < threshold && Math.abs(pos.left - left) < threshold); }; // 递归生成不重叠的随机位置 const generateRandomPosition = (): { top: number; left: number } => { const topValue = Math.random() * 80; const leftValue = -1 + Math.random() * 85; // 检查是否重叠 if (isOverlap(topValue, leftValue)) { return generateRandomPosition(); // 如果重叠,重新生成位置 } // 不重叠则存储位置 positions.push({ top: topValue, left: leftValue }); return { top: topValue, left: leftValue }; }; //背景随机 // const bg = [ // "https:///word-bg1.png", // "https:///word-bg2.png", // "https:///word-bg3.png", // "https:///word-bg4.png", // ] // const colorsList = ["#738FF7", "#F06B7F", "#45C1C0","#59BAF6"]; // 背景图和文字颜色的对应组合 const bgColorCombinations = [ { bg: "https:///word-bg1.png", color: "#738FF7", }, { bg: "https:///word-bg2.png", color: "#F06B7F", }, { bg: "https:///word-bg3.png", color: "#45C1C0", }, { bg: "https:///word-bg4.png", color: "#59BAF6", }, ]; // 随机选择一个背景组合 const getRandomBgColor = () => { return bgColorCombinations[Math.floor(Math.random() * bgColorCombinations.length)]; }; // 根据排名动态生成样式 const getItemStyle = (index: number): any => { // // 随机取一个背景图 // const randomBg = bg[Math.floor(Math.random() * bg.length)]; // // 随机取一个文字颜色 // const randomColor = colorsList[Math.floor(Math.random() * colorsList.length)]; // 随机选择一个背景组合 const { bg, color } = getRandomBgColor(); const baseStyle: any = { fontWeight: "normal", fontSize: "14px", padding: "6px 24px 4px", // backgroundImage: "url('@/assets/pic/backup.png')", backgroundImage: `url(${bg})`, backgroundSize: "cover", backgroundRepeat: "no-repeat", color: color, // 随机文字颜色 display: "flex", alignItems: "center", justifyContent: "center", whiteSpace: "nowrap", // 防止文本换行 position: "absolute", }; // 动态生成位置 let positionStyle: any; switch (index) { case 0: positionStyle = { top: "24%", left: "38%" }; break; case 1: positionStyle = { top: "46%", left: "26%" }; break; case 2: positionStyle = { top: "55%", left: "59%" }; break; case 3: positionStyle = { top: "3%", left: "54%" }; break; case 4: positionStyle = { top: "14%", left: "75%" }; break; case 5: // positionStyle = { top: "40%", left: "74%" }; positionStyle = { top: "12%", left: "17%" }; break; case 6: positionStyle = { top: "80%", left: "69%" }; break; case 7: positionStyle = { top: "75%", left: "42%" }; break; case 8: positionStyle = { top: "80%", left: "7%" }; break; case 9: // positionStyle = { top: "64%", left: "15%" }; positionStyle = { top: "33%", left: "2%" }; break; case 10: positionStyle = { top: "33%", left: "2%" }; break; case 11: positionStyle = { top: "12%", left: "17%" }; break; default: const { top, left } = generateRandomPosition(); // 生成不重叠的随机位置 positionStyle = { top: `${top}%`, left: `${left}%` }; break; } // 动态设置背景图,前三名使用不同的背景 // const backgroundImage = // index < 3 // ? `url(${new URL("@/assets/pic/backup2.png", import.meta.url)})` // : `url(${new URL("@/assets/pic/backup.png", import.meta.url)})`; return { ...baseStyle, // backgroundImage,//前三不同背景时启用 // fontWeight: index < 3 ? "bold" : "normal", // backgroundSize: index < 3 ? "cover" : "contain",//前三背景图 // fontSize: index === 0 ? "17px" : index === 1 ? "16px" : index === 2 ? "15px" : "14px", //不同大小 fontSize: "14px", //统一大小 padding: index === 0 ? "7px 15px 8px" : index === 1 ? "7px 12px 7px" : index === 2 ? "8px 12px 8px" : "11px 24px 11px", ...positionStyle, }; }; </script> <style lang="scss" scoped> .post { position: relative; width: 100%; height: 260px; margin: 0 auto; padding: 12px 16px; margin-bottom: 55px; } .portal-title { .icon { width: 30px; height: 30px; margin-right: 8px; } .name { font-weight: 600; font-size: 16px; line-height: 24px; text-align: left; color: var(--theme-font-color); } } .post-box-background { position: absolute; width: 100%; height: 100%; border-radius: 50%; top: 0; left: 0; transform: translate(-50%, -50%); z-index: 0; } .data-container { position: absolute; width: 95%; height: 100%; z-index: 1; } .data-item { text-align: center; padding: 6px 24px 4px; z-index: 1; white-space: nowrap; line-height: 1; letter-spacing: 2px; min-width: 100px; } span { letter-spacing: 1px; font-family: PingFang SC, PingFang SC; } .crown-icon { width: 20px; height: 20px; margin-right: 3px; } </style>
父组件使用:
<BsyWordCloud :dataSource="dataSource" /> <script setup lang="ts"> const dataSource=[ {postName:'鞠躬尽瘁1'}, {postName:'循循善诱2'}, {postName:'良师益友3'}, {postName:'最美教师4'}, {postName:'最美教师5'}, {postName:'人力资源6'}, {postName:'人力资源7'}, {postName:'人力资源8'}, {postName:'人力资源9'} ] </script>

浙公网安备 33010602011771号