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