<script setup lang="ts">
import { uploadFileApi } from "@/api/file";

const imgUrl = ref("");
const canvas = ref();
let ctx: CanvasRenderingContext2D;

// 正在绘制中，用来控制 move 和 end 事件
let painting = false;

// 获取触发点相对被触发dom的左、上角距离
const getOffset = (event: MouseEvent | TouchEvent) => {
  let offset: [number, number];
  if ((event as MouseEvent).offsetX) {
    // pc端
    const { offsetX, offsetY } = event as MouseEvent;
    offset = [offsetX, offsetY];
  } else {
    // 移动端
    const { top, left } = canvas.value.getBoundingClientRect();
    const offsetX = (event as TouchEvent).touches[0].clientX - left;
    const offsetY = (event as TouchEvent).touches[0].clientY - top;
    offset = [offsetX, offsetY];
  }

  return offset;
};

// 绘制起点
let startX = 0,
  startY = 0;

// 鼠标/触摸 按下时，保存 触发点相对被触发dom的左、上 距离
const onEventStart = (event: MouseEvent | TouchEvent) => {
  [startX, startY] = getOffset(event);
  painting = true;
};

const onEventMove = (event: MouseEvent | TouchEvent) => {
  if (painting) {
    // 鼠标/触摸 移动时，保存 移动点相对 被触发dom的左、上 距离
    const [endX, endY] = getOffset(event);
    paint(startX, startY, endX, endY, ctx);

    // 每次绘制 或 清除结束后，起点要重置为上次的终点
    startX = endX;
    startY = endY;
  }
};

const onEventEnd = () => {
  if (painting) {
    painting = false; // 停止绘制
  }
};

onMounted(() => {
  ctx = canvas.value.getContext("2d") as CanvasRenderingContext2D;
});
const handleToFile = async () => {
  if (isCanvasBlank(canvas.value)) {
    ElMessage({
      type: "warning",
      message: "当前签名文件为空",
    });
    return;
  }
  const file = dataURLtoFile(canvas.value.toDataURL(), "签名.png");

  if (!file) return;
  const { data } = await uploadFileApi(file);
  handleClearSign();
  imgUrl.value = data.url;
};
const handleClearSign = () => {
  ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
};
const isCanvasBlank = (canvas: HTMLCanvasElement) => {
  const blank = document.createElement("canvas"); //系统获取一个空canvas对象
  blank.width = canvas.width;
  blank.height = canvas.height;
  return canvas.toDataURL() == blank.toDataURL(); //比较值相等则为空
};

// 保存为图片
const handleSaveImg = () => {
  if (isCanvasBlank(canvas.value)) {
    ElMessage({
      type: "warning",
      message: "当前签名文件为空",
    });
    return;
  }
  const el = document.createElement("a");
  // 设置 href 为图片经过 base64 编码后的字符串，默认为 png 格式
  el.href = canvas.value.toDataURL();
  el.download = "签名";
  // 创建一个点击事件并对 a 标签进行触发
  const event = new MouseEvent("click");
  el.dispatchEvent(event);
};
// 转为file格式，可传递给后端
const dataURLtoFile = (dataurl: string, filename: string) => {
  const arr: string[] = dataurl.split(",");
  if (!arr.length) return;

  const mime = arr[0].match(/:(.*?);/);
  if (mime) {
    const bstr = atob(arr[1]);
    let n = bstr.length;
    const u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    return new File([u8arr], filename, { type: mime[1] });
  }
};
// canvas 画图
function paint(
  startX: number,
  startY: number,
  endX: number,
  endY: number,
  ctx: CanvasRenderingContext2D
) {
  ctx.beginPath();
  ctx.globalAlpha = 1;
  ctx.lineWidth = 2;
  ctx.strokeStyle = "#000";
  ctx.moveTo(startX, startY);
  ctx.lineTo(endX, endY);
  ctx.closePath();
  ctx.stroke();
}
</script>
<template>
  <div class="canvas-dom">
    <h3>基于canvas实现的签名组件</h3>
    <header>
      <el-button type="primary" @click="handleSaveImg">保存为图片</el-button>
      <el-button @click="handleToFile"> 保存到后端 </el-button>
      <el-button @click="handleClearSign"> 清空签名 </el-button>
    </header>
    <canvas
      ref="canvas"
      height="200"
      width="500"
      @mousedown="onEventStart"
      @mousemove.stop.prevent="onEventMove"
      @mouseup="onEventEnd"
      @touchstart="onEventStart"
      @touchmove.stop.prevent="onEventMove"
      @touchend="onEventEnd"
    >
    </canvas>
    <img v-if="imgUrl" :src="imgUrl" alt="签名" />
  </div>
</template>
<style scoped lang="scss">
.canvas-dom {
  width: 100%;
  height: 100%;
  padding: 0 20px;
  background-color: #fff;

  canvas {
    border: 1px solid #e6e6e6;
  }

  header {
    display: flex;
    flex-flow: row nowrap;
    align-items: center;
    width: 100%;
    margin: 8px;

    .eraser-option {
      display: flex;

      label {
        white-space: nowrap;
      }
    }
  }
}
</style>
