效果图:
组件代码:
<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>