词云组件

效果图:

image

 

组件代码:

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

 

posted @ 2025-10-01 13:57  shuihanxiao  阅读(6)  评论(0)    收藏  举报