From 6c377a85f76fc4e15e1c86cecea4bf8fd2513532 Mon Sep 17 00:00:00 2001
From: unknown <qq1940665526@163.com>
Date: 星期二, 25 六月 2024 10:42:19 +0800
Subject: [PATCH] Merge branch 'master' of http://182.92.203.7:2001/r/testbookLayout

---
 src/books/mathBook/view/components/chapter001.vue  |   10 
 src/components/graffiti/components/toolBtns.vue    |   74 +++++++++
 src/components/graffiti/index.vue                  |  234 +++++++++++++++++++++++++++++
 src/components/graffiti/components/brushSize.vue   |   83 ++++++++++
 src/components/graffiti/components/colorPicker.vue |   61 +++++++
 5 files changed, 458 insertions(+), 4 deletions(-)

diff --git a/src/books/mathBook/view/components/chapter001.vue b/src/books/mathBook/view/components/chapter001.vue
index a9c0d5a..adbe2e5 100644
--- a/src/books/mathBook/view/components/chapter001.vue
+++ b/src/books/mathBook/view/components/chapter001.vue
@@ -575,9 +575,10 @@
         </div>
       </div>
     </div>
-    <!-- <div class="page-box padding-116" page="10">
-      <drag :question="dragQuestion" :page="10"/>
-    </div> -->
+    <div class="page-box padding-116" page="10">
+      <!-- <drag :question="dragQuestion" :page="10"/> -->
+       <graffiti style="width:100%" />
+    </div>
     <!-- 鍑芥暟鎺т欢寮圭獥 -->
     <el-dialog
       :visible.sync="dialogVisible"
@@ -823,9 +824,10 @@
 import { getResourcePath } from "@/assets/methods/resources";
 import { getCollectResource,setCollectResource } from "@/assets/methods/resources";
 import drag from '@/components/dragQuestion/index.vue'
