vue+微信小程序五角星

c 复制代码
<template>
  <view class="star-container">
    <canvas canvas-id="starCanvas" id="starCanvas" class="star-canvas"
      :style="{ width: size + 'px', height: size + 'px' }"></canvas>
  </view>
</template>

<script>
export default {
  props: {
    size: {
      type: Number,
      default: 300
    },
    data: {
      type: Array,
      required: true,
      validator: (value) => {
        return value.length === 5 &&
          value.every(item => item.percentage >= 0 && item.percentage <= 100);
      },
      default: () => [
        { percentage: 80, color: '#FF5252', title: '学业水平' },
        { percentage: 60, color: '#FF9800', title: '思想品德' },
        { percentage: 90, color: '#FFEB3B', title: '身心健康' },
        { percentage: 70, color: '#4CAF50', title: '艺术素养' },
        { percentage: 50, color: '#2196F3', title: '社会实践' }
      ]
    }
  },
  data() {
    return {
      centerX: 0,
      centerY: 0,
      radius: 0,
      ctx: null
    };
  },
  mounted() {
    this.$nextTick(() => {
      this.initCanvas();
    });
  },
  methods: {
    initCanvas() {
      if (!this.data || this.data.length !== 5) {
        console.error('Invalid data format. Expected array of 5 items.');
        return;
      }

      this.centerX = this.size / 2;
      this.centerY = this.size / 2;
      this.radius = this.size * 0.4; // 恢复原始半径

      this.ctx = uni.createCanvasContext('starCanvas', this);
      this.drawStar();
    },

    drawStar() {
      if (!this.ctx) return;
      this.ctx.clearRect(0, 0, this.size, this.size);

      // 绘制填充区域
      this.data.forEach((item, index) => {
        this.drawSector(index, item.percentage, item.color);
      });

      // 绘制中心连线
      this.drawCenterLines();

      // 绘制五角星轮廓
      this.drawStarOutline();

      // 绘制标签
      this.drawLabels();

      this.ctx.draw();
    },

    drawSector(index, percentage, color) {
      if (percentage <= 0) return;

      const angleOffset = -Math.PI / 2;
      const angle1 = angleOffset + (index * 2 * Math.PI) / 5;
      const angle2 = angleOffset + ((index + 1) * 2 * Math.PI) / 5;

      const outerX1 = this.centerX + this.radius * Math.cos(angle1);
      const outerY1 = this.centerY + this.radius * Math.sin(angle1);
      const outerX2 = this.centerX + this.radius * Math.cos(angle2);
      const outerY2 = this.centerY + this.radius * Math.sin(angle2);

      const innerAngle = angle1 + Math.PI / 5;
      const innerX = this.centerX + this.radius * 0.4 * Math.cos(innerAngle);
      const innerY = this.centerY + this.radius * 0.4 * Math.sin(innerAngle);

      const fillRatio = percentage / 100;
      const fillX1 = this.centerX + (outerX1 - this.centerX) * fillRatio;
      const fillY1 = this.centerY + (outerY1 - this.centerY) * fillRatio;
      const fillX2 = this.centerX + (innerX - this.centerX) * fillRatio;
      const fillY2 = this.centerY + (innerY - this.centerY) * fillRatio;
      const fillX3 = this.centerX + (outerX2 - this.centerX) * fillRatio;
      const fillY3 = this.centerY + (outerY2 - this.centerY) * fillRatio;

      this.ctx.beginPath();
      this.ctx.moveTo(this.centerX, this.centerY);
      this.ctx.lineTo(fillX1, fillY1);
      this.ctx.lineTo(fillX2, fillY2);
      this.ctx.lineTo(fillX3, fillY3);
      this.ctx.closePath();

      this.ctx.fillStyle = color;
      this.ctx.fill();
    },

    drawStarOutline() {
      this.ctx.beginPath();

      const angleOffset = -Math.PI / 2;
      for (let i = 0; i <= 5; i++) {
        const angle = angleOffset + (i * 2 * Math.PI) / 5;
        const x = this.centerX + this.radius * Math.cos(angle);
        const y = this.centerY + this.radius * Math.sin(angle);

        if (i === 0) {
          this.ctx.moveTo(x, y);
        } else {
          this.ctx.lineTo(x, y);
        }

        const innerAngle = angle + Math.PI / 5;
        const innerX = this.centerX + this.radius * 0.4 * Math.cos(innerAngle);
        const innerY = this.centerY + this.radius * 0.4 * Math.sin(innerAngle);
        this.ctx.lineTo(innerX, innerY);
      }

      this.ctx.closePath();
      this.ctx.strokeStyle = '#333';
      this.ctx.lineWidth = 2;
      this.ctx.stroke();
    },

    drawCenterLines() {
      this.ctx.beginPath();

      const angleOffset = -Math.PI / 2;
      for (let i = 0; i < 5; i++) {
        const angle = angleOffset + (i * 2 * Math.PI) / 5;
        const innerAngle = angle + Math.PI / 5;
        const x = this.centerX + this.radius * 0.4 * Math.cos(innerAngle);
        const y = this.centerY + this.radius * 0.4 * Math.sin(innerAngle);

        this.ctx.moveTo(this.centerX, this.centerY);
        this.ctx.lineTo(x, y);
      }

      this.ctx.strokeStyle = '#999';
      this.ctx.lineWidth = 1;
      this.ctx.stroke();
    },

    drawLabels() {
      // 调整角度偏移使标签位置正确
      const angleOffset = -Math.PI / 2;
      const labelRadius = this.radius * 1.15;

      this.data.forEach((item, index) => {
        const angle1 = angleOffset + (index * 2 * Math.PI) / 5;
        const angle2 = angleOffset + ((index + 1) * 2 * Math.PI) / 5;
        const midAngle = (angle1 + angle2) / 2;

        let labelX = this.centerX + labelRadius * Math.cos(midAngle);
        let labelY = this.centerY + labelRadius * Math.sin(midAngle);

        // 微调各标签位置
        if (index === 0) { // 顶部标签
          labelY -= -10;//学业
        } else if (index === 1) { // 左上标签
          labelX -= 30;//思想
          labelY -= 5;
        } else if (index === 2) { // 左下标签
          labelX -= 5;//身心
          labelY += -15;
        } else if (index === 3) { // 右下标签
          labelX += 25;//艺术
          // labelY += 5;
        } else if (index === 4) { // 右上标签
          labelX += 5;
          labelY -= -10;
        }

        this.ctx.textAlign = 'center';
        this.ctx.textBaseline = 'middle';
        this.ctx.font = 'bold 14px Arial';
        this.ctx.fillStyle = item.color;
        this.ctx.fillText(item.title, labelX, labelY - 10);

        this.ctx.font = '12px Arial';
        this.ctx.fillText(`${item.percentage}%`, labelX, labelY + 10);
      });
    }
  },

  watch: {
    data: {
      deep: true,
      handler() {
        this.initCanvas();
      }
    }
  }
};
</script>

