<template>
  <view class="cmd-circle">
    <canvas :canvas-id="cid" :style="calCircleStyle"></canvas>
  </view>
</template>

<script>
  /**
   * 进度圈组件  
   * @description 用圈显示一个操作完成的百分比时,为用户显示该操作的当前进度和状态。  
   * @tutorial https://ext.dcloud.net.cn/plugin?id=259  
   * @property {String} cid 画布编号 - 默认defaultCanvas  
   * @property {String} type 进度圈类型 - 圆圈形:circle、仪表盘:dashboard,默认圆圈形:circle  
   * @property {Number} percent 进度圈百分比值 - 显示范围0-100 ,可能数比较大就需要自己转成百分比的值  
   * @property {Boolean} show-info 进度圈进度状态信息 - 显示进度数值或状态图标,默认true  
   * @property {String} font-color 进度圈文字信息颜色  
   * @property {String} font-size 进度圈文字信息大小 - 默认:14  
   * @property {String} status 进度圈状态 - 正常:normal、完成:success、失败:exception,默认正常:normal  
   * @property {Number} stroke-width 进度圈线条宽度 - 建议在条线的宽度范围:1-50,与进度条显示宽度有关,默认:6  
   * @property {String} stroke-color 进度圈的颜色 - 设置后status状态无效  
   * @property {String} stroke-background 进度圈的底圈颜色 - 默认:#eeeeee  
   * @property {String} stroke-shape 进度圈两端的形状 - 圆:round、方直角:square,默认圆:round  
   * @property {Number} width 进度圈布宽度 - 默认80  
   * @property {String} gap-degree 进度圈形缺口角度 - 可取值 0 ~ 360,仅支持类型:circle  
   * @property {String} gap-position 进度圈形缺口位置 - 可取值'top', 'bottom', 'left', 'right',仅支持类型:circle  
   * @example <cmd-circle id="circle1" type="circle" :percent="75"></cmd-circle>  
   */
  export default {
    name: "cmd-circle",

    props: {
      // 画布编号 默认defaultCanvas
      cid: {
        type: String,
        default: "defaultCanvas"
      },
      // 圈类型默认:circle,可选 circle dashboard
      type: {
        type: String,
        validator: val => {
          return ['circle', 'dashboard'].includes(val);
        },
        default: 'circle'
      },
      // 圈进度百分比值
      percent: {
        type: Number,
        validator: val => {
          return val >= 0 && val <= 100;
        },
        default: 0
      },
      // 圈是否显示进度数值或状态图标
      showInfo: {
        type: Boolean,
        default: true
      },
      // 圈文字信息颜色
      fontColor: {
        type: String,
        default: "#595959"
      },
      // 圈文字信息大小 默认14
      fontSize: {
        type: Number,
        default: 14
      },
      // 圈进度状态,可选:normal success exception
      status: {
        type: String,
        validator: val => {
          return ['normal', 'success', 'exception'].includes(val);
        },
        default: 'normal'
      },
      // 圈线条宽度1-50,与width有关
      strokeWidth: {
        type: Number,
        default: 6
      },
      // 圈的颜色,设置后status状态无效
      strokeColor: {
        type: String,
        default: ''
      },
      // 圈的底圈颜色 默认:#eeeeee
      strokeBackground: {
        type: String,
        default: '#eeeeee'
      },
      // 圈两端的形状 可选:'round', 'square'
      strokeShape: {
        type: String,
        validator: val => {
          return ['round', 'square'].includes(val);
        },
        default: 'round'
      },
      // 圈画布宽度
      width: {
        type: Number,
        default: 80
      },
      // 圈缺口角度,可取值 0 ~ 360,仅支持类型:circle  
      gapDegree: {
        type: Number,
        validator: val => {
          return val >= 0 && val <= 360;
        },
        default: 360
      },
      // 圈缺口开始位置,可取值'top', 'bottom', 'left', 'right',仅支持类型:circle  
      gapPosition: {
        type: String,
        validator: val => {
          return ['top', 'bottom', 'left', 'right'].includes(val);
        },
        default: 'top'
      }
    },

    data() {
      return {
        // 画布实例
        ctx: {},
        // 圈半径
        width2px: ""
      }
    },

    computed: {
      // 计算设置圈样式
      calCircleStyle() {
        return `width: ${this.width}px;
				height: ${this.width}px;`
      },
      // 计算圈状态
      calStatus() {
        let status = {}
        switch (this.status) {
          case 'normal':
            status = {
              color: "#1890ff",
              value: 1
            };
            break;
          case 'success':
            status = {
              color: "#52c41a",
              value: 2
            };
            break;
          case 'exception':
            status = {
              color: "#f5222d",
              value: 3
            };
            break;
        }
        return status
      },
      // 计算圈缺口角度
      calGapDegree() {
        return this.gapDegree <= 0 ? 360 : this.gapDegree
      },
      // 计算圈缺口位置
      calGapPosition() {
        let gapPosition = 0
        switch (this.gapPosition) {
          case 'bottom':
            gapPosition = 90;
            break;
          case 'left':
            gapPosition = 180;
            break;
          case 'top':
            gapPosition = 270;
            break;
          case 'right':
            gapPosition = 360;
            break;
        }
        return gapPosition
      },
    },

    watch: {
      // 监听百分比值改变
      percent(val) {
        this.drawStroke(val);
      }
    },

    mounted() {
      // 创建画布实例
      this.ctx = uni.createCanvasContext(this.cid, this)
      // upx转px 圈半径大小
      this.width2px = uni.upx2px(this.width)
      // 绘制初始 
      this.$nextTick(() => {
        this.drawStroke(this.percent)
      })
    },

    methods: {
      // 绘制圈
      drawStroke(percent) {
        percent = percent >= 100 ? 100 : percent < 0 ? 0 : percent
        // 圈条进度色
        let color = this.strokeColor || this.calStatus.color
        // 是否圈中心显示信息
        if (this.showInfo) {
          switch (this.calStatus.value) {
            case 1:
              if (percent >= 100) {
                // 设置打勾
                this.drawSuccess()
                percent = 100
                color = "#52c41a"
              } else {
                // 设置字体
                this.drawText(percent)
              }
              break;
            case 2:
              // 设置打勾
              this.drawSuccess()
              percent = 100
              color = "#52c41a"
              break;
            case 3:
              // 设置打叉
              this.drawException()
              percent = 0
              color = "#f5222d"
              break;
            default:
              break;
          }
        }
        // 缺口
        let gapPosition = this.calGapPosition
        let gapDegree = this.calGapDegree
        // 仪表固定
        if (this.type === "dashboard") {
          gapPosition = 135
          gapDegree = 270
        }
        // 圈型条宽
        this.ctx.setLineCap(this.strokeShape)
        this.ctx.setLineWidth(this.strokeWidth)
        // 位置原点
        this.ctx.translate(this.width2px, this.width2px)
        // 缺口方向 
        this.ctx.rotate(gapPosition * Math.PI / 180)
        // 圈底 
        this.ctx.beginPath()
        this.ctx.arc(0, 0, this.width2px - this.strokeWidth, 0, gapDegree * Math.PI / 180)
        this.ctx.setStrokeStyle(this.strokeBackground)
        this.ctx.stroke()
        // 圈进度 
        this.ctx.beginPath()
        this.ctx.arc(0, 0, this.width2px - this.strokeWidth, 0, percent * gapDegree * Math.PI / 18000)
        this.ctx.setStrokeStyle(color)
        this.ctx.stroke()
        // 绘制
        this.ctx.draw()
      },
      // 绘制文字格式
      drawText(percent) {
        this.ctx.beginPath()
        this.ctx.setFontSize(this.fontSize)
        this.ctx.setFillStyle(this.fontColor)
        this.ctx.setTextAlign('center')
        this.ctx.fillText(`${percent}%`, this.width2px, this.width2px + this.fontSize / 2)
        this.ctx.stroke()
      },
      // 绘制成功打勾
      drawSuccess() {
        let x = this.width2px - this.fontSize / 2
        let y = this.width2px + this.fontSize / 2
        this.ctx.beginPath()
        this.ctx.setLineCap('round')
        this.ctx.setLineWidth(this.fontSize / 4)
        this.ctx.moveTo(this.width2px, y)
        this.ctx.lineTo(y, x)
        this.ctx.moveTo(this.width2px, y)
        this.ctx.lineTo(x, this.width2px)
        this.ctx.setStrokeStyle("#52c41a")
        this.ctx.stroke()
      },
      // 绘制异常打叉
      drawException() {
        let x = this.width2px - this.fontSize / 2
        let y = this.width2px + this.fontSize / 2
        this.ctx.beginPath()
        this.ctx.setLineCap('round')
        this.ctx.setLineWidth(this.fontSize / 4)
        this.ctx.moveTo(x, x)
        this.ctx.lineTo(y, y)
        this.ctx.moveTo(y, x)
        this.ctx.lineTo(x, y)
        this.ctx.setStrokeStyle("#f5222d")
        this.ctx.stroke()
      }
    }
  };
</script>

<style>
  .cmd-circle {
    display: inline-block;
    box-sizing: border-box;
    list-style: none;
    margin: 0;
    padding: 0;
  }
</style>