Merge branch 'master' of http://182.92.203.7:2001/r/testbookLayout
| | |
| | | </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" |
| | |
| | | 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, |
New file |
| | |
| | | <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> |
New file |
| | |
| | | <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> |
New file |
| | |
| | | <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> |
New file |
| | |
| | | <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); |
| | | }, |
| | | |
| | | // 计算当前鼠标相对于canvas的坐标 |
| | | 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> |