<style scoped>
.star-container {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 20px;
  background-color: #f8f8f8;
}

.star-canvas {
  display: block;
  margin: 0 auto;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
</style>```
相关推荐
程序员小李白11 分钟前
vue题目
前端·javascript·vue.js
humors22121 分钟前
Deepseek工具:H5+Vue 项目转微信小程序报告生成工具
前端·vue.js·微信小程序·h5·工具·报告
贺小涛30 分钟前
Vue介绍
前端·javascript·vue.js
蜡台1 小时前
vue.config.js 配置
前端·javascript·vue.js·webpack
踩着两条虫2 小时前
AI驱动的Vue3应用开发平台 深入探究(十六):扩展与定制之自定义组件与设计器面板
前端·vue.js·人工智能·开源·ai编程
棋鬼王2 小时前
Cesium(十) 动态修改白模颜色、白模渐变色、白模光圈特效、白模动态扫描光效、白模着色器
前端·javascript·vue.js·智慧城市·数字孪生·cesium
ThridTianFuStreet小貂蝉2 小时前
面试题1:请系统讲讲 Vue2 与 Vue3 的核心差异(响应式、API 设计、性能与编译器)。
前端·javascript·vue.js
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于微信小程序的蓝鲸旧物回收系统的设计与实现为例,包含答辩的问题和答案
微信小程序·小程序
布局呆星3 小时前
Vue3 | 事件绑定与双向数据绑定
前端·javascript·vue.js
@菜菜_达3 小时前
Vue 入门学习
前端·vue.js·学习