深度画布-平铺设置
This commit is contained in:
@@ -36,9 +36,10 @@
|
|||||||
step: { default: 0.1, type: Number }
|
step: { default: 0.1, type: Number }
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['export', 'export-local', 'import-local', 'export-close'])
|
const emit = defineEmits(['export', 'export-local', 'import-local', 'export-close'])
|
||||||
const importLocalImage = inject('importLocalImage') as () => void
|
const importLocalImage = inject('importLocalImage') as (isRecord?: boolean) => void
|
||||||
const stateManager = inject('stateManager') as any
|
const stateManager = inject('stateManager') as any
|
||||||
const toolManager = inject('toolManager') as any
|
const toolManager = inject('toolManager') as any
|
||||||
|
const objectManager = inject('objectManager') as any
|
||||||
const tool = computed(() => toolManager.currentTool.value)
|
const tool = computed(() => toolManager.currentTool.value)
|
||||||
const historyIndex = computed(() => stateManager.historyIndex.value)
|
const historyIndex = computed(() => stateManager.historyIndex.value)
|
||||||
const historyList = computed(() => stateManager.historyList.value)
|
const historyList = computed(() => stateManager.historyList.value)
|
||||||
@@ -54,7 +55,7 @@
|
|||||||
icon: 'dc-image',
|
icon: 'dc-image',
|
||||||
iconSize: 17,
|
iconSize: 17,
|
||||||
disabled: ref(false),
|
disabled: ref(false),
|
||||||
on: () => importLocalImage()
|
on: () => onImageClick()
|
||||||
},
|
},
|
||||||
{ name: OperationType.SELECTBOX, icon: 'dc-selectbox', iconSize: 16, disabled: ref(false) },
|
{ name: OperationType.SELECTBOX, icon: 'dc-selectbox', iconSize: 16, disabled: ref(false) },
|
||||||
{ name: OperationType.RECTANGLE, icon: 'dc-rectangle', iconSize: 16, disabled: ref(false) },
|
{ name: OperationType.RECTANGLE, icon: 'dc-rectangle', iconSize: 16, disabled: ref(false) },
|
||||||
@@ -82,6 +83,10 @@
|
|||||||
toolManager.setTool(tool.name)
|
toolManager.setTool(tool.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const onImageClick = async () => {
|
||||||
|
const layer = await importLocalImage(false)
|
||||||
|
objectManager.setFillRepeat(layer?.info?.id)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
<template>
|
||||||
|
<div class="fill-repeat">
|
||||||
|
<div>
|
||||||
|
<div class="title">Image</div>
|
||||||
|
<div class="content">
|
||||||
|
<img :src="object.info.fill.source" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="title">Sttings</div>
|
||||||
|
<div class="content">
|
||||||
|
<div>
|
||||||
|
<div class="label">Rotation</div>
|
||||||
|
<div class="value">
|
||||||
|
<depth-input
|
||||||
|
v-model="angle"
|
||||||
|
type="number"
|
||||||
|
@input="inputFillTransform"
|
||||||
|
@change="changeFillTransform"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">Scale</div>
|
||||||
|
<div class="value">
|
||||||
|
<depth-slider
|
||||||
|
v-model="scale"
|
||||||
|
:min="10"
|
||||||
|
:max="1000"
|
||||||
|
:tipFormatter="(v) => v + '%'"
|
||||||
|
@input="inputFillTransform"
|
||||||
|
@change="changeFillTransform"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">Gap X</div>
|
||||||
|
<div class="value">
|
||||||
|
<depth-slider
|
||||||
|
v-model="gapX"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:tipFormatter="(v) => v + 'px'"
|
||||||
|
@input="inputFillGap"
|
||||||
|
@change="changeFillGap"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">Gap Y</div>
|
||||||
|
<div class="value">
|
||||||
|
<depth-slider
|
||||||
|
v-model="gapY"
|
||||||
|
:min="0"
|
||||||
|
:max="1000"
|
||||||
|
:tipFormatter="(v) => v + 'px'"
|
||||||
|
@input="inputFillGap"
|
||||||
|
@change="changeFillGap"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="label">Offset</div>
|
||||||
|
<div class="value">
|
||||||
|
<depth-offset-tool
|
||||||
|
v-model="offset"
|
||||||
|
:show-dish="false"
|
||||||
|
@input="inputFillTransform"
|
||||||
|
@change="changeFillTransform"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="offset-tool-dish">
|
||||||
|
<depth-offset-tool
|
||||||
|
v-model="offset"
|
||||||
|
:show-input="false"
|
||||||
|
@input="inputFillTransform"
|
||||||
|
@change="changeFillTransform"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, inject, computed, nextTick, onBeforeUnmount } from 'vue'
|
||||||
|
import DepthOffsetTool from '../tools/depth-offset-tool.vue'
|
||||||
|
import { getTransformScaleAngle } from '../../manager/ObjectManager'
|
||||||
|
const objectManager = inject('objectManager') as any
|
||||||
|
const stateManager = inject('stateManager') as any
|
||||||
|
const props = defineProps({
|
||||||
|
object: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const id = computed(() => props.object.info.id)
|
||||||
|
const angle = ref(0)
|
||||||
|
const scale = ref(100)
|
||||||
|
const gapX = ref(0)
|
||||||
|
const gapY = ref(0)
|
||||||
|
const offset = ref({ x: 0, y: 0 })
|
||||||
|
|
||||||
|
const updateData = async () => {
|
||||||
|
await nextTick()
|
||||||
|
const fill = props.object.fill
|
||||||
|
angle.value = getTransformScaleAngle(fill?.patternTransform).angle
|
||||||
|
scale.value = Math.round(getTransformScaleAngle(fill?.patternTransform).scale * 100)
|
||||||
|
gapX.value = props.object.info.fill.gapX
|
||||||
|
gapY.value = props.object.info.fill.gapY
|
||||||
|
offset.value = {
|
||||||
|
x: Math.round((fill.offsetX / props.object.width) * 100),
|
||||||
|
y: Math.round((fill.offsetY / props.object.height) * 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateData()
|
||||||
|
|
||||||
|
const inputFillTransform = () => setFillTransform(false)
|
||||||
|
const changeFillTransform = () => setFillTransform(true)
|
||||||
|
const setFillTransform = (isRecord: boolean) => {
|
||||||
|
const options = {
|
||||||
|
scale: scale.value / 100,
|
||||||
|
angle: angle.value,
|
||||||
|
offsetX: props.object.width * (offset.value.x / 100),
|
||||||
|
offsetY: props.object.height * (offset.value.y / 100)
|
||||||
|
}
|
||||||
|
objectManager.updateFillRepeatTransform(id.value, options, isRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputFillGap = () => setFillGap(false)
|
||||||
|
const changeFillGap = () => setFillGap(true)
|
||||||
|
const setFillGap = (isRecord: boolean) => {
|
||||||
|
const options = {
|
||||||
|
gapX: gapX.value,
|
||||||
|
gapY: gapY.value
|
||||||
|
}
|
||||||
|
objectManager.updateFillRepeatGap(id.value, options, isRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
stateManager.event.add('canvas:undo', updateData)
|
||||||
|
stateManager.event.add('canvas:redo', updateData)
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
stateManager.event.remove('canvas:undo', updateData)
|
||||||
|
stateManager.event.remove('canvas:redo', updateData)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.fill-repeat {
|
||||||
|
> div {
|
||||||
|
> .content {
|
||||||
|
> img {
|
||||||
|
display: block;
|
||||||
|
width: 55%;
|
||||||
|
height: auto;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 1.6rem;
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
> .label {
|
||||||
|
min-width: 6rem;
|
||||||
|
margin-right: 0.8rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
> .value {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .offset-tool-dish {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,14 +7,16 @@
|
|||||||
<svg-icon name="dc-down_arrow2" size="10" />
|
<svg-icon name="dc-down_arrow2" size="10" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="content" v-if="activeObject" v-show="show">
|
<div class="content" v-if="isShow" v-show="show">
|
||||||
<basic-info :object="activeObject" />
|
<!-- <basic-info :object="activeObject" /> -->
|
||||||
|
<fill-repeat :object="activeObject" v-if="isRepeat" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, inject, computed, watch, onMounted } from 'vue'
|
import { ref, inject, computed, watch, onMounted } from 'vue'
|
||||||
|
import FillRepeat from './fill-repeat.vue'
|
||||||
const props = defineProps({})
|
const props = defineProps({})
|
||||||
const layerManager = inject('layerManager') as any
|
const layerManager = inject('layerManager') as any
|
||||||
const canvasManager = inject('canvasManager') as any
|
const canvasManager = inject('canvasManager') as any
|
||||||
@@ -22,6 +24,10 @@
|
|||||||
const activeID = computed(() => layerManager.activeID.value)
|
const activeID = computed(() => layerManager.activeID.value)
|
||||||
const layers = computed(() => layerManager.layers.value)
|
const layers = computed(() => layerManager.layers.value)
|
||||||
const activeObject = ref(null)
|
const activeObject = ref(null)
|
||||||
|
|
||||||
|
const isRepeat = computed(() => activeObject.value?.fill?.repeat === 'repeat')
|
||||||
|
const isShow = computed(() => isRepeat.value)
|
||||||
|
|
||||||
const updateActiveObject = () => {
|
const updateActiveObject = () => {
|
||||||
const obj = layers.value.find((v: any) => v.info.id === activeID.value)
|
const obj = layers.value.find((v: any) => v.info.id === activeID.value)
|
||||||
activeObject.value = obj ? JSON.parse(JSON.stringify(obj)) : null
|
activeObject.value = obj ? JSON.parse(JSON.stringify(obj)) : null
|
||||||
@@ -75,7 +81,23 @@
|
|||||||
}
|
}
|
||||||
> .content {
|
> .content {
|
||||||
padding: 1.6rem;
|
padding: 1.6rem;
|
||||||
|
overflow: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
&:deep(> div) {
|
||||||
|
> div {
|
||||||
|
margin-bottom: 1.6rem;
|
||||||
|
> .title {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #000;
|
||||||
|
margin-bottom: 1.6rem;
|
||||||
|
}
|
||||||
|
> .content {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
padding: 0 1.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,7 +5,14 @@
|
|||||||
<svg-icon :name="icon" :size="iconSize" size-unit="px" />
|
<svg-icon :name="icon" :size="iconSize" size-unit="px" />
|
||||||
</span>
|
</span>
|
||||||
<span v-show="before" class="before">{{ before }}</span>
|
<span v-show="before" class="before">{{ before }}</span>
|
||||||
<input v-bind="attrs" :value="modelValue" @input="onInput" @copy.stop @keydown.stop />
|
<input
|
||||||
|
v-bind="attrs"
|
||||||
|
:value="modelValue"
|
||||||
|
@input="onInput"
|
||||||
|
@change="onChange"
|
||||||
|
@copy.stop
|
||||||
|
@keydown.stop
|
||||||
|
/>
|
||||||
<span v-show="after" class="after">{{ after }}</span>
|
<span v-show="after" class="after">{{ after }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -20,13 +27,19 @@
|
|||||||
after: { default: '', type: String }
|
after: { default: '', type: String }
|
||||||
})
|
})
|
||||||
const attrs = useAttrs()
|
const attrs = useAttrs()
|
||||||
const emit = defineEmits(['update:modelValue', 'input'])
|
const emit = defineEmits(['update:modelValue', 'input', 'change'])
|
||||||
const onInput = (e) => {
|
const onInput = (e) => {
|
||||||
var value = e.target.value
|
var value = e.target.value
|
||||||
if (attrs.type === 'number') value = Number(value)
|
if (attrs.type === 'number') value = Number(value)
|
||||||
emit('update:modelValue', value)
|
emit('update:modelValue', value)
|
||||||
emit('input', value)
|
emit('input', value)
|
||||||
}
|
}
|
||||||
|
const onChange = (e) => {
|
||||||
|
var value = e.target.value
|
||||||
|
if (attrs.type === 'number') value = Number(value)
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
emit('change', value)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.depth-input {
|
.depth-input {
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="depth-offset-tool">
|
<div class="depth-offset-tool">
|
||||||
<div class="input" v-show="showInput">
|
<div class="input" v-show="showInput">
|
||||||
<depth-input v-model="left" type="number" before="X" after="%" :min="-100" :max="100" />
|
<depth-input
|
||||||
<depth-input v-model="top" type="number" before="Y" after="%" :min="-100" :max="100" />
|
v-model="left"
|
||||||
|
type="number"
|
||||||
|
before="X"
|
||||||
|
after="%"
|
||||||
|
:min="-100"
|
||||||
|
:max="100"
|
||||||
|
@input="onInput"
|
||||||
|
@change="onChange"
|
||||||
|
/>
|
||||||
|
<depth-input
|
||||||
|
v-model="top"
|
||||||
|
type="number"
|
||||||
|
before="Y"
|
||||||
|
after="%"
|
||||||
|
:min="-100"
|
||||||
|
:max="100"
|
||||||
|
@input="onInput"
|
||||||
|
@change="onChange"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="dish"
|
class="dish"
|
||||||
@@ -103,7 +121,7 @@
|
|||||||
}
|
}
|
||||||
emit('update:modelValue', value)
|
emit('update:modelValue', value)
|
||||||
emit('change', value)
|
emit('change', value)
|
||||||
}, 500)
|
}, 100)
|
||||||
}
|
}
|
||||||
const lineZStyle = computed(() => ({
|
const lineZStyle = computed(() => ({
|
||||||
'--rotateZ': calculateAngle(0, 0, left.value, top.value) + 'deg',
|
'--rotateZ': calculateAngle(0, 0, left.value, top.value) + 'deg',
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
import { CanvasManager } from './manager/CanvasManager'
|
import { CanvasManager } from './manager/CanvasManager'
|
||||||
import { ToolManager } from './manager/ToolManager'
|
import { ToolManager } from './manager/ToolManager'
|
||||||
import { KeyEventManager } from './manager/events/KeyEventManager'
|
import { KeyEventManager } from './manager/events/KeyEventManager'
|
||||||
|
import { ObjectManager } from './manager/ObjectManager'
|
||||||
|
|
||||||
const emit = defineEmits(['close'])
|
const emit = defineEmits(['close'])
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -80,6 +81,11 @@
|
|||||||
stateManager.setManager({ keyEventManager })
|
stateManager.setManager({ keyEventManager })
|
||||||
provide('keyEventManager', keyEventManager)
|
provide('keyEventManager', keyEventManager)
|
||||||
|
|
||||||
|
// 对象管理器
|
||||||
|
const objectManager = new ObjectManager({ stateManager, canvasManager, layerManager })
|
||||||
|
stateManager.setManager({ objectManager })
|
||||||
|
provide('objectManager', objectManager)
|
||||||
|
|
||||||
const observer = ref(null)
|
const observer = ref(null)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
canvasManager.initCanvas({
|
canvasManager.initCanvas({
|
||||||
@@ -89,11 +95,12 @@
|
|||||||
canvasWidth: 750,
|
canvasWidth: 750,
|
||||||
canvasHeight: 600
|
canvasHeight: 600
|
||||||
})
|
})
|
||||||
stateManager?.onMounted?.()
|
stateManager.onMounted()
|
||||||
canvasManager?.onMounted?.()
|
canvasManager.onMounted()
|
||||||
layerManager?.onMounted?.()
|
layerManager.onMounted()
|
||||||
toolManager?.onMounted?.()
|
toolManager.onMounted()
|
||||||
keyEventManager?.onMounted?.()
|
keyEventManager.onMounted()
|
||||||
|
objectManager.onMounted()
|
||||||
|
|
||||||
const trailingTimeout = ref(null)
|
const trailingTimeout = ref(null)
|
||||||
observer.value = new ResizeObserver((entries) => {
|
observer.value = new ResizeObserver((entries) => {
|
||||||
@@ -113,6 +120,7 @@
|
|||||||
layerManager.dispose()
|
layerManager.dispose()
|
||||||
toolManager.dispose()
|
toolManager.dispose()
|
||||||
keyEventManager.dispose()
|
keyEventManager.dispose()
|
||||||
|
objectManager.dispose()
|
||||||
})
|
})
|
||||||
async function handleWindowResize() {
|
async function handleWindowResize() {
|
||||||
console.log('==========画布窗口大小变化==========')
|
console.log('==========画布窗口大小变化==========')
|
||||||
@@ -123,7 +131,8 @@
|
|||||||
canvasManager.resetZoom()
|
canvasManager.resetZoom()
|
||||||
}
|
}
|
||||||
/** 导入本地图片 */
|
/** 导入本地图片 */
|
||||||
const importLocalImage = () => {
|
const importLocalImage = (isRecord = true) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
const input = document.createElement('input')
|
const input = document.createElement('input')
|
||||||
input.type = 'file'
|
input.type = 'file'
|
||||||
input.accept = 'image/*'
|
input.accept = 'image/*'
|
||||||
@@ -136,11 +145,12 @@
|
|||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
toolManager.setTool(OperationType.SELECT)
|
toolManager.setTool(OperationType.SELECT)
|
||||||
const url = reader.result as string
|
const url = reader.result as string
|
||||||
layerManager.createImageLayer(url, {
|
layerManager
|
||||||
info: { name: file.name }
|
.createImageLayer(url, { info: { name: file.name } }, isRecord)
|
||||||
})
|
.then((v) => resolve(v))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
provide('importLocalImage', importLocalImage)
|
provide('importLocalImage', importLocalImage)
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export class CanvasManager {
|
|||||||
this.deviceInfo = detectDeviceType();
|
this.deviceInfo = detectDeviceType();
|
||||||
this.currentZoom = ref(100)
|
this.currentZoom = ref(100)
|
||||||
}
|
}
|
||||||
|
onMounted() { }
|
||||||
setCanvasViewSize(options) {
|
setCanvasViewSize(options) {
|
||||||
this.canvasViewWidth = options.canvasViewWidth || 1920
|
this.canvasViewWidth = options.canvasViewWidth || 1920
|
||||||
this.canvasViewHeight = options.canvasViewHeight || 1080
|
this.canvasViewHeight = options.canvasViewHeight || 1080
|
||||||
@@ -106,15 +107,15 @@ export class CanvasManager {
|
|||||||
// this.stateManager.toolManager.setTool(OperationType.RECTANGLE)
|
// this.stateManager.toolManager.setTool(OperationType.RECTANGLE)
|
||||||
}
|
}
|
||||||
/** 画布添加对象 */
|
/** 画布添加对象 */
|
||||||
async add(obj: any, isUpdate = true) {
|
async add(obj: any, isRecord = true) {
|
||||||
this.canvas.add(obj)
|
this.canvas.add(obj)
|
||||||
const id = obj?.info?.id || ""
|
const id = obj?.info?.id || ""
|
||||||
if (isUpdate) {
|
if (id) {
|
||||||
this.layerManager.updateLayers()
|
this.layerManager.updateLayers()
|
||||||
this.renderAll()
|
this.renderAll()
|
||||||
if (id) await this.layerManager.updateLayerThumbnailsById(id)
|
await this.layerManager.updateLayerThumbnailsById(id)
|
||||||
this.stateManager.recordState()
|
|
||||||
}
|
}
|
||||||
|
if (isRecord) this.stateManager.recordState()
|
||||||
}
|
}
|
||||||
/** 画布移除对象 */
|
/** 画布移除对象 */
|
||||||
remove(obj: any, isUpdate = true) {
|
remove(obj: any, isUpdate = true) {
|
||||||
@@ -149,26 +150,22 @@ export class CanvasManager {
|
|||||||
}
|
}
|
||||||
// 使用动画管理器的缩放方法
|
// 使用动画管理器的缩放方法
|
||||||
animateZoom(point, targetZoom, options = {}) {
|
animateZoom(point, targetZoom, options = {}) {
|
||||||
console.log(point, targetZoom, options)
|
|
||||||
this.animationManager.animateZoom(point, targetZoom, options);
|
this.animationManager.animateZoom(point, targetZoom, options);
|
||||||
}
|
}
|
||||||
zoomIn() {
|
zoomIn() {
|
||||||
const currentZoom = this.canvas.getZoom()
|
const currentZoom = this.canvas.getZoom()
|
||||||
const newZoom = Math.min(currentZoom + 0.1, 20) // 增加20%,最大20倍
|
const newZoom = Math.min(currentZoom + 0.1, 20) // 增加20%,最大20倍
|
||||||
|
|
||||||
// 使用画布中心作为缩放点
|
// 使用画布中心作为缩放点
|
||||||
const centerPoint = {
|
const centerPoint = {
|
||||||
x: this.canvas.width / 2,
|
x: this.canvas.width / 2,
|
||||||
y: this.canvas.height / 2
|
y: this.canvas.height / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
this.animateZoom(centerPoint, newZoom)
|
this.animateZoom(centerPoint, newZoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOut() {
|
zoomOut() {
|
||||||
const currentZoom = this.canvas.getZoom()
|
const currentZoom = this.canvas.getZoom()
|
||||||
const newZoom = Math.max(currentZoom - 0.1, 0.1) // 减少20%,最小0.1倍
|
const newZoom = Math.max(currentZoom - 0.1, 0.1) // 减少20%,最小0.1倍
|
||||||
|
|
||||||
// 使用画布中心作为缩放点
|
// 使用画布中心作为缩放点
|
||||||
const centerPoint = {
|
const centerPoint = {
|
||||||
x: this.canvas.width / 2,
|
x: this.canvas.width / 2,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export class LayerManager {
|
|||||||
this.layers = ref([])
|
this.layers = ref([])
|
||||||
this.activeID = ref("")
|
this.activeID = ref("")
|
||||||
}
|
}
|
||||||
|
onMounted() { }
|
||||||
setActiveID(id: string, isActive = true) {
|
setActiveID(id: string, isActive = true) {
|
||||||
this.activeID.value = id
|
this.activeID.value = id
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
@@ -89,7 +90,7 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/** 创建空图层 */
|
/** 创建空图层 */
|
||||||
createEmptyLayer(isUpdate = true) {
|
createEmptyLayer(isRecord = true) {
|
||||||
const emptyObject = new fabric.Rect({
|
const emptyObject = new fabric.Rect({
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
@@ -100,7 +101,7 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setLayerPosition(emptyObject)
|
this.setLayerPosition(emptyObject)
|
||||||
this.canvasManager.add(emptyObject, isUpdate)
|
this.canvasManager.add(emptyObject, isRecord)
|
||||||
return emptyObject
|
return emptyObject
|
||||||
}
|
}
|
||||||
/** 创建文本图层 */
|
/** 创建文本图层 */
|
||||||
@@ -155,7 +156,7 @@ export class LayerManager {
|
|||||||
return circleObject
|
return circleObject
|
||||||
}
|
}
|
||||||
/** 创建图片图层 */
|
/** 创建图片图层 */
|
||||||
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any) {
|
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true) {
|
||||||
const canvasWidth = this.canvasManager.canvasWidth
|
const canvasWidth = this.canvasManager.canvasWidth
|
||||||
const canvasHeight = this.canvasManager.canvasHeight
|
const canvasHeight = this.canvasManager.canvasHeight
|
||||||
|
|
||||||
@@ -181,13 +182,11 @@ export class LayerManager {
|
|||||||
})
|
})
|
||||||
}) as fabric.Object
|
}) as fabric.Object
|
||||||
this.setLayerPosition(imageObject, options)
|
this.setLayerPosition(imageObject, options)
|
||||||
await this.canvasManager.add(imageObject)
|
await this.canvasManager.add(imageObject, isRecord)
|
||||||
this.setActiveID(imageObject.info.id)
|
this.setActiveID(imageObject.info.id)
|
||||||
return imageObject
|
return imageObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** 合并图层 */
|
/** 合并图层 */
|
||||||
async imageMergeToLayer(targetLayer: fabric.Object, fabricImage: fabric.Object) {
|
async imageMergeToLayer(targetLayer: fabric.Object, fabricImage: fabric.Object) {
|
||||||
const info = await exportObjectsToImage([targetLayer, fabricImage], true)
|
const info = await exportObjectsToImage([targetLayer, fabricImage], true)
|
||||||
|
|||||||
196
src/components/Canvas/DepthCanvas/manager/ObjectManager.ts
Normal file
196
src/components/Canvas/DepthCanvas/manager/ObjectManager.ts
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { fabric } from 'fabric-with-all'
|
||||||
|
import { createId } from '../../tools/tools'
|
||||||
|
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
|
||||||
|
import { OperationType } from '../tools/layerHelper'
|
||||||
|
|
||||||
|
export const FillSourceToBase64 = (source) => {
|
||||||
|
if (source?.toDataURL) {
|
||||||
|
return source.toDataURL?.();
|
||||||
|
} else if (source?.src) {
|
||||||
|
return source.src;
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片添加gap转换
|
||||||
|
* @param {HTMLCanvasElement} image - img元素
|
||||||
|
* @param {Number} gapX - 水平gap
|
||||||
|
* @param {Number} gapY - 垂直gap
|
||||||
|
* @returns {HTMLCanvasElement} 转换后的canvas元素
|
||||||
|
*/
|
||||||
|
export function imageAddGapToCanvas(image, gapX, gapY) {
|
||||||
|
// 创建透明 Canvas
|
||||||
|
const tcanvas = document.createElement('canvas');
|
||||||
|
tcanvas.width = image.width + gapX;
|
||||||
|
tcanvas.height = image.height + gapY;
|
||||||
|
const ctx = tcanvas.getContext('2d', { willReadFrequently: true });
|
||||||
|
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
|
||||||
|
ctx.drawImage(image, 0, 0);
|
||||||
|
return tcanvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建缩放+旋转的变换矩阵
|
||||||
|
* @param {number} scale - 缩放比例
|
||||||
|
* @param {number} angle - 旋转角度(度)
|
||||||
|
* @returns {Array} 变换矩阵 [a, b, c, d, e, f]
|
||||||
|
*/
|
||||||
|
export function createPatternTransform(scale, angle) {
|
||||||
|
const angle_ = angle * Math.PI / 180;
|
||||||
|
const cos = Math.cos(angle_);
|
||||||
|
const sin = Math.sin(angle_);
|
||||||
|
|
||||||
|
// 先缩放,后旋转
|
||||||
|
return [
|
||||||
|
scale * cos, // a
|
||||||
|
scale * sin, // b
|
||||||
|
-scale * sin, // c
|
||||||
|
scale * cos, // d
|
||||||
|
0, // e (水平位移)
|
||||||
|
0 // f (垂直位移)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取变换矩阵的缩放、旋转
|
||||||
|
* @param {Array} Transform - 变换矩阵、
|
||||||
|
* @returns {Object} 缩放、旋转角度 {scale, angle}
|
||||||
|
*/
|
||||||
|
export function getTransformScaleAngle(Transform) {
|
||||||
|
const a = Transform[0];
|
||||||
|
const b = Transform[1];
|
||||||
|
const c = Transform[2];
|
||||||
|
const d = Transform[3];
|
||||||
|
const scale = Math.sqrt(a * a + b * b);
|
||||||
|
const angle = Math.round(Math.atan2(b, a) * 180 / Math.PI);
|
||||||
|
return { scale, angle };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ObjectManager {
|
||||||
|
stateManager: any
|
||||||
|
canvasManager: any
|
||||||
|
layerManager: any
|
||||||
|
constructor(options) {
|
||||||
|
this.stateManager = options.stateManager;
|
||||||
|
this.canvasManager = options.canvasManager;
|
||||||
|
this.layerManager = options.layerManager
|
||||||
|
}
|
||||||
|
onMounted() { }
|
||||||
|
/** 设置平铺状态 */
|
||||||
|
setFillRepeat(id: string) {
|
||||||
|
const object = this.canvasManager.getObjectById(id)
|
||||||
|
if (!object) return console.warn('设置平铺状态失败,对象不存在ID:', id)
|
||||||
|
if (object.type !== 'image') return console.warn('设置平铺状态失败,对象不是图片类型:', id)
|
||||||
|
|
||||||
|
// 创建透明 Canvas
|
||||||
|
const imgEl = object.getElement();
|
||||||
|
const tcanvas = document.createElement('canvas');
|
||||||
|
tcanvas.width = imgEl.width;
|
||||||
|
tcanvas.height = imgEl.height;
|
||||||
|
const ctx = tcanvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
|
||||||
|
ctx.drawImage(imgEl, 0, 0);
|
||||||
|
const fill = {
|
||||||
|
source: FillSourceToBase64(imgEl),
|
||||||
|
gapX: 0,
|
||||||
|
gapY: 0,
|
||||||
|
}
|
||||||
|
const pattern = new fabric.Pattern({
|
||||||
|
source: tcanvas,
|
||||||
|
repeat: 'repeat',
|
||||||
|
patternTransform: createPatternTransform(1, 0),
|
||||||
|
offsetX: 0, // 水平偏移
|
||||||
|
offsetY: 0, // 垂直偏移
|
||||||
|
});
|
||||||
|
const info = {
|
||||||
|
...object.info,
|
||||||
|
fill,
|
||||||
|
}
|
||||||
|
const rect = new fabric.Rect({
|
||||||
|
width: object.width,
|
||||||
|
height: object.height,
|
||||||
|
top: object.top,
|
||||||
|
left: object.left,
|
||||||
|
angle: object.angle,
|
||||||
|
scaleX: object.scaleX,
|
||||||
|
scaleY: object.scaleY,
|
||||||
|
flipX: object.flipX,
|
||||||
|
flipY: object.flipY,
|
||||||
|
originX: "left",
|
||||||
|
originY: "top",
|
||||||
|
info,
|
||||||
|
});
|
||||||
|
rect.set("fill", pattern)
|
||||||
|
this.canvasManager.canvas.remove(object)
|
||||||
|
this.canvasManager.add(rect)
|
||||||
|
}
|
||||||
|
/** 获取填充对象 */
|
||||||
|
getFillRepeatObject(id: string) {
|
||||||
|
const object = this.canvasManager.getObjectById(id)
|
||||||
|
if (!object) {
|
||||||
|
console.warn('getFillRepeatObject 对象不存在ID:', id)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (object.type !== 'rect') {
|
||||||
|
console.warn('getFillRepeatObject 对象不是矩形类型:', id)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (object.fill?.repeat !== 'repeat') {
|
||||||
|
console.warn('getFillRepeatObject 对象不是平铺填充类型:', id)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 修改平铺参数
|
||||||
|
* @param id 目标对象ID
|
||||||
|
* @param options 参数
|
||||||
|
* @param options.scale 缩放比例
|
||||||
|
* @param options.angle 旋转角度
|
||||||
|
* @param options.offsetX 水平偏移
|
||||||
|
* @param options.offsetY 垂直偏移
|
||||||
|
*/
|
||||||
|
async updateFillRepeatTransform(id: string, options: any, isRecord: boolean) {
|
||||||
|
const object = this.getFillRepeatObject(id)
|
||||||
|
if (!object) return null
|
||||||
|
const fill = object.get("fill");
|
||||||
|
const scale = options.hasOwnProperty('scale') ? options.scale : getTransformScaleAngle(fill.patternTransform).scale
|
||||||
|
const angle = options.hasOwnProperty('angle') ? options.angle : getTransformScaleAngle(fill.patternTransform).angle
|
||||||
|
const offsetX = options.hasOwnProperty('offsetX') ? options.offsetX : fill.offsetX
|
||||||
|
const offsetY = options.hasOwnProperty('offsetY') ? options.offsetY : fill.offsetY
|
||||||
|
fill.patternTransform = createPatternTransform(scale, angle)
|
||||||
|
fill.offsetX = offsetX
|
||||||
|
fill.offsetY = offsetY
|
||||||
|
object.set("fill", new fabric.Pattern(fill));
|
||||||
|
this.canvasManager.renderAll()
|
||||||
|
if (isRecord) this.stateManager.recordState()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 修改平铺间隙
|
||||||
|
* @param id 目标对象ID
|
||||||
|
* @param options 间隙参数
|
||||||
|
* @param options.gapX 水平间隙
|
||||||
|
* @param options.gapY 垂直间隙
|
||||||
|
*/
|
||||||
|
async updateFillRepeatGap(id: string, options: any, isRecord: boolean) {
|
||||||
|
const object = this.getFillRepeatObject(id)
|
||||||
|
if (!object) return null
|
||||||
|
const gapX = options.hasOwnProperty('gapX') ? options.gapX : object.info.fill.gapX
|
||||||
|
const gapY = options.hasOwnProperty('gapY') ? options.gapY : object.info.fill.gapY
|
||||||
|
const fill = object.get("fill");
|
||||||
|
const image = new Image();
|
||||||
|
image.crossOrigin = "anonymous";
|
||||||
|
image.src = object.info.fill.source;
|
||||||
|
await image.decode();
|
||||||
|
fill.source = imageAddGapToCanvas(image, gapX, gapY)
|
||||||
|
object.info.fill.gapX = gapX
|
||||||
|
object.info.fill.gapY = gapY
|
||||||
|
object.set("fill", new fabric.Pattern(fill));
|
||||||
|
this.canvasManager.renderAll()
|
||||||
|
if (isRecord) this.stateManager.recordState()
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() { }
|
||||||
|
}
|
||||||
@@ -3,6 +3,23 @@ import { ElMessageBox } from 'element-plus'
|
|||||||
import i18n from '@/lang'
|
import i18n from '@/lang'
|
||||||
const t = i18n.global.t
|
const t = i18n.global.t
|
||||||
|
|
||||||
|
class Event {
|
||||||
|
list: any[]
|
||||||
|
constructor() {
|
||||||
|
this.list = []
|
||||||
|
}
|
||||||
|
add(name, call) {
|
||||||
|
this.list.push({ name, call })
|
||||||
|
}
|
||||||
|
remove(name, call) {
|
||||||
|
this.list = this.list.filter(item => item.name != name && item.call != call)
|
||||||
|
}
|
||||||
|
emit(name, data) {
|
||||||
|
this.list.forEach(item => {
|
||||||
|
if (item.name == name) item.call(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class StateManager {
|
export class StateManager {
|
||||||
// 历史记录-撤回/重做
|
// 历史记录-撤回/重做
|
||||||
@@ -11,6 +28,8 @@ export class StateManager {
|
|||||||
historyIndex: any
|
historyIndex: any
|
||||||
running: any
|
running: any
|
||||||
|
|
||||||
|
event: Event
|
||||||
|
|
||||||
// 管理器
|
// 管理器
|
||||||
canvasManager: any
|
canvasManager: any
|
||||||
layerManager: any
|
layerManager: any
|
||||||
@@ -18,6 +37,7 @@ export class StateManager {
|
|||||||
toolManager: any
|
toolManager: any
|
||||||
brushManager: any
|
brushManager: any
|
||||||
keyEventManager: any
|
keyEventManager: any
|
||||||
|
objectManager: any
|
||||||
// 设置管理器
|
// 设置管理器
|
||||||
setManager(options) {
|
setManager(options) {
|
||||||
options.eventManager && (this.eventManager = options.eventManager)
|
options.eventManager && (this.eventManager = options.eventManager)
|
||||||
@@ -26,16 +46,16 @@ export class StateManager {
|
|||||||
options.toolManager && (this.toolManager = options.toolManager)
|
options.toolManager && (this.toolManager = options.toolManager)
|
||||||
options.brushManager && (this.brushManager = options.brushManager)
|
options.brushManager && (this.brushManager = options.brushManager)
|
||||||
options.keyEventManager && (this.keyEventManager = options.keyEventManager)
|
options.keyEventManager && (this.keyEventManager = options.keyEventManager)
|
||||||
|
options.objectManager && (this.objectManager = options.objectManager)
|
||||||
}
|
}
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.mxHistory = ref(50)
|
this.mxHistory = ref(50)
|
||||||
this.historyList = ref([])
|
this.historyList = ref([])
|
||||||
this.historyIndex = ref(0)
|
this.historyIndex = ref(0)
|
||||||
this.running = ref(false)
|
this.running = ref(false)
|
||||||
|
this.event = new Event()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
onMounted() { }
|
||||||
|
|
||||||
/** 设置是否开始记录状态 */
|
/** 设置是否开始记录状态 */
|
||||||
setIsRecord(isRecord: boolean) {
|
setIsRecord(isRecord: boolean) {
|
||||||
@@ -73,6 +93,7 @@ export class StateManager {
|
|||||||
this.running.value = true
|
this.running.value = true
|
||||||
this.historyIndex.value = index
|
this.historyIndex.value = index
|
||||||
this.canvasManager.loadJSON(state.canvas, () => {
|
this.canvasManager.loadJSON(state.canvas, () => {
|
||||||
|
this.event.emit('canvas:undo', state)
|
||||||
this.running.value = false
|
this.running.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -85,6 +106,7 @@ export class StateManager {
|
|||||||
this.running.value = true
|
this.running.value = true
|
||||||
this.historyIndex.value = index
|
this.historyIndex.value = index
|
||||||
this.canvasManager.loadJSON(state.canvas, () => {
|
this.canvasManager.loadJSON(state.canvas, () => {
|
||||||
|
this.event.emit('canvas:redo', state)
|
||||||
this.running.value = false
|
this.running.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class BrushRegistry {
|
|||||||
*/
|
*/
|
||||||
register(id, brushClass, metadata = {}) {
|
register(id, brushClass, metadata = {}) {
|
||||||
if (this.brushes.has(id)) {
|
if (this.brushes.has(id)) {
|
||||||
console.warn(`笔刷 ${id} 已存在,请使用其他ID`);
|
// console.warn(`笔刷 ${id} 已存在,请使用其他ID`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -708,34 +708,34 @@ export class CanvasEventManager {
|
|||||||
|
|
||||||
const updateLayers = (e) => {
|
const updateLayers = (e) => {
|
||||||
if (e.target._objects) return;
|
if (e.target._objects) return;
|
||||||
this.layerManager.updateLayers();
|
// this.layerManager.updateLayers();// 先不用数据大了非常卡
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加对象开始变换时的状态捕获
|
// 添加对象开始变换时的状态捕获
|
||||||
this.canvas.on("object:moving", (e) => {
|
this.canvas.on("object:moving", (e) => {
|
||||||
// console.log("object:moving", e);
|
// console.log("object:moving", e);
|
||||||
updateLayers(e);
|
// updateLayers(e);
|
||||||
});
|
});
|
||||||
this.canvas.on("object:scaling", (e) => {
|
this.canvas.on("object:scaling", (e) => {
|
||||||
// console.log("object:scaling", e);
|
// console.log("object:scaling", e);
|
||||||
updateLayers(e);
|
// updateLayers(e);
|
||||||
});
|
});
|
||||||
this.canvas.on("object:rotating", (e) => {
|
this.canvas.on("object:rotating", (e) => {
|
||||||
// console.log("object:rotating", e);
|
// console.log("object:rotating", e);
|
||||||
updateLayers(e);
|
// updateLayers(e);
|
||||||
});
|
});
|
||||||
this.canvas.on("object:skewing", (e) => {
|
this.canvas.on("object:skewing", (e) => {
|
||||||
// console.log("object:skewing", e);
|
// console.log("object:skewing", e);
|
||||||
updateLayers(e);
|
// updateLayers(e);
|
||||||
});
|
});
|
||||||
this.canvas.on("object:modified", async (e) => {
|
this.canvas.on("object:modified", async (e) => {
|
||||||
updateLayers(e);
|
// updateLayers(e);
|
||||||
const id = e.target?.info?.id;
|
const id = e.target?.info?.id;
|
||||||
if (id) await this.layerManager.updateLayerThumbnailsById(id)
|
if (id) await this.layerManager.updateLayerThumbnailsById(id)
|
||||||
this.stateManager.recordState();
|
this.stateManager.recordState();
|
||||||
});
|
});
|
||||||
this.canvas.on("object:removed", (e) => {
|
this.canvas.on("object:removed", (e) => {
|
||||||
updateLayers(e);
|
// updateLayers(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setupDoubleClickEvents() {
|
setupDoubleClickEvents() {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export class KeyEventManager {
|
|||||||
this.stateManager = options.stateManager;
|
this.stateManager = options.stateManager;
|
||||||
this.registerEvents()
|
this.registerEvents()
|
||||||
}
|
}
|
||||||
|
onMounted() { }
|
||||||
|
|
||||||
/** 处理键盘事件 */
|
/** 处理键盘事件 */
|
||||||
handleKeyDown(event: any) {
|
handleKeyDown(event: any) {
|
||||||
|
|||||||
@@ -5,7 +5,14 @@
|
|||||||
<svg-icon :name="icon" :size="iconSize" size-unit="px" />
|
<svg-icon :name="icon" :size="iconSize" size-unit="px" />
|
||||||
</span>
|
</span>
|
||||||
<span v-show="before" class="before">{{ before }}</span>
|
<span v-show="before" class="before">{{ before }}</span>
|
||||||
<input v-bind="attrs" :value="modelValue" @input="onInput" @copy.stop @keydown.stop />
|
<input
|
||||||
|
v-bind="attrs"
|
||||||
|
:value="modelValue"
|
||||||
|
@input="onInput"
|
||||||
|
@change="onChange"
|
||||||
|
@copy.stop
|
||||||
|
@keydown.stop
|
||||||
|
/>
|
||||||
<span v-show="after" class="after">{{ after }}</span>
|
<span v-show="after" class="after">{{ after }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -20,13 +27,19 @@
|
|||||||
after: { default: '', type: String }
|
after: { default: '', type: String }
|
||||||
})
|
})
|
||||||
const attrs = useAttrs()
|
const attrs = useAttrs()
|
||||||
const emit = defineEmits(['update:modelValue', 'input'])
|
const emit = defineEmits(['update:modelValue', 'input', 'change'])
|
||||||
const onInput = (e) => {
|
const onInput = (e) => {
|
||||||
var value = e.target.value
|
var value = e.target.value
|
||||||
if (attrs.type === 'number') value = Number(value)
|
if (attrs.type === 'number') value = Number(value)
|
||||||
emit('update:modelValue', value)
|
emit('update:modelValue', value)
|
||||||
emit('input', value)
|
emit('input', value)
|
||||||
}
|
}
|
||||||
|
const onChange = (e) => {
|
||||||
|
var value = e.target.value
|
||||||
|
if (attrs.type === 'number') value = Number(value)
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
emit('change', value)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.my-input {
|
.my-input {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
}
|
}
|
||||||
emit('update:modelValue', value)
|
emit('update:modelValue', value)
|
||||||
emit('change', value)
|
emit('change', value)
|
||||||
}, 500)
|
}, 100)
|
||||||
}
|
}
|
||||||
const lineZStyle = computed(() => ({
|
const lineZStyle = computed(() => ({
|
||||||
'--rotateZ': calculateAngle(0, 0, left.value, top.value) + 'deg',
|
'--rotateZ': calculateAngle(0, 0, left.value, top.value) + 'deg',
|
||||||
|
|||||||
Reference in New Issue
Block a user