+import graffiti from "@/components/graffiti/index.vue"
 export default {
   name: "chapter-one",
-  components: { examinations,drag },
+  components: { examinations,drag,graffiti },
   props: {
     showPageList: {
       type: Array,
diff --git a/src/components/graffiti/components/brushSize.vue b/src/components/graffiti/components/brushSize.vue
new file mode 100644
index 0000000..9c47a7c
--- /dev/null
+++ b/src/components/graffiti/components/brushSize.vue
@@ -0,0 +1,83 @@
+<template>
+  <div class="wrap-range">
+    <!-- 涓轰簡涓嶅湪瀛愮粍浠朵腑鍙樻洿鍊硷紝涓嶇敤v-model -->
+    <input
+      type="range"
+      :value="brushSize"
+      min="1"
+      max="30"
+      title="璋冩暣绗斿埛绮楃粏"
+      @change="(event) => $emit('change-size', +event.target.value)"
+    />
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    size: {
+      type: Number,
+      default: 5,
+    },
+  },
+  computed:{
+    brushSize() {
+      return this.size
+    }
+  }
+};
+
+// const brushSize = computed(() => props.size);
+</script>
+<style scoped>
+.wrap-range input {
+  width: 150px;
+  height: 20px;
+  margin: 0;
+  transform-origin: 75px 75px;
+  border-radius: 15px;
+  -webkit-appearance: none;
+  appearance: none;
+  outline: none;
+  position: relative;
+}
+
+.wrap-range input::after {
+  display: block;
+  content: "";
+  width: 0;
+  height: 0;
+  border: 5px solid transparent;
+  border-right: 150px solid #00ccff;
+  border-left-width: 0;
+  position: absolute;
+  left: 0;
+  top: 5px;
+  border-radius: 15px;
+  z-index: 0;
+}
+
+.wrap-range input[type="range"]::-webkit-slider-thumb,
+.wrap-range input[type="range"]::-moz-range-thumb {
+  -webkit-appearance: none;
+}
+
+.wrap-range input[type="range"]::-webkit-slider-runnable-track,
+.wrap-range input[type="range"]::-moz-range-track {
+  height: 10px;
+  border-radius: 10px;
+  box-shadow: none;
+}
+
+.wrap-range input[type="range"]::-webkit-slider-thumb {
+  -webkit-appearance: none;
+  height: 20px;
+  width: 20px;
+  margin-top: -1px;
+  background: #ffffff;
+  border-radius: 50%;
+  box-shadow: 0 0 8px #00ccff;
+  position: relative;
+  z-index: 999;
+}
+</style>
diff --git a/src/components/graffiti/components/colorPicker.vue b/src/components/graffiti/components/colorPicker.vue
new file mode 100644
index 0000000..4cde0de
--- /dev/null
+++ b/src/components/graffiti/components/colorPicker.vue
@@ -0,0 +1,61 @@
+<template>
+  <div>
+    <span
+      v-for="(color, index) of colorList"
+      class="color-item"
+      :class="{ active: colorSelected === color }"
+      :style="{ backgroundColor: color }"
+      :key="index"
+      @click="onChangeColor(color)"
+    ></span>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    color: {
+      type: String,
+    },
+  },
+  computed:{
+    colorSelected() {
+      return this.color
+    }
+  },
+  data() {
+    return {
+      colorList: [
+        "#000000",
+        "#808080",
+        "#FF3333",
+        "#0066FF",
+        "#FFFF33",
+        "#33CC66",
+      ],
+    };
+  },
+  methods: {
+    onChangeColor(color) {
+      this.$emit("change-color", color);
+    },
+  },
+};
+</script>
+
+<style scoped>
+.color-item {
+  display: inline-block;
+  width: 32px;
+  height: 32px;
+  margin: 0 4px;
+  box-sizing: border-box;
+  border: 4px solid white;
+  box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
+  cursor: pointer;
+  transition: 0.3s;
+}
+.color-item.active {
+  box-shadow: 0 0 15px #00ccff;
+}
+</style>
diff --git a/src/components/graffiti/components/toolBtns.vue b/src/components/graffiti/components/toolBtns.vue
new file mode 100644
index 0000000..aad1a3f
--- /dev/null
+++ b/src/components/graffiti/components/toolBtns.vue
@@ -0,0 +1,74 @@
+<template>
+  <div class="tools">
+    <button
+      v-for="(item, index) of toolList"
+      :key="index"
+      :class="{ active: toolSelected === item.name }"
+      :title="item.title"
+      @click="onChangeTool(item.name)"
+    >
+      <i :class="['iconfont', item.icon]"></i>
+    </button>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    tool: {
+      type: String,
+      default: "brush",
+    },
+  },
+  computed:{
+    toolSelected() {
+      return this.tool
+    }
+  },
+  data() {
+    return {
+      toolList: [
+        { name: "brush", title: "鐢荤瑪", icon: "icon-qianbi" },
+        { name: "eraser", title: "姗$毊鎿�", icon: "icon-xiangpi" },
+        { name: "clear", title: "娓呯┖", icon: "icon-qingchu" },
+        { name: "undo", title: "鎾ら攢", icon: "icon-chexiao" },
+        { name: "save", title: "淇濆瓨", icon: "icon-fuzhi" },
+      ],
+    };
+  },
+  methods: {
+    onChangeTool(tool) {
+      this.$emit("change-tool", tool);
+    },
+  },
+};
+</script>
+
+<style scoped>
+.tools button {
+  /* border-radius: 50%; */
+  width: 32px;
+  height: 32px;
+  background-color: rgba(255, 255, 255, 0.7);
+  border: 1px solid #eee;
+  outline: none;
+  cursor: pointer;
+  box-sizing: border-box;
+  margin: 0 8px;
+  padding: 0;
+  text-align: center;
+  color: #ccc;
+  box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
+  transition: 0.3s;
+}
+
+.tools button.active,
+.tools button:active {
+  /* box-shadow: 0 0 15px #00CCFF; */
+  color: #00ccff;
+}
+
+.tools button i {
+  font-size: 20px;
+}
+</style>
diff --git a/src/components/graffiti/index.vue b/src/components/graffiti/index.vue
new file mode 100644
index 0000000..8980a01
--- /dev/null
+++ b/src/components/graffiti/index.vue
@@ -0,0 +1,234 @@
+<template>
+  <div class="page">
+    <div class="main">
+      <div id="canvas_panel">
+        <canvas
+          id="canvas"
+          :style="{
+            backgroundImage: `url(${backgroundImage})`,
+            backgroundSize: 'cover',
+            backgroundPosition: 'center',
+          }"
+          >褰撳墠娴忚鍣ㄤ笉鏀寔canvas銆�</canvas
+        >
+      </div>
+    </div>
+    <div class="footer">
+      <BrushSize :size="brushSize" @change-size="onChangeSize" />
+      <ColorPicker :color="brushColor" @change-color="onChangeColor" />
+      <ToolBtns :tool="brushTool" @change-tool="onChangeTool" />
+    </div>
+  </div>
+</template>
+
+<script>
+import BrushSize from "./components/brushSize.vue";
+import ColorPicker from "./components/colorPicker.vue";
+import ToolBtns from "./components/toolBtns.vue";
+export default {
+  name: "graffiti",
+  components: { BrushSize, ColorPicker, ToolBtns },
+  data() {
+    return {
+      canvas: null,
+      context: null,
+      painting: false,
+      historyData: [], // 瀛樺偍鍘嗗彶鏁版嵁锛岀敤浜庢挙閿�
+      brushSize: 5, // 绗斿埛澶у皬
+      brushColor: "#000000", // 绗斿埛棰滆壊
+      brushTool: "brush",
+      canvasOffset: {
+        left: 0,
+        top: 0,
+      },
+      backgroundImage:
+        "https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF",
+    };
+  },
+  mounted() {
+    this.canvas = document.getElementById("canvas");
+    if (this.canvas.getContext) {
+      this.context = this.canvas.getContext("2d", { willReadFrequently: true });
+      this.initCanvas();
+      // window.addEventListener('resize', updateCanvasPosition);
+      window.addEventListener("scroll", this.updateCanvasOffset); // 娣诲姞婊氬姩鏉℃粴鍔ㄤ簨浠剁洃鍚櫒
+      this.getCanvasOffset();
+      this.context.lineGap = "round";
+      this.context.lineJoin = "round";
+      this.canvas.addEventListener("mousedown", this.downCallback);
+      this.canvas.addEventListener("mousemove", this.moveCallback);
+      this.canvas.addEventListener("mouseup", this.closePaint);
+      this.canvas.addEventListener("mouseleave", this.closePaint);
+    }
+    this.toolClear();
+  },
+  methods: {
+    changeBackground(imgUrl) {
+      this.backgroundImage = imgUrl;
+    },
+    initCanvas() {
+      const that = this
+      function resetCanvas() {
+        const elPanel = document.getElementById("canvas_panel");
+        that.canvas.width = elPanel.clientWidth;
+        that.canvas.height = elPanel.clientHeight;
+        that.context = that.canvas.getContext("2d", {
+          willReadFrequently: true,
+        }); // 娣诲姞杩欎竴琛�
+        that.context.fillStyle = "white";
+        that.context.fillRect(0, 0, that.canvas.width, that.canvas.height);
+        that.context.fillStyle = "black";
+        that.getCanvasOffset(); // 鏇存柊鐢诲竷浣嶇疆
+      }
+
+      resetCanvas();
+      window.addEventListener("resize", resetCanvas);
+    },
+
+    // 鑾峰彇canvas鐨勫亸绉诲��
+    getCanvasOffset() {
+      const rect = this.canvas.getBoundingClientRect();
+      this.canvasOffset.left = rect.left * (this.canvas.width / rect.width); // 鍏煎缂╂斁鍦烘櫙
+      this.canvasOffset.top = rect.top * (this.canvas.height / rect.height);
+    },
+
+    // 璁$畻褰撳墠榧犳爣鐩稿浜巆anvas鐨勫潗鏍�
+    calcRelativeCoordinate(x, y) {
+      return {
+        x: x - this.canvasOffset.left,
+        y: y - this.canvasOffset.top,
+      };
+    },
+
+    downCallback(event) {
+      // 鍏堜繚瀛樹箣鍓嶇殑鏁版嵁锛岀敤浜庢挙閿�鏃舵仮澶嶏紙缁樺埗鍓嶄繚瀛橈紝涓嶆槸缁樺埗鍚庡啀淇濆瓨锛�
+      const data = this.context.getImageData(
+        0,
+        0,
+        this.canvas.width,
+        this.canvas.height
+      );
+      this.saveData(data);
+      const { clientX, clientY } = event;
+      const { x, y } = this.calcRelativeCoordinate(clientX, clientY);
+      this.context.beginPath();
+      this.context.moveTo(x, y);
+      this.context.lineWidth = this.brushSize;
+      this.context.strokeStyle =
+        this.brushTool === "eraser" ? "#FFFFFF" : this.brushColor;
+      this.painting = true;
+    },
+
+    moveCallback(event) {
+      if (!this.painting) {
+        return;
+      }
+      const { clientX, clientY } = event;
+      const { x, y } = this.calcRelativeCoordinate(clientX, clientY);
+      this.context.lineTo(x, y);
+      this.context.stroke();
+    },
+    closePaint() {
+      this.painting = false;
+    },
+    updateCanvasOffset() {
+      this.getCanvasOffset(); // 閲嶆柊璁$畻鐢诲竷鐨勫亸绉诲��
+    },
+
+    onChangeSize(size) {
+      this.brushSize = size;
+    },
+    onChangeColor(color) {
+      this.brushColor = color;
+    },
+    onChangeTool(tool) {
+      this.brushTool = tool;
+      switch (tool) {
+        case "clear":
+          this.toolClear();
+          break;
+        case "undo":
+          this.toolUndo();
+          break;
+        case "save":
+          this.toolSave();
+          break;
+      }
+    },
+    toolClear() {
+      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
+      this.resetToolActive();
+    },
+    toolSave() {
+      const imageDataUrl = this.canvas.toDataURL("image/png");
+      console.log(imageDataUrl);
+      // const imgUrl = canvas.toDataURL('image/png');
+      // const el = document.createElement('a');
+      // el.setAttribute('href', imgUrl);
+      // el.setAttribute('target', '_blank');
+      // el.setAttribute('download', `graffiti-${Date.now()}`);
+      // document.body.appendChild(el);
+      // el.click();
+      // document.body.removeChild(el);
+      // resetToolActive();
+    },
+    toolUndo() {
+      if (this.historyData.length <= 0) {
+        this.resetToolActive();
+        return;
+      }
+      const lastIndex = this.historyData.length - 1;
+      this.context.putImageData(this.historyData[lastIndex], 0, 0);
+      this.historyData.pop();
+
+      this.resetToolActive();
+    },
+    // 瀛樺偍鏁版嵁
+    saveData(data) {
+      this.historyData.length >= 50 && this.historyData.shift(); // 璁剧疆鍌ㄥ瓨涓婇檺涓�50姝�
+      this.historyData.push(data);
+    },
+    // 娓呴櫎銆佹挙閿�銆佷繚瀛樼姸鎬佷笉闇�瑕佷繚鎸侊紝鎿嶄綔瀹屽悗鎭㈠绗斿埛鐘舵��
+    resetToolActive() {
+      setTimeout(() => {
+        this.brushTool = "brush";
+      }, 1000);
+    },
+  },
+};
+</script>
+
+<style scoped>
+.page {
+  display: flex;
+  flex-direction: column;
+  width: 1038px;
+  height: 866px;
+}
+
+.main {
+  flex: 1;
+}
+
+.footer {
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  height: 88px;
+  background-color: #fff;
+}
+
+#canvas_panel {
+  margin: 12px;
+  height: calc(100% - 24px);
+  /* 娑堥櫎绌烘牸褰卞搷 */
+  font-size: 0;
+  background-color: #fff;
+  box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
+}
+
+#canvas {
+  cursor: crosshair;
+  /* background: url('https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF') no-repeat !important; */
+}
+</style>

--
Gitblit v1.9.1