Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/FiDA_Front
This commit is contained in:
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
3
src/assets/icons/dc/brush_sb.svg
Normal file
3
src/assets/icons/dc/brush_sb.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.157122 13.4725L0.526449 10.3737C0.550847 10.1699 0.642783 9.98015 0.787096 9.83413L9.86461 0.654119C10.1812 0.334063 10.6118 0.151974 11.062 0.149342C11.5123 0.146814 11.9457 0.324063 12.2659 0.640637L13.8892 2.24581C14.2094 2.56248 14.3915 2.99386 14.394 3.44416C14.3964 3.89432 14.2192 4.32698 13.9027 4.64714L4.8252 13.8272C4.68081 13.9731 4.49215 14.0672 4.28861 14.0938L1.19415 14.4979C1.05466 14.5161 0.912174 14.5022 0.778872 14.4573C0.645754 14.4124 0.525008 14.3374 0.425099 14.2386C0.325191 14.1398 0.248858 14.0199 0.202421 13.8873C0.167635 13.7878 0.14946 13.6829 0.149902 13.578L0.157122 13.4725ZM11.752 5.29496L13.1397 3.89164C13.2557 3.77408 13.3206 3.61539 13.3198 3.45019C13.3189 3.28482 13.2522 3.12618 13.1347 3.00982L11.5104 1.40368C11.3928 1.28748 11.2334 1.22262 11.068 1.22354C10.9028 1.22458 10.7449 1.29127 10.6286 1.40863L9.24097 2.81195L11.752 5.29496ZM1.84678 13.3302L4.09809 13.0363L10.9965 6.05996L8.4845 3.57598L1.58606 10.5523L1.31747 12.8068L1.24565 13.4088L1.84678 13.3302Z" fill="#0D0D0D" stroke="black" stroke-width="0.3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
3
src/assets/icons/dc/erase_sb.svg
Normal file
3
src/assets/icons/dc/erase_sb.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11.26 0.555138L15.4449 4.74003C15.6209 4.91603 15.7605 5.12497 15.8557 5.35492C15.951 5.58488 16 5.83134 16 6.08024C16 6.32914 15.951 6.5756 15.8557 6.80556C15.7605 7.03551 15.6209 7.24445 15.4449 7.42045L8.13225 14.7322L13.2682 14.7331C13.421 14.7329 13.5687 14.7881 13.6839 14.8884C13.7992 14.9888 13.8741 15.1275 13.8949 15.2789L13.9 15.3648C13.9 15.5175 13.8447 15.665 13.7443 15.7801C13.644 15.8951 13.5054 15.97 13.3541 15.9907L13.2682 15.9966L6.18385 15.9975C5.91791 16.0124 5.65181 15.9709 5.403 15.8758C5.15418 15.7807 4.92826 15.6342 4.74003 15.4457L0.555138 11.26C0.379138 11.084 0.239526 10.875 0.144276 10.6451C0.0490248 10.4151 0 10.1687 0 9.91976C0 9.67086 0.0490248 9.4244 0.144276 9.19444C0.239526 8.96449 0.379138 8.75555 0.555138 8.57955L8.57955 0.555138C8.75555 0.379138 8.96449 0.239527 9.19444 0.144276C9.4244 0.049025 9.67086 0 9.91976 0C10.1687 0 10.4151 0.049025 10.6451 0.144276C10.875 0.239527 11.084 0.379138 11.26 0.555138ZM2.70065 8.21986L1.44805 9.4733C1.32974 9.59176 1.26328 9.75234 1.26328 9.91976C1.26328 10.0872 1.32974 10.2478 1.44805 10.3662L5.63378 14.5519C5.75677 14.6749 5.9185 14.7373 6.08024 14.7373L6.10467 14.7331L6.13668 14.7347C6.28414 14.7216 6.42224 14.6569 6.5267 14.5519L7.7793 13.2993L2.69981 8.21986H2.70065ZM9.4733 1.44805L3.59356 7.3261L8.67305 12.4056L14.552 6.52754C14.6107 6.46887 14.6572 6.3992 14.689 6.32253C14.7208 6.24585 14.7371 6.16366 14.7371 6.08066C14.7371 5.99766 14.7208 5.91547 14.689 5.8388C14.6572 5.76212 14.6107 5.69245 14.552 5.63378L10.3662 1.44805C10.2478 1.32974 10.0872 1.26328 9.91976 1.26328C9.75234 1.26328 9.59176 1.32974 9.4733 1.44805Z" fill="#212121"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 962 B After Width: | Height: | Size: 962 B |
@@ -1,23 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div v-if="show" class="ai-selectbox-panel">
|
<div v-if="show" class="ai-selectbox-panel">
|
||||||
<div>
|
<div
|
||||||
<span class="icon"><svg-icon name="dc-add" size="16" /></span>
|
v-for="item in list"
|
||||||
<span class="label">Add</span>
|
:key="item.type"
|
||||||
</div>
|
:class="{ active: item.name === props.currentTool }"
|
||||||
<div>
|
>
|
||||||
<span class="icon"><svg-icon name="dc-remove" size="16" /></span>
|
<span class="icon"><svg-icon :name="item.name" size="16" /></span>
|
||||||
<span class="label">Remove</span>
|
<span class="label">{{ item.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button>创建</button>
|
<button>创建</button>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
<brush-control-panel :currentTool="show ? 'draw' : ''" style="top: 14rem" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, inject, computed, watch } from 'vue'
|
import { ref, inject, computed, watch } from 'vue'
|
||||||
import depthSlider from './tools/depth-slider.vue'
|
import depthSlider from './tools/depth-slider.vue'
|
||||||
import { OperationType } from '../tools/layerHelper'
|
import { OperationType, AI_SELECTBOX_TYPE } from '../tools/layerHelper'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentTool: { required: true, type: [String, null] }
|
currentTool: { required: true, type: [String, null] }
|
||||||
})
|
})
|
||||||
@@ -25,6 +27,28 @@
|
|||||||
const toolManager = inject('toolManager') as any
|
const toolManager = inject('toolManager') as any
|
||||||
const showTools = [OperationType.SELECTBOX]
|
const showTools = [OperationType.SELECTBOX]
|
||||||
const show = computed(() => showTools.includes(props.currentTool))
|
const show = computed(() => showTools.includes(props.currentTool))
|
||||||
|
const list = ref([
|
||||||
|
{
|
||||||
|
type: AI_SELECTBOX_TYPE.ADD,
|
||||||
|
name: 'dc-add_sb',
|
||||||
|
label: 'Add'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: AI_SELECTBOX_TYPE.REMOVE,
|
||||||
|
name: 'dc-remove_sb',
|
||||||
|
label: 'Remove'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: AI_SELECTBOX_TYPE.DRAW,
|
||||||
|
name: 'dc-brush_sb',
|
||||||
|
label: 'Brush'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: AI_SELECTBOX_TYPE.ERASER,
|
||||||
|
name: 'dc-erase_sb',
|
||||||
|
label: 'Erase'
|
||||||
|
}
|
||||||
|
])
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
// 淡入淡出动画
|
// 淡入淡出动画
|
||||||
|
|||||||
@@ -46,6 +46,10 @@
|
|||||||
watch(brushState, (value) => {
|
watch(brushState, (value) => {
|
||||||
if (value) updateBrushState()
|
if (value) updateBrushState()
|
||||||
})
|
})
|
||||||
|
watch(
|
||||||
|
() => props.currentTool,
|
||||||
|
(value) => updateBrushState()
|
||||||
|
)
|
||||||
const brushSize = ref(40)
|
const brushSize = ref(40)
|
||||||
const brushOpacity = ref(100)
|
const brushOpacity = ref(100)
|
||||||
const brushColor = ref('#000000')
|
const brushColor = ref('#000000')
|
||||||
|
|||||||
@@ -33,8 +33,8 @@
|
|||||||
const isShow = computed(() => isRepeat.value)
|
const isShow = computed(() => isRepeat.value)
|
||||||
|
|
||||||
const updateActiveObject = () => {
|
const updateActiveObject = () => {
|
||||||
const obj = layers.value.find((v: any) => v.info.id === activeID.value)
|
const layer = layerManager.getActiveLayer()
|
||||||
activeObject.value = obj ? JSON.parse(JSON.stringify(obj)) : null
|
activeObject.value = layer ? JSON.parse(JSON.stringify(layer)) : null
|
||||||
}
|
}
|
||||||
watch(layers, () => updateActiveObject())
|
watch(layers, () => updateActiveObject())
|
||||||
watch(activeID, () => updateActiveObject())
|
watch(activeID, () => updateActiveObject())
|
||||||
|
|||||||
@@ -1,33 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layer-item" @click="onClickLayer">
|
<div class="layer-item">
|
||||||
<div class="drag"><svg-icon name="dc-drag" size="18" /></div>
|
<div class="item" @click="onClickLayer">
|
||||||
<div class="thumb">
|
<div class="drag"><svg-icon name="dc-drag" size="18" /></div>
|
||||||
<img v-if="layer.thumbnail" :src="layer.thumbnail" />
|
<div class="thumb">
|
||||||
</div>
|
<img v-if="layer.thumbnail" :src="layer.thumbnail" />
|
||||||
<div class="name">
|
</div>
|
||||||
<div @dblclick="onClickEditName" v-if="!editName" :title="layer.info.name">
|
<div class="name">
|
||||||
{{ layer.info.name || '未命名图层' }}
|
<div @dblclick="onClickEditName" v-if="!editName" :title="layer.info.name">
|
||||||
|
{{ layer.info.name || '未命名图层' }}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
v-else
|
||||||
|
type="text"
|
||||||
|
ref="nameInputRef"
|
||||||
|
:value="layer.info.name"
|
||||||
|
@blur="onChangeName"
|
||||||
|
@keyup.enter="onChangeName"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="icons">
|
||||||
|
<span @click.stop="onClickLock">
|
||||||
|
<svg-icon v-show="!layer.info.lock" name="dc-lock_0" size="15" />
|
||||||
|
<svg-icon v-show="layer.info.lock" name="dc-lock_1" size="15" color="#1890ff" />
|
||||||
|
</span>
|
||||||
|
<span @click.stop="onClickShowHide"
|
||||||
|
><svg-icon :name="layer.visible ? 'dc-show' : 'dc-hide'" size="15"
|
||||||
|
/></span>
|
||||||
|
<span @click.stop="onClickDelete"><svg-icon name="dc-delete" size="13" /></span>
|
||||||
|
<span
|
||||||
|
v-if="isGroup"
|
||||||
|
@click.stop="onShowGroup"
|
||||||
|
class="show-group"
|
||||||
|
:class="{ active: layer.info.showChildren }"
|
||||||
|
>
|
||||||
|
<svg-icon name="dc-down_arrow" size="11" />
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
|
||||||
v-else
|
|
||||||
type="text"
|
|
||||||
ref="nameInputRef"
|
|
||||||
:value="layer.info.name"
|
|
||||||
@blur="onChangeName"
|
|
||||||
@keyup.enter="onChangeName"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="icons">
|
|
||||||
<span @click.stop="onClickLock">
|
|
||||||
<svg-icon v-show="!layer.info.lock" name="dc-lock_0" size="15" />
|
|
||||||
<svg-icon v-show="layer.info.lock" name="dc-lock_1" size="15" color="#1890ff" />
|
|
||||||
</span>
|
|
||||||
<span @click.stop="onClickShowHide"
|
|
||||||
><svg-icon :name="layer.visible ? 'dc-show' : 'dc-hide'" size="15"
|
|
||||||
/></span>
|
|
||||||
<span @click.stop="onClickDelete"><svg-icon name="dc-delete" size="13" /></span>
|
|
||||||
<!-- <span><svg-icon name="dc-down_arrow" size="11" /></span> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -42,6 +52,10 @@
|
|||||||
layer: {
|
layer: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
|
},
|
||||||
|
isGroup: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const nameInputRef = ref(null)
|
const nameInputRef = ref(null)
|
||||||
@@ -72,82 +86,98 @@
|
|||||||
const info = props.layer.info
|
const info = props.layer.info
|
||||||
layerManager.setLayerLockById(info.id, !info.lock)
|
layerManager.setLayerLockById(info.id, !info.lock)
|
||||||
}
|
}
|
||||||
|
const onShowGroup = () => {
|
||||||
|
props.layer.info.showChildren = !props.layer.info.showChildren
|
||||||
|
// layerManager.setLayerGroupVisibleById(props.layer.info.id, !props.layer.visible)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.layer-item {
|
.layer-item {
|
||||||
width: 100%;
|
&:last-child > .item {
|
||||||
height: 9.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0 1.4rem;
|
|
||||||
background-color: #fafafa;
|
|
||||||
gap: 1rem;
|
|
||||||
border-bottom: 0.2rem solid #ededed;
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
&:not([draging='true']):hover {
|
&:not([draging='true']) > .item:hover {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
&.active {
|
&.active > .item {
|
||||||
background-color: #ededed !important;
|
background-color: #ededed !important;
|
||||||
}
|
}
|
||||||
&.drag {
|
&.drag {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
&.ghost,
|
&.ghost > .item,
|
||||||
&.chosen {
|
&.chosen > .item {
|
||||||
box-shadow: inset 0 0 5px #aaa;
|
box-shadow: inset 0 0 5px #aaa;
|
||||||
background-color: #ededed !important;
|
background-color: #ededed !important;
|
||||||
}
|
}
|
||||||
> .drag {
|
> .item {
|
||||||
padding: 0.3rem;
|
width: 100%;
|
||||||
cursor: move;
|
height: 9.5rem;
|
||||||
}
|
|
||||||
> .thumb {
|
|
||||||
width: 5.6rem;
|
|
||||||
height: 5.6rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 0.2rem solid #ebebeb;
|
|
||||||
background-color: #fff;
|
|
||||||
> img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .name {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
overflow: hidden;
|
|
||||||
> div {
|
|
||||||
cursor: pointer;
|
|
||||||
height: 3rem;
|
|
||||||
line-height: 3rem;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
> input {
|
|
||||||
width: 100%;
|
|
||||||
height: 3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .icons {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.8rem;
|
padding: 0 1.4rem;
|
||||||
> span {
|
background-color: #fafafa;
|
||||||
cursor: pointer;
|
gap: 1rem;
|
||||||
width: 1.6rem;
|
border-bottom: 0.2rem solid #ededed;
|
||||||
height: 1.6rem;
|
|
||||||
|
> .drag {
|
||||||
|
padding: 0.3rem;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
> .thumb {
|
||||||
|
width: 5.6rem;
|
||||||
|
height: 5.6rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 0.2rem solid #ebebeb;
|
||||||
|
background-color: #fff;
|
||||||
|
> img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
overflow: hidden;
|
||||||
|
> div {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 3rem;
|
||||||
|
line-height: 3rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
> input {
|
||||||
|
width: 100%;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .icons {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
> span {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 1.6rem;
|
||||||
|
height: 1.6rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
> .show-group {
|
||||||
|
> .c-svg {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
&.active > .c-svg {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,28 +12,16 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<VueDraggable
|
<VueDraggable
|
||||||
:model-value="list"
|
:model-value="list"
|
||||||
@start="handleDragStart"
|
@start="(e) => handleDragStart(e)"
|
||||||
@end="handleDragEnd"
|
@end="(e) => handleDragEnd(e)"
|
||||||
|
@add="(e) => handleAdd(e)"
|
||||||
class="sortable-layers"
|
class="sortable-layers"
|
||||||
:data-container-type="'root'"
|
v-bind="config"
|
||||||
:data-parent-id="null"
|
|
||||||
:animation="250"
|
|
||||||
:disabled="false"
|
|
||||||
handle=".drag"
|
|
||||||
ghost-class="ghost"
|
|
||||||
chosen-class="chosen"
|
|
||||||
drag-class="drag"
|
|
||||||
:group="{
|
:group="{
|
||||||
name: 'groupName',
|
name: 'group',
|
||||||
pull: true,
|
pull: true,
|
||||||
put: true
|
put: true
|
||||||
}"
|
}"
|
||||||
:swap-threshold="0.5"
|
|
||||||
:empty-insert-threshold="5"
|
|
||||||
:force-fallback="false"
|
|
||||||
:fallback-tolerance="3"
|
|
||||||
:scroll-sensitivity="100"
|
|
||||||
:scroll-speed="10"
|
|
||||||
>
|
>
|
||||||
<layer-item
|
<layer-item
|
||||||
v-for="layer in list"
|
v-for="layer in list"
|
||||||
@@ -41,7 +29,34 @@
|
|||||||
:layer="layer"
|
:layer="layer"
|
||||||
:draging="draging"
|
:draging="draging"
|
||||||
:class="{ active: layer.info.id === layerManager.activeID.value }"
|
:class="{ active: layer.info.id === layerManager.activeID.value }"
|
||||||
/>
|
:is-group="layer.type === 'group'"
|
||||||
|
>
|
||||||
|
<VueDraggable
|
||||||
|
v-if="layer.type === 'group'"
|
||||||
|
v-show="layer.info.showChildren"
|
||||||
|
:model-value="layer.children"
|
||||||
|
@start="(e) => handleDragStart(e, layer)"
|
||||||
|
@end="(e) => handleDragEnd(e, layer)"
|
||||||
|
@add="(e) => handleAdd(e, layer)"
|
||||||
|
class="sortable-layers-child"
|
||||||
|
v-bind="config"
|
||||||
|
:group="{
|
||||||
|
name: 'child_' + layer.info.id,
|
||||||
|
pull: true,
|
||||||
|
put: true
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<layer-item
|
||||||
|
v-for="child in layer.children"
|
||||||
|
:key="child.info.id"
|
||||||
|
:layer="child"
|
||||||
|
:draging="draging"
|
||||||
|
:class="{
|
||||||
|
active: child.info.id === layerManager.activeID.value
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</VueDraggable>
|
||||||
|
</layer-item>
|
||||||
</VueDraggable>
|
</VueDraggable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,14 +68,65 @@
|
|||||||
import layerItem from './layer-item.vue'
|
import layerItem from './layer-item.vue'
|
||||||
const draging = ref(false)
|
const draging = ref(false)
|
||||||
const layerManager = inject('layerManager') as any
|
const layerManager = inject('layerManager') as any
|
||||||
|
const canvasManager = inject('canvasManager') as any
|
||||||
const list = computed(() => layerManager.layers.value)
|
const list = computed(() => layerManager.layers.value)
|
||||||
const handleDragStart = () => {
|
const config = ref({
|
||||||
draging.value = true
|
'data-container-type': 'root',
|
||||||
|
'data-parent-id': 'null',
|
||||||
|
animation: 250,
|
||||||
|
disabled: false,
|
||||||
|
handle: '.drag',
|
||||||
|
'ghost-class': 'ghost',
|
||||||
|
'chosen-class': 'chosen',
|
||||||
|
'drag-class': 'drag',
|
||||||
|
'swap-threshold': 0.5,
|
||||||
|
'empty-insert-threshold': 5,
|
||||||
|
'force-fallback': false,
|
||||||
|
'fallback-tolerance': 3,
|
||||||
|
'scroll-sensitivity': 100,
|
||||||
|
'scroll-speed': 10
|
||||||
|
})
|
||||||
|
const startParent = ref(null)
|
||||||
|
const clearData = () => {
|
||||||
|
startParent.value = null
|
||||||
}
|
}
|
||||||
const handleDragEnd = (event) => {
|
const handleDragStart = (event, parent?) => {
|
||||||
|
draging.value = true
|
||||||
|
startParent.value = parent
|
||||||
|
}
|
||||||
|
const moveElementInPlace = (arr, oldIndex, newIndex) => {
|
||||||
|
if (oldIndex === newIndex) return arr
|
||||||
|
const movedItem = arr[oldIndex]
|
||||||
|
// 移除元素
|
||||||
|
arr.splice(oldIndex, 1)
|
||||||
|
// 插入到新位置
|
||||||
|
arr.splice(newIndex, 0, movedItem)
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
const handleDragEnd = (event, parent?) => {
|
||||||
draging.value = false
|
draging.value = false
|
||||||
const { from, to, oldIndex, newIndex, data } = event
|
const { from, to, oldIndex, newIndex, data } = event
|
||||||
layerManager.dragSort(data.info.id, newIndex)
|
if (from !== to) return
|
||||||
|
const arr = parent ? parent.children : layerManager.layers.value
|
||||||
|
moveElementInPlace(arr, oldIndex, newIndex)
|
||||||
|
clearData()
|
||||||
|
layerManager.sortLayers(true)
|
||||||
|
}
|
||||||
|
const handleAdd = (event, parent?) => {
|
||||||
|
const { from, to, oldIndex, newIndex, data } = event
|
||||||
|
if (data.type === 'group') return
|
||||||
|
console.log('跨级拖动', startParent.value, oldIndex, parent, newIndex)
|
||||||
|
const oldArr = startParent.value ? startParent.value.children : layerManager.layers.value
|
||||||
|
const arr = parent ? parent.children : layerManager.layers.value
|
||||||
|
const layer = oldArr.splice(oldIndex, 1)[0]
|
||||||
|
if (parent) {
|
||||||
|
layer.info.parentId = parent.info.id
|
||||||
|
} else {
|
||||||
|
delete layer.info.parentId
|
||||||
|
}
|
||||||
|
arr.splice(newIndex, 0, layer)
|
||||||
|
clearData()
|
||||||
|
layerManager.sortLayers(true)
|
||||||
}
|
}
|
||||||
const addLayer = () => {
|
const addLayer = () => {
|
||||||
layerManager.createEmptyLayer(true, true)
|
layerManager.createEmptyLayer(true, true)
|
||||||
@@ -116,4 +182,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sortable-layers-child {
|
||||||
|
border-left: 2.5rem solid #e8e8e8;
|
||||||
|
border-bottom: 0.2rem solid #ededed;
|
||||||
|
// min-height: 5rem;
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { fabric } from 'fabric-with-all'
|
import { fabric } from 'fabric-with-all'
|
||||||
import { createId } from '../../tools/tools'
|
import { OperationType, AI_SELECTBOX_TYPE } from '../tools/layerHelper'
|
||||||
|
import { getObjectAlphaToCanvas, traceImageContour } from '../tools/canvasMethod'
|
||||||
|
|
||||||
/** 智能框选工具管理器 */
|
/** 智能框选工具管理器 */
|
||||||
export class AISelectboxToolManager {
|
export class AISelectboxToolManager {
|
||||||
@@ -7,6 +8,7 @@ export class AISelectboxToolManager {
|
|||||||
canvasManager: any
|
canvasManager: any
|
||||||
stateManager: any
|
stateManager: any
|
||||||
layerManager: any
|
layerManager: any
|
||||||
|
toolManager: any
|
||||||
|
|
||||||
isDragging: boolean = false
|
isDragging: boolean = false
|
||||||
startX: number = 0
|
startX: number = 0
|
||||||
@@ -16,7 +18,16 @@ export class AISelectboxToolManager {
|
|||||||
this.canvasManager = options.canvasManager
|
this.canvasManager = options.canvasManager
|
||||||
this.stateManager = options.stateManager
|
this.stateManager = options.stateManager
|
||||||
this.layerManager = options.layerManager
|
this.layerManager = options.layerManager
|
||||||
|
this.toolManager = options.toolManager
|
||||||
|
}
|
||||||
|
/** 处理切换工具 */
|
||||||
|
handleToolChange(oldTool: string, newTool: string) {
|
||||||
|
if (newTool === OperationType.SELECTBOX) {
|
||||||
|
// 切换到智能框选工具
|
||||||
|
} else {
|
||||||
|
// 切换到普通框选工具
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mouseDownEvent(e) {
|
mouseDownEvent(e) {
|
||||||
this.isDragging = true
|
this.isDragging = true
|
||||||
@@ -63,7 +74,7 @@ export class AISelectboxToolManager {
|
|||||||
this.canvasManager.canvas.remove(this.demoObject)
|
this.canvasManager.canvas.remove(this.demoObject)
|
||||||
this.canvasManager.canvas.renderAll()
|
this.canvasManager.canvas.renderAll()
|
||||||
|
|
||||||
this.createSelectbox()
|
// this.createSelectbox()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -81,15 +92,11 @@ export class AISelectboxToolManager {
|
|||||||
stroke: "rgba(255, 77, 71, 1)",
|
stroke: "rgba(255, 77, 71, 1)",
|
||||||
strokeWidth: 1.5,
|
strokeWidth: 1.5,
|
||||||
strokeDashArray: [4, 4],
|
strokeDashArray: [4, 4],
|
||||||
fill: "rgba(255, 186, 186, 0.5)",
|
fill: "transparent",
|
||||||
strokeUniform: true, // 保持描边宽度不随缩放改变
|
strokeUniform: true, // 保持描边宽度不随缩放改变
|
||||||
// strokeLineCap: "round",// 折线端点样式
|
selectable: false,
|
||||||
// strokeLineJoin: "bevel", // 折线连接样式
|
evented: false,
|
||||||
// selectable: false,
|
absolutePositioned: true,
|
||||||
// evented: false,
|
|
||||||
excludeFromExport: true,
|
|
||||||
hoverCursor: "default",
|
|
||||||
moveCursor: "default",
|
|
||||||
};
|
};
|
||||||
async createSelectbox() {
|
async createSelectbox() {
|
||||||
const url = "http://118.31.39.42:3000/falls/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png"
|
const url = "http://118.31.39.42:3000/falls/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png"
|
||||||
@@ -112,200 +119,31 @@ export class AISelectboxToolManager {
|
|||||||
}).join(" L ");
|
}).join(" L ");
|
||||||
const path = new fabric.Path(`M ${str} z`);
|
const path = new fabric.Path(`M ${str} z`);
|
||||||
path.set({
|
path.set({
|
||||||
left: left + minX * scaleX,
|
left: left + minX,
|
||||||
top: top + minY * scaleY,
|
top: top + minY,
|
||||||
scaleX: scaleX,
|
scaleX: scaleX,
|
||||||
scaleY: scaleY,
|
scaleY: scaleY,
|
||||||
...this.selectionStyle,
|
...this.selectionStyle,
|
||||||
});
|
});
|
||||||
const rect1 = new fabric.Rect({
|
const group = await this.layerManager.createGroupLayer({
|
||||||
left: 0,
|
clipPath: path,
|
||||||
top: 0,
|
}, false, false)
|
||||||
width: 100,
|
const rect = await this.layerManager.createRectLayer({
|
||||||
height: 100,
|
width: path.width,
|
||||||
fill: '#f00',
|
height: path.height,
|
||||||
info: {
|
left: left + minX,
|
||||||
id: createId("rect"),
|
top: top + minY,
|
||||||
name: '矩形图层',
|
fill: "rgba(255, 186, 186, 0.5)",
|
||||||
}
|
info: { parentId: group.info.id },
|
||||||
})
|
}, false, true)
|
||||||
const rect2 = new fabric.Rect({
|
await this.canvasManager.updateSubLayerClipPath()
|
||||||
left: 200,
|
await this.layerManager.updateLayerThumbnailsById(rect.info.id, "", false)
|
||||||
top: 200,
|
await this.layerManager.updateLayerThumbnailsById(group.info.id, rect.thumbnail)
|
||||||
width: 100,
|
this.stateManager.recordState()
|
||||||
height: 100,
|
this.toolManager.setTool(OperationType.SELECT)
|
||||||
fill: '#ff0',
|
|
||||||
info: {
|
|
||||||
id: createId("rect"),
|
|
||||||
name: '矩形图层',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.layerManager.createGroupLayer({
|
|
||||||
child: [rect1, rect2],
|
|
||||||
})
|
|
||||||
// this.canvasManager.canvas.add(path)
|
|
||||||
// this.canvasManager.canvas.renderAll()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dispose() { }
|
dispose() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取对象黑白通道画布
|
|
||||||
* @param {fabric.Object} object - 要处理的 fabric 对象
|
|
||||||
* @param {ImageData} revData - 相反的ImageData,白通道的相同位置是否为透明,revData为白色为透明,黑色为不透明
|
|
||||||
* @param {number} diff - 差值,默认 25
|
|
||||||
* @param {Object} rgba - 自定义 rgba 值,默认 { r: 255, g: 255, b: 255, a: 255 }
|
|
||||||
* @param {boolean} isMerge - 是否合并,true=合并revData,false=反转revData
|
|
||||||
* @returns {HTMLCanvasElement|null} 包含黑白通道的画布,或 null 如果失败
|
|
||||||
*/
|
|
||||||
export function getObjectAlphaToCanvas(object, revData, diff = 30, rgba = { r: 255, g: 255, b: 255, a: 255 }, isMerge = false) {
|
|
||||||
const image = object.getElement();
|
|
||||||
if (image.nodeName !== "IMG" && image.nodeName !== "CANVAS") {
|
|
||||||
console.warn("对象不是图片");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { width, height } = image;
|
|
||||||
if (!width || !height) {
|
|
||||||
console.warn("对象没有元素");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
ctx.drawImage(image, 0, 0, width, height);
|
|
||||||
const data = ctx.getImageData(0, 0, width, height);
|
|
||||||
for (let i = 0; i < data.data.length; i += 4) {
|
|
||||||
const r = data.data[i + 0];
|
|
||||||
const g = data.data[i + 1];
|
|
||||||
const b = data.data[i + 2];
|
|
||||||
const a = data.data[i + 3];
|
|
||||||
const revR = revData?.data[i + 0] || 0;
|
|
||||||
const revG = revData?.data[i + 1] || 0;
|
|
||||||
const revB = revData?.data[i + 2] || 0;
|
|
||||||
const revA = revData?.data[i + 3] || 0;
|
|
||||||
let isHave = false;
|
|
||||||
if (r || g || b || a) {
|
|
||||||
if (revR > diff || revG > diff || revB > diff || revA > diff) {
|
|
||||||
isHave = false;
|
|
||||||
} else {
|
|
||||||
isHave = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isMerge && (revR || revG || revB || revA)) isHave = true;
|
|
||||||
if (isHave) {
|
|
||||||
data.data[i + 0] = rgba.r;
|
|
||||||
data.data[i + 1] = rgba.g;
|
|
||||||
data.data[i + 2] = rgba.b;
|
|
||||||
data.data[i + 3] = rgba.a;
|
|
||||||
} else {
|
|
||||||
data.data[i + 0] = 0;
|
|
||||||
data.data[i + 1] = 0;
|
|
||||||
data.data[i + 2] = 0;
|
|
||||||
data.data[i + 3] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.clearRect(0, 0, width, height);
|
|
||||||
ctx.putImageData(data, 0, 0);
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 图片边界跟踪算法(透明底)
|
|
||||||
* @param {HTMLCanvasElement} canvas - canvas元素
|
|
||||||
* @param {Number} scale - 缩放比例
|
|
||||||
* @returns {Array} 边界点数组 [{x, y}, ...]
|
|
||||||
*/
|
|
||||||
export function traceImageContour(canvas) {
|
|
||||||
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
||||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
||||||
const data = imageData.data;
|
|
||||||
const width = canvas.width;
|
|
||||||
const height = canvas.height;
|
|
||||||
|
|
||||||
// 查找起始点(第一个不透明像素)
|
|
||||||
let startX = -1;
|
|
||||||
let startY = -1;
|
|
||||||
|
|
||||||
outer: for (let y = 0; y < height; y++) {
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
const index = (y * width + x) * 4;
|
|
||||||
if (data[index + 3] > 0) {
|
|
||||||
startX = x;
|
|
||||||
startY = y;
|
|
||||||
break outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startX === -1) return []; // 没有不透明像素
|
|
||||||
|
|
||||||
// Moore-Neighbor边界跟踪算法
|
|
||||||
const contour = [];
|
|
||||||
const visited = new Set();
|
|
||||||
const directions = [
|
|
||||||
[-1, 0],
|
|
||||||
[-1, -1],
|
|
||||||
[0, -1],
|
|
||||||
[1, -1],
|
|
||||||
[1, 0],
|
|
||||||
[1, 1],
|
|
||||||
[0, 1],
|
|
||||||
[-1, 1],
|
|
||||||
];
|
|
||||||
|
|
||||||
let currentX = startX;
|
|
||||||
let currentY = startY;
|
|
||||||
let backtrackDir = 4; // 起始方向:右
|
|
||||||
|
|
||||||
do {
|
|
||||||
const pointKey = `${currentX},${currentY}`;
|
|
||||||
if (!visited.has(pointKey)) {
|
|
||||||
contour.push({ x: currentX, y: currentY });
|
|
||||||
visited.add(pointKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从右方向开始顺时针查找
|
|
||||||
let found = false;
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
const dir = (backtrackDir + i) % 8;
|
|
||||||
const dx = directions[dir][0];
|
|
||||||
const dy = directions[dir][1];
|
|
||||||
const checkX = currentX + dx;
|
|
||||||
const checkY = currentY + dy;
|
|
||||||
|
|
||||||
if (
|
|
||||||
checkX >= 0 &&
|
|
||||||
checkX < width &&
|
|
||||||
checkY >= 0 &&
|
|
||||||
checkY < height
|
|
||||||
) {
|
|
||||||
const index = (checkY * width + checkX) * 4;
|
|
||||||
if (data[index + 3] > 0) {
|
|
||||||
currentX = checkX;
|
|
||||||
currentY = checkY;
|
|
||||||
backtrackDir = (dir + 5) % 8; // 下一个开始查找的方向
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) break;
|
|
||||||
} while (
|
|
||||||
!(currentX === startX && currentY === startY) &&
|
|
||||||
visited.size < width * height
|
|
||||||
);
|
|
||||||
|
|
||||||
return contour;
|
|
||||||
}
|
|
||||||
@@ -5,11 +5,12 @@ import { AnimationManager } from './AnimationManager'
|
|||||||
import { detectDeviceType } from '../tools/index'
|
import { detectDeviceType } from '../tools/index'
|
||||||
import { CanvasEventManager } from "./events/CanvasEventManager";
|
import { CanvasEventManager } from "./events/CanvasEventManager";
|
||||||
import { OperationType } from '../tools/layerHelper'
|
import { OperationType } from '../tools/layerHelper'
|
||||||
|
import { cloneObjects } from '../tools/canvasMethod'
|
||||||
import { createId } from '../../tools/tools'
|
import { createId } from '../../tools/tools'
|
||||||
import md5 from 'md5'
|
import md5 from 'md5'
|
||||||
|
|
||||||
// 自定义画布转对象属性
|
// 自定义画布转对象属性
|
||||||
fabric.Object.prototype.customProperties = ["top", "left", "width", "height", "scaleX", "scaleY", "info", "thumbnail"];
|
fabric.Object.prototype.customProperties = ["top", "left", "width", "height", "scaleX", "scaleY", "info", "thumbnail", "absolutePositioned"];
|
||||||
fabric.Object.prototype.toObject_ = fabric.Object.prototype.toObject
|
fabric.Object.prototype.toObject_ = fabric.Object.prototype.toObject
|
||||||
fabric.Object.prototype.toObject = function () {
|
fabric.Object.prototype.toObject = function () {
|
||||||
const args = [...arguments]
|
const args = [...arguments]
|
||||||
@@ -129,25 +130,28 @@ export class CanvasManager {
|
|||||||
|
|
||||||
/** 测试-开始 */
|
/** 测试-开始 */
|
||||||
// this.stateManager.setIsRecord(false)
|
// this.stateManager.setIsRecord(false)
|
||||||
// const rect = await this.layerManager.createRectLayer({ left: 200 })
|
// const groupObject = await this.layerManager.createGroupLayer()
|
||||||
// await this.layerManager.createStarLayer({ left: 400 })
|
// const parentId = groupObject.info.id
|
||||||
// await this.layerManager.createArrowLayer({ left: 600 })
|
// const rect = await this.layerManager.createRectLayer({ left: 200, info: { parentId } })
|
||||||
|
// const star = await this.layerManager.createStarLayer({ left: 400, info: { parentId } })
|
||||||
|
// const arrow = await this.layerManager.createArrowLayer({ left: 600, info: { parentId } })
|
||||||
|
// await this.layerManager.createGroupLayer()
|
||||||
// this.layerManager.setActiveID(rect.info.id)
|
// this.layerManager.setActiveID(rect.info.id)
|
||||||
// this.stateManager.setIsRecord(true)
|
// this.stateManager.setIsRecord(true)
|
||||||
/** 测试-结束 */
|
/** 测试-结束 */
|
||||||
|
|
||||||
this.resetZoom(false, true)// 画布居中
|
this.resetZoom(false, true)// 画布居中
|
||||||
|
|
||||||
|
this.stateManager.toolManager.setTool(OperationType.SELECT)
|
||||||
this.layerManager.updateLayers()
|
this.layerManager.updateLayers()
|
||||||
this.stateManager.recordState()
|
this.stateManager.recordState()
|
||||||
// this.stateManager.toolManager.setTool(OperationType.RECTANGLE)
|
|
||||||
}
|
}
|
||||||
/** 画布添加对象 */
|
/** 画布添加对象 */
|
||||||
async add(obj: any, isRecord = 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 (id) {
|
if (id) {
|
||||||
this.layerManager.updateLayers()
|
await this.layerManager.updateLayers(!!obj.info.parentId)
|
||||||
this.renderAll()
|
this.renderAll()
|
||||||
await this.layerManager.updateLayerThumbnailsById(id)
|
await this.layerManager.updateLayerThumbnailsById(id)
|
||||||
}
|
}
|
||||||
@@ -162,6 +166,35 @@ export class CanvasManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 更新子图层裁剪区域 */
|
||||||
|
async updateSubLayerClipPath() {
|
||||||
|
const objects = this.getObjects().filter((v: any) => v.type !== "group" && !!v.info?.id);
|
||||||
|
for (let i = 0; i < objects.length; i++) {
|
||||||
|
let object = objects[i]
|
||||||
|
if (object.clipPath) object.set({ clipPath: null })
|
||||||
|
let group = this.getObjectById(object.info.parentId)
|
||||||
|
if (!group) continue
|
||||||
|
let path = group.clipPath
|
||||||
|
if (!path) continue
|
||||||
|
let clipPath = await cloneObjects([path]).then((v) => v[0])
|
||||||
|
clipPath.set({
|
||||||
|
absolutePositioned: true,
|
||||||
|
})
|
||||||
|
object.set({ clipPath })
|
||||||
|
}
|
||||||
|
this.renderAll()
|
||||||
|
}
|
||||||
|
/** 排序画布对象 */
|
||||||
|
async sortObjectByIds(ids: string[], isRecord?: boolean) {
|
||||||
|
ids.forEach((id, index) => {
|
||||||
|
this.canvas.moveTo(this.getObjectById(id), index)
|
||||||
|
})
|
||||||
|
await this.updateSubLayerClipPath()
|
||||||
|
this.renderAll()
|
||||||
|
if (isRecord) this.stateManager.recordState()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** 设置画布事件 */
|
/** 设置画布事件 */
|
||||||
setupCanvasEvents() {
|
setupCanvasEvents() {
|
||||||
// 创建画布事件管理器
|
// 创建画布事件管理器
|
||||||
@@ -177,8 +210,18 @@ export class CanvasManager {
|
|||||||
}
|
}
|
||||||
/** 设置激活对象 */
|
/** 设置激活对象 */
|
||||||
setActiveObjectById(id: string) {
|
setActiveObjectById(id: string) {
|
||||||
|
this.discardActiveObject()
|
||||||
const obj = this.getObjectById(id)
|
const obj = this.getObjectById(id)
|
||||||
if (obj && obj.evented) this.canvas.setActiveObject(obj)
|
if (!obj) return
|
||||||
|
if (obj.type === "group") {
|
||||||
|
const objects = [];
|
||||||
|
this.getObjects().forEach((item: any) => {
|
||||||
|
if (item?.info?.parentId === id) objects.push(item)
|
||||||
|
})
|
||||||
|
if (objects.length > 0) this.canvas.setActiveObject(new fabric.ActiveSelection(objects, { canvas: this.canvas }));
|
||||||
|
} else {
|
||||||
|
if (obj.evented) this.canvas.setActiveObject(obj)
|
||||||
|
}
|
||||||
this.renderAll()
|
this.renderAll()
|
||||||
}
|
}
|
||||||
resetZoom(animated = true, adaptive = true) {
|
resetZoom(animated = true, adaptive = true) {
|
||||||
@@ -222,11 +265,11 @@ export class CanvasManager {
|
|||||||
renderAll() {
|
renderAll() {
|
||||||
this.canvas.renderAll()
|
this.canvas.renderAll()
|
||||||
}
|
}
|
||||||
deleteObjectById(id: string) {
|
deleteObjectById(id: string, isUpdate = true) {
|
||||||
const object = this.getObjectById(id)
|
const object = this.getObjectById(id)
|
||||||
if (object) {
|
if (object) {
|
||||||
this.canvas.remove(object)
|
this.canvas.remove(object)
|
||||||
this.layerManager.updateLayers()
|
if (isUpdate) this.layerManager.updateLayers()
|
||||||
this.renderAll()
|
this.renderAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,13 +278,6 @@ export class CanvasManager {
|
|||||||
this.canvas.discardActiveObject()
|
this.canvas.discardActiveObject()
|
||||||
this.renderAll()
|
this.renderAll()
|
||||||
}
|
}
|
||||||
// 拖拽排序
|
|
||||||
dragSort(id, newIndex) {
|
|
||||||
this.canvas.moveTo(this.getObjectById(id), newIndex)
|
|
||||||
this.layerManager.updateLayers()
|
|
||||||
this.renderAll()
|
|
||||||
this.stateManager.recordState()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 画笔事件 */
|
/** 画笔事件 */
|
||||||
setupBrushEvents() {
|
setupBrushEvents() {
|
||||||
@@ -254,13 +290,13 @@ export class CanvasManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
/** 处理绘制图像 */
|
/** 处理绘制图像 */
|
||||||
handleDrawImage(fabricImage: fabric.Object) {
|
async handleDrawImage(fabricImage: fabric.Object) {
|
||||||
const activeID = this.stateManager.layerManager.activeID.value
|
const activeID = this.stateManager.layerManager.activeID.value
|
||||||
const activeLayer = this.getObjectById(activeID)
|
const activeLayer = this.getObjectById(activeID)
|
||||||
if (activeLayer) {
|
if (activeLayer && activeLayer.fill?.repeat !== "repeat") {
|
||||||
this.layerManager.imageMergeToLayer(activeLayer, fabricImage)
|
this.layerManager.imageMergeToLayer(activeLayer, fabricImage)
|
||||||
} else {
|
} else {
|
||||||
const emptyLayer = this.layerManager.createEmptyLayer(false);
|
const emptyLayer = await this.layerManager.createEmptyLayer(false);
|
||||||
this.layerManager.setActiveID(emptyLayer.info.id, false)
|
this.layerManager.setActiveID(emptyLayer.info.id, false)
|
||||||
this.layerManager.imageMergeToLayer(emptyLayer, fabricImage)
|
this.layerManager.imageMergeToLayer(emptyLayer, fabricImage)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ref } from 'vue'
|
|||||||
import { fabric } from 'fabric-with-all'
|
import { fabric } from 'fabric-with-all'
|
||||||
import { createId } from '../../tools/tools'
|
import { createId } from '../../tools/tools'
|
||||||
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
|
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
|
||||||
import { OperationType } from '../tools/layerHelper'
|
import { OperationType, BlendMode } from '../tools/layerHelper'
|
||||||
import { getArrowPath, cloneObjects, getStarArr } from '../tools/canvasMethod'
|
import { getArrowPath, cloneObjects, getStarArr } from '../tools/canvasMethod'
|
||||||
|
|
||||||
export class LayerManager {
|
export class LayerManager {
|
||||||
@@ -18,7 +18,12 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
onMounted() { }
|
onMounted() { }
|
||||||
setActiveID(id: string, isActive = true) {
|
setActiveID(id: string, isActive = true) {
|
||||||
this.activeID.value = id
|
const layer = this.getLayerById(id)
|
||||||
|
if (layer?.type === "group") {
|
||||||
|
this.activeID.value = ""
|
||||||
|
} else {
|
||||||
|
this.activeID.value = id
|
||||||
|
}
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
this.canvasManager.setActiveObjectById(id)
|
this.canvasManager.setActiveObjectById(id)
|
||||||
this.stateManager.toolManager.setTool(OperationType.SELECT)
|
this.stateManager.toolManager.setTool(OperationType.SELECT)
|
||||||
@@ -28,7 +33,18 @@ export class LayerManager {
|
|||||||
return this.getLayerById(this.activeID.value)
|
return this.getLayerById(this.activeID.value)
|
||||||
}
|
}
|
||||||
getLayerById(id) {
|
getLayerById(id) {
|
||||||
return this.layers.value.find((item: any) => item.info.id === id)
|
function call(arr) {
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
let v = arr[i]
|
||||||
|
if (v.info.id === id) return v
|
||||||
|
if (v.children) {
|
||||||
|
let layer = call(v.children)
|
||||||
|
if (layer) return layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return call(this.layers.value)
|
||||||
}
|
}
|
||||||
setLayerNameById(id, name: string) {
|
setLayerNameById(id, name: string) {
|
||||||
const layer = this.getLayerById(id)
|
const layer = this.getLayerById(id)
|
||||||
@@ -82,6 +98,10 @@ export class LayerManager {
|
|||||||
|
|
||||||
/** 删除指定图层 */
|
/** 删除指定图层 */
|
||||||
deleteLayerById(id, isActive = true) {
|
deleteLayerById(id, isActive = true) {
|
||||||
|
const layer = this.getLayerById(id)
|
||||||
|
if (layer.children) {
|
||||||
|
layer.children.forEach(v => this.canvasManager.deleteObjectById(v.info.id, false))
|
||||||
|
}
|
||||||
this.canvasManager.deleteObjectById(id)
|
this.canvasManager.deleteObjectById(id)
|
||||||
if (id === this.activeID.value && isActive) {
|
if (id === this.activeID.value && isActive) {
|
||||||
this.setActiveID(this.layers.value[0]?.info?.id || "")
|
this.setActiveID(this.layers.value[0]?.info?.id || "")
|
||||||
@@ -106,17 +126,38 @@ export class LayerManager {
|
|||||||
this.setActiveID(newObject.info.id)
|
this.setActiveID(newObject.info.id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 拖拽排序
|
/** 根据layers排序图层 */
|
||||||
dragSort(id, newIndex) {
|
async sortLayers(isRecord?: boolean) {
|
||||||
const index = Math.abs(this.layers.value.length - newIndex - 1)
|
const ids = [];
|
||||||
this.canvasManager.dragSort(id, index)
|
call(this.layers.value)
|
||||||
|
await this.canvasManager.sortObjectByIds(ids.reverse(), isRecord)
|
||||||
|
function call(arr) {
|
||||||
|
arr.forEach(v => {
|
||||||
|
ids.push(v.info.id)
|
||||||
|
if (v.children) call(v.children)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新图层列表
|
// 更新图层列表
|
||||||
updateLayers() {
|
async updateLayers(isSort = false) {
|
||||||
this.layers.value = this.canvasManager.getObjects()
|
const objects = this.canvasManager.getObjects().map(v => v.toObject()).filter(v => !!v.info?.id).reverse()
|
||||||
.filter((v: any) => !!v?.info?.id)
|
objects.forEach(v => {
|
||||||
.reverse()
|
if (v.type === "group") {
|
||||||
.map(v => v.toObject())
|
if (!v.children) v.children = []
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parentId = v.info?.parentId
|
||||||
|
if (!parentId) return
|
||||||
|
objects.forEach((obj: any) => {
|
||||||
|
if (obj.info?.id !== parentId) return
|
||||||
|
if (!obj.children) obj.children = []
|
||||||
|
obj.children.push(v)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const layers = objects.filter(v => !v.info?.parentId)
|
||||||
|
this.layers.value = layers
|
||||||
|
if (isSort) await this.sortLayers()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置图层位置-不设置默认居中 */
|
/** 设置图层位置-不设置默认居中 */
|
||||||
@@ -136,7 +177,7 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/** 创建空图层 */
|
/** 创建空图层 */
|
||||||
createEmptyLayer(isRecord = true, isActive = false) {
|
async createEmptyLayer(isRecord = true, isActive = false) {
|
||||||
const emptyObject = new fabric.Rect({
|
const emptyObject = new fabric.Rect({
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
@@ -147,37 +188,28 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setLayerPosition(emptyObject)
|
this.setLayerPosition(emptyObject)
|
||||||
this.canvasManager.add(emptyObject, isRecord)
|
await this.canvasManager.add(emptyObject, isRecord)
|
||||||
if (isActive) this.setActiveID(emptyObject.info.id, false)
|
if (isActive) this.setActiveID(emptyObject.info.id, false)
|
||||||
return emptyObject
|
return emptyObject
|
||||||
}
|
}
|
||||||
/** 创建组图层 */
|
/** 创建组图层 */
|
||||||
createGroupLayer(options?: any, isRecord = true, isActive = false) {
|
async createGroupLayer(options?: any, isRecord = true, isActive = false) {
|
||||||
const child = options?.child || []
|
const children = options?.children || []
|
||||||
delete options.child
|
delete options.children
|
||||||
const groupObject = new fabric.Group(child, {
|
const groupObject = new fabric.Group(children, {
|
||||||
// subTargetCheck: true, // 关键:检测子对象
|
...(options || {}),
|
||||||
// interactive: true, // 启用交互
|
hasControls: false, // 不显示控制点
|
||||||
// hasControls: true,
|
hasBorders: false, // 不显示边框
|
||||||
// hasBorders: true,
|
selectable: false, // 不可选中(可选)
|
||||||
|
|
||||||
// // 子对象样式
|
|
||||||
// cornerColor: 'blue',
|
|
||||||
// cornerSize: 8,
|
|
||||||
// borderColor: 'green',
|
|
||||||
|
|
||||||
// // 允许子对象独立变换
|
|
||||||
// lockScalingX: false,
|
|
||||||
// lockScalingY: false,
|
|
||||||
// lockRotation: false,
|
|
||||||
info: {
|
info: {
|
||||||
id: createId("group"),
|
id: createId("group"),
|
||||||
name: '组图层',
|
name: '智能选区组',
|
||||||
|
showChildren: true,
|
||||||
...(options?.info || {}),
|
...(options?.info || {}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// this.setLayerPosition(groupObject)
|
this.setLayerPosition(groupObject, options)
|
||||||
this.canvasManager.add(groupObject, isRecord)
|
await this.canvasManager.add(groupObject, isRecord)
|
||||||
if (isActive) this.setActiveID(groupObject.info.id, false)
|
if (isActive) this.setActiveID(groupObject.info.id, false)
|
||||||
return groupObject
|
return groupObject
|
||||||
}
|
}
|
||||||
@@ -199,7 +231,7 @@ export class LayerManager {
|
|||||||
return textObject
|
return textObject
|
||||||
}
|
}
|
||||||
/** 创建矩形图层 */
|
/** 创建矩形图层 */
|
||||||
async createRectLayer(options?: any, isActive = false) {
|
async createRectLayer(options?: any, isRecord = true, isActive = true) {
|
||||||
const rectObject = new fabric.Rect({
|
const rectObject = new fabric.Rect({
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
@@ -213,12 +245,12 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setLayerPosition(rectObject, options)
|
this.setLayerPosition(rectObject, options)
|
||||||
await this.canvasManager.add(rectObject)
|
await this.canvasManager.add(rectObject, isRecord)
|
||||||
if (isActive) this.setActiveID(rectObject.info.id)
|
if (isActive) this.setActiveID(rectObject.info.id)
|
||||||
return rectObject
|
return rectObject
|
||||||
}
|
}
|
||||||
/** 创建直线图层 */
|
/** 创建直线图层 */
|
||||||
async createLineLayer(options?: any, isActive = false) {
|
async createLineLayer(options?: any, isRecord = true, isActive = true) {
|
||||||
const line = [options?.x1 || 0, options?.y1 || 0, options?.x2 || 100, options?.y2 || 0]
|
const line = [options?.x1 || 0, options?.y1 || 0, options?.x2 || 100, options?.y2 || 0]
|
||||||
delete options.x1
|
delete options.x1
|
||||||
delete options.y1
|
delete options.y1
|
||||||
@@ -235,14 +267,13 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setLayerPosition(lineObject, options)
|
this.setLayerPosition(lineObject, options)
|
||||||
await this.canvasManager.add(lineObject)
|
await this.canvasManager.add(lineObject, isRecord)
|
||||||
if (isActive) this.setActiveID(lineObject.info.id)
|
if (isActive) this.setActiveID(lineObject.info.id)
|
||||||
return lineObject
|
return lineObject
|
||||||
}
|
}
|
||||||
/** 创建椭圆图层 */
|
/** 创建椭圆图层 */
|
||||||
async createEllipseLayer(options?: any, isActive = false) {
|
async createEllipseLayer(options?: any, isRecord = true, isActive = true) {
|
||||||
const ellipseObject = new fabric.Ellipse({
|
const ellipseObject = new fabric.Ellipse({
|
||||||
radius: 50,
|
|
||||||
fill: '#000',
|
fill: '#000',
|
||||||
strokeWidth: 0,
|
strokeWidth: 0,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
@@ -258,7 +289,7 @@ export class LayerManager {
|
|||||||
return ellipseObject
|
return ellipseObject
|
||||||
}
|
}
|
||||||
/** 创建三角形图层 */
|
/** 创建三角形图层 */
|
||||||
async createTriangleLayer(options?: any, isActive = false) {
|
async createTriangleLayer(options?: any, isRecord = true, isActive = true) {
|
||||||
const triangleObject = new fabric.Triangle({
|
const triangleObject = new fabric.Triangle({
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
@@ -272,12 +303,12 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setLayerPosition(triangleObject, options)
|
this.setLayerPosition(triangleObject, options)
|
||||||
await this.canvasManager.add(triangleObject)
|
await this.canvasManager.add(triangleObject, isRecord)
|
||||||
if (isActive) this.setActiveID(triangleObject.info.id)
|
if (isActive) this.setActiveID(triangleObject.info.id)
|
||||||
return triangleObject
|
return triangleObject
|
||||||
}
|
}
|
||||||
/** 创建五角星图层 */
|
/** 创建五角星图层 */
|
||||||
async createStarLayer(options?: any, isActive = false) {
|
async createStarLayer(options?: any, isRecord = true, isActive = true) {
|
||||||
const width = options?.width || 100
|
const width = options?.width || 100
|
||||||
const height = options?.height || 100
|
const height = options?.height || 100
|
||||||
delete options.points
|
delete options.points
|
||||||
@@ -292,12 +323,12 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setLayerPosition(starObject, options)
|
this.setLayerPosition(starObject, options)
|
||||||
await this.canvasManager.add(starObject)
|
await this.canvasManager.add(starObject, isRecord)
|
||||||
if (isActive) this.setActiveID(starObject.info.id)
|
if (isActive) this.setActiveID(starObject.info.id)
|
||||||
return starObject
|
return starObject
|
||||||
}
|
}
|
||||||
/** 创建箭头图层 */
|
/** 创建箭头图层 */
|
||||||
async createArrowLayer(options?: any, isActive = false) {
|
async createArrowLayer(options?: any, isRecord = true, isActive = true) {
|
||||||
const width = options?.width || 100
|
const width = options?.width || 100
|
||||||
const height = options?.height || 10
|
const height = options?.height || 10
|
||||||
delete options.width
|
delete options.width
|
||||||
@@ -316,7 +347,7 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.setLayerPosition(arrowObject, options)
|
this.setLayerPosition(arrowObject, options)
|
||||||
this.canvasManager.add(arrowObject)
|
await this.canvasManager.add(arrowObject, isRecord)
|
||||||
if (isActive) this.setActiveID(arrowObject.info.id)
|
if (isActive) this.setActiveID(arrowObject.info.id)
|
||||||
return arrowObject
|
return arrowObject
|
||||||
}
|
}
|
||||||
@@ -324,7 +355,7 @@ export class LayerManager {
|
|||||||
|
|
||||||
|
|
||||||
/** 创建图片图层 */
|
/** 创建图片图层 */
|
||||||
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true) {
|
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true, isActive = true) {
|
||||||
const { canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize();
|
const { canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize();
|
||||||
|
|
||||||
const imageObject = await new Promise((resolve) => {
|
const imageObject = await new Promise((resolve) => {
|
||||||
@@ -350,7 +381,7 @@ export class LayerManager {
|
|||||||
}) as fabric.Object
|
}) as fabric.Object
|
||||||
this.setLayerPosition(imageObject, options)
|
this.setLayerPosition(imageObject, options)
|
||||||
await this.canvasManager.add(imageObject, isRecord)
|
await this.canvasManager.add(imageObject, isRecord)
|
||||||
this.setActiveID(imageObject.info.id)
|
if (isActive) this.setActiveID(imageObject.info.id)
|
||||||
return imageObject
|
return imageObject
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,6 +394,7 @@ export class LayerManager {
|
|||||||
left: info.left,
|
left: info.left,
|
||||||
top: info.top,
|
top: info.top,
|
||||||
info: {
|
info: {
|
||||||
|
...(targetLayer?.info || {}),
|
||||||
id: createId("image"),
|
id: createId("image"),
|
||||||
name: targetLayer?.info?.name || "合并图层",
|
name: targetLayer?.info?.name || "合并图层",
|
||||||
}
|
}
|
||||||
@@ -370,12 +402,17 @@ export class LayerManager {
|
|||||||
resolve(img)
|
resolve(img)
|
||||||
}, { crossOrigin: 'anonymous' })
|
}, { crossOrigin: 'anonymous' })
|
||||||
})
|
})
|
||||||
// console.log(mergedImage)
|
|
||||||
const index = this.canvasManager.getObjects().indexOf(targetLayer);
|
const index = this.canvasManager.getObjects().indexOf(targetLayer);
|
||||||
this.deleteLayerById(targetLayer.info.id, false)
|
this.deleteLayerById(targetLayer.info.id, false)
|
||||||
this.setActiveID(mergedImage.info.id, false)
|
|
||||||
|
const nid = mergedImage.info.id
|
||||||
await this.canvasManager.add(mergedImage, false);
|
await this.canvasManager.add(mergedImage, false);
|
||||||
|
this.setActiveID(nid, false)
|
||||||
this.canvasManager.canvas.moveTo(mergedImage, index);
|
this.canvasManager.canvas.moveTo(mergedImage, index);
|
||||||
|
|
||||||
|
// this.stateManager.objectManager.setBlendMode(nid, BlendMode.MULTIPLY)
|
||||||
|
// this.stateManager.objectManager.setFillRepeat(nid, false)
|
||||||
|
|
||||||
this.canvasManager.renderAll()
|
this.canvasManager.renderAll()
|
||||||
this.updateLayers()
|
this.updateLayers()
|
||||||
this.stateManager.recordState()
|
this.stateManager.recordState()
|
||||||
@@ -391,12 +428,12 @@ export class LayerManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/** 更新图层缩略图 */
|
/** 更新图层缩略图 */
|
||||||
async updateLayerThumbnailsById(id: string) {
|
async updateLayerThumbnailsById(id: string, thumbnail?: string, isUpdate = true) {
|
||||||
const object = this.canvasManager.getObjectById(id);
|
const object = this.canvasManager.getObjectById(id);
|
||||||
if (!object) return;
|
if (!object) return;
|
||||||
const url = await exportObjectToThumbnail(object);
|
const url = thumbnail || await exportObjectToThumbnail(object);
|
||||||
object.thumbnail = url
|
object.thumbnail = url
|
||||||
this.updateLayers()
|
if (isUpdate) this.updateLayers()
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() { }
|
dispose() { }
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export class ObjectManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 设置平铺状态 */
|
/** 设置平铺状态 */
|
||||||
setFillRepeat(id: string) {
|
setFillRepeat(id: string, isRecord = true) {
|
||||||
const object = this.canvasManager.getObjectById(id)
|
const object = this.canvasManager.getObjectById(id)
|
||||||
if (!object) return console.warn('设置平铺状态失败,对象不存在ID:', id)
|
if (!object) return console.warn('设置平铺状态失败,对象不存在ID:', id)
|
||||||
if (object.type !== 'image') return console.warn('设置平铺状态失败,对象不是图片类型:', id)
|
if (object.type !== 'image') return console.warn('设置平铺状态失败,对象不是图片类型:', id)
|
||||||
@@ -133,7 +133,7 @@ export class ObjectManager {
|
|||||||
});
|
});
|
||||||
rect.set("fill", pattern)
|
rect.set("fill", pattern)
|
||||||
this.canvasManager.canvas.remove(object)
|
this.canvasManager.canvas.remove(object)
|
||||||
this.canvasManager.add(rect)
|
this.canvasManager.add(rect, isRecord)
|
||||||
}
|
}
|
||||||
/** 获取填充对象 */
|
/** 获取填充对象 */
|
||||||
getFillRepeatObject(id: string) {
|
getFillRepeatObject(id: string) {
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export class ShapeToolManager {
|
|||||||
upRectangle(object) {
|
upRectangle(object) {
|
||||||
if (object.width === 0) object.width = 100
|
if (object.width === 0) object.width = 100
|
||||||
if (object.height === 0) object.height = 100
|
if (object.height === 0) object.height = 100
|
||||||
this.layerManager.createRectLayer(object, true)
|
this.layerManager.createRectLayer(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 绘制直线 */
|
/** 绘制直线 */
|
||||||
@@ -151,7 +151,7 @@ export class ShapeToolManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
upLine(object) {
|
upLine(object) {
|
||||||
this.layerManager.createLineLayer(object, true)
|
this.layerManager.createLineLayer(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 绘制椭圆 */
|
/** 绘制椭圆 */
|
||||||
@@ -170,7 +170,7 @@ export class ShapeToolManager {
|
|||||||
upEllipse(object) {
|
upEllipse(object) {
|
||||||
if (object.rx === 0) object.rx = 50
|
if (object.rx === 0) object.rx = 50
|
||||||
if (object.ry === 0) object.ry = 50
|
if (object.ry === 0) object.ry = 50
|
||||||
this.layerManager.createEllipseLayer(object, true)
|
this.layerManager.createEllipseLayer(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ export class ShapeToolManager {
|
|||||||
upTriangle(object) {
|
upTriangle(object) {
|
||||||
if (object.width === 0) object.width = 100
|
if (object.width === 0) object.width = 100
|
||||||
if (object.height === 0) object.height = 100
|
if (object.height === 0) object.height = 100
|
||||||
this.layerManager.createTriangleLayer(object, true)
|
this.layerManager.createTriangleLayer(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ export class ShapeToolManager {
|
|||||||
upStar(object) {
|
upStar(object) {
|
||||||
if (object.width === 0) object.width = 100
|
if (object.width === 0) object.width = 100
|
||||||
if (object.height === 0) object.height = 100
|
if (object.height === 0) object.height = 100
|
||||||
this.layerManager.createStarLayer(object, true)
|
this.layerManager.createStarLayer(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 绘制箭头 */
|
/** 绘制箭头 */
|
||||||
@@ -249,7 +249,7 @@ export class ShapeToolManager {
|
|||||||
top: this.startY,
|
top: this.startY,
|
||||||
}, true)
|
}, true)
|
||||||
} else {
|
} else {
|
||||||
this.layerManager.createArrowLayer(object, true)
|
this.layerManager.createArrowLayer(object)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export class StateManager {
|
|||||||
brushManager: any
|
brushManager: any
|
||||||
keyEventManager: any
|
keyEventManager: any
|
||||||
objectManager: any
|
objectManager: any
|
||||||
|
aiSelectboxToolManager: any
|
||||||
// 设置管理器
|
// 设置管理器
|
||||||
setManager(options) {
|
setManager(options) {
|
||||||
options.eventManager && (this.eventManager = options.eventManager)
|
options.eventManager && (this.eventManager = options.eventManager)
|
||||||
@@ -47,6 +48,7 @@ export class StateManager {
|
|||||||
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)
|
options.objectManager && (this.objectManager = options.objectManager)
|
||||||
|
options.aiSelectboxToolManager && (this.aiSelectboxToolManager = options.aiSelectboxToolManager)
|
||||||
}
|
}
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.mxHistory = ref(50)
|
this.mxHistory = ref(50)
|
||||||
@@ -71,6 +73,7 @@ export class StateManager {
|
|||||||
/** 记录状态 */
|
/** 记录状态 */
|
||||||
recordState() {
|
recordState() {
|
||||||
if (this.running.value) return
|
if (this.running.value) return
|
||||||
|
console.log("recordState")
|
||||||
this.running.value = true
|
this.running.value = true
|
||||||
if (this.historyIndex.value < this.historyList.value.length - 1) {
|
if (this.historyIndex.value < this.historyList.value.length - 1) {
|
||||||
this.historyList.value.splice(this.historyIndex.value + 1)
|
this.historyList.value.splice(this.historyIndex.value + 1)
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export class ToolManager {
|
|||||||
setTool(value: string) {
|
setTool(value: string) {
|
||||||
const tool = this.tools.find((t) => t.name === value)
|
const tool = this.tools.find((t) => t.name === value)
|
||||||
if (!tool) return console.warn(`工具${tool}不存在`)
|
if (!tool) return console.warn(`工具${tool}不存在`)
|
||||||
|
const oldTool = this.currentTool.value
|
||||||
this.currentTool.value = tool.name
|
this.currentTool.value = tool.name
|
||||||
this.canvasManager.canvas.defaultCursor = tool.cursor
|
this.canvasManager.canvas.defaultCursor = tool.cursor
|
||||||
this.setCanvasEvented(!!tool.selection)
|
this.setCanvasEvented(!!tool.selection)
|
||||||
@@ -110,6 +111,7 @@ export class ToolManager {
|
|||||||
|
|
||||||
if (tool.setup) tool.setup()
|
if (tool.setup) tool.setup()
|
||||||
|
|
||||||
|
this.stateManager?.aiSelectboxToolManager?.handleToolChange?.(oldTool, tool.name)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.canvasManager.renderAll()
|
this.canvasManager.renderAll()
|
||||||
});
|
});
|
||||||
@@ -142,13 +144,16 @@ export class ToolManager {
|
|||||||
const brushStore = this.brushManager?.brushStore
|
const brushStore = this.brushManager?.brushStore
|
||||||
if (brushStore) {
|
if (brushStore) {
|
||||||
// 同步基本属性
|
// 同步基本属性
|
||||||
this.brushManager.setBrushSize(brushStore.state.size);
|
// this.brushManager.setBrushSize(brushStore.state.size);
|
||||||
this.brushManager.setBrushColor(brushStore.state.color);
|
// this.brushManager.setBrushColor(brushStore.state.color);
|
||||||
this.brushManager.setBrushOpacity(brushStore.state.opacity);
|
// this.brushManager.setBrushOpacity(brushStore.state.opacity);
|
||||||
|
|
||||||
// 同步笔刷类型 - 修复方法名,使用正确的setBrushType方法
|
// 同步笔刷类型 - 修复方法名,使用正确的setBrushType方法
|
||||||
this.brushManager.setBrushType("pencil");
|
this.brushManager.setBrushType("pencil");
|
||||||
}
|
}
|
||||||
|
this.brushManager.setBrushSize(5);
|
||||||
|
this.brushManager.setBrushColor("#000");
|
||||||
|
this.brushManager.setBrushOpacity(1);
|
||||||
|
|
||||||
// 更新应用到画布
|
// 更新应用到画布
|
||||||
this.brushManager.updateBrush();
|
this.brushManager.updateBrush();
|
||||||
@@ -168,6 +173,7 @@ export class ToolManager {
|
|||||||
this.brushManager.createEraser();
|
this.brushManager.createEraser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.brushManager.setBrushSize(5);
|
||||||
this.stateManager.layerManager.setActiveObjectErasable()
|
this.stateManager.layerManager.setActiveObjectErasable()
|
||||||
// 启用笔刷指示器
|
// 启用笔刷指示器
|
||||||
this._enableBrushIndicator();
|
this._enableBrushIndicator();
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export class CanvasEventManager {
|
|||||||
}
|
}
|
||||||
this.shapeToolManager = new ShapeToolManager(managers)
|
this.shapeToolManager = new ShapeToolManager(managers)
|
||||||
this.aiSelectboxToolManager = new AISelectboxToolManager(managers)
|
this.aiSelectboxToolManager = new AISelectboxToolManager(managers)
|
||||||
|
this.stateManager.setManager({ aiSelectboxToolManager: this.aiSelectboxToolManager })
|
||||||
|
|
||||||
// 初始化所有事件
|
// 初始化所有事件
|
||||||
this.initEvents();
|
this.initEvents();
|
||||||
@@ -59,9 +60,8 @@ export class CanvasEventManager {
|
|||||||
// 共享事件
|
// 共享事件
|
||||||
this.setupSelectionEvents();
|
this.setupSelectionEvents();
|
||||||
this.setupObjectEvents();
|
this.setupObjectEvents();
|
||||||
// this.setupDoubleClickEvents();
|
this.setupDoubleClickEvents();
|
||||||
|
|
||||||
// this.setupHandlePathCreated();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupZoomEvents() {
|
setupZoomEvents() {
|
||||||
@@ -730,8 +730,10 @@ export class CanvasEventManager {
|
|||||||
});
|
});
|
||||||
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 target = e.target;
|
||||||
|
const id = target?.info?.id;
|
||||||
if (id) await this.layerManager.updateLayerThumbnailsById(id)
|
if (id) await this.layerManager.updateLayerThumbnailsById(id)
|
||||||
|
if (target.type === "group") await this.canvasManager.updateSubLayerClipPath()
|
||||||
this.stateManager.recordState();
|
this.stateManager.recordState();
|
||||||
});
|
});
|
||||||
this.canvas.on("object:removed", (e) => {
|
this.canvas.on("object:removed", (e) => {
|
||||||
@@ -752,113 +754,6 @@ export class CanvasEventManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setupLongPress(callback) {
|
|
||||||
this.canvas.on("mouse:down", (opt) => {
|
|
||||||
if (!opt.target) return;
|
|
||||||
|
|
||||||
this.longPressTimer = setTimeout(() => {
|
|
||||||
callback(opt);
|
|
||||||
}, this.longPressThreshold);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.canvas.on("mouse:up", () => {
|
|
||||||
clearTimeout(this.longPressTimer);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.canvas.on("mouse:move", () => {
|
|
||||||
clearTimeout(this.longPressTimer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置路径创建事件
|
|
||||||
setupHandlePathCreated() {
|
|
||||||
// 在 CanvasEventManager 的构造函数或初始化方法中
|
|
||||||
// this.canvas.on("path:created", this._handlePathCreated.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
_handlePathCreated(e) {
|
|
||||||
// // 获取新创建的路径对象
|
|
||||||
// const path = e.path;
|
|
||||||
// // 设置路径的ID和其他属性
|
|
||||||
// path.id = generateId(); // 生成唯一ID
|
|
||||||
// // 获取当前活动图层
|
|
||||||
// const activeLayer = this.layerManager.getActiveLayer();
|
|
||||||
// // 将路径对象绑定到当前活动图层
|
|
||||||
// if (activeLayer) {
|
|
||||||
// // 设置路径的图层ID
|
|
||||||
// path.layerId = activeLayer.id;
|
|
||||||
// // 更新图层对象列表
|
|
||||||
// if (!activeLayer.fabricObjects) activeLayer.fabricObjects = [];
|
|
||||||
// activeLayer.fabricObjects.push(path);
|
|
||||||
// // 更新图层缩略图
|
|
||||||
// if (this.thumbnailManager) {
|
|
||||||
// this.thumbnailManager.generateLayerThumbnail(activeLayer.id);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并图层中的对象为组以提高性能
|
|
||||||
* @param {Object} options 合并选项
|
|
||||||
* @param {fabric.Image} options.fabricImage 新的图像对象
|
|
||||||
* @param {Object} options.activeLayer 当前活动图层
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async mergeLayerObjectsForPerformance({ fabricImage, activeLayer, options }) {
|
|
||||||
// 确保有命令管理器
|
|
||||||
if (!this.layerManager || !this.layerManager.commandManager) {
|
|
||||||
console.warn("合并对象失败:没有命令管理器");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保有活动图层
|
|
||||||
if (!activeLayer) {
|
|
||||||
console.warn("合并对象失败:没有活动图层");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 验证是否需要合并
|
|
||||||
const hasExistingObjects =
|
|
||||||
Array.isArray(activeLayer.fabricObjects) &&
|
|
||||||
activeLayer.fabricObjects.length > 0;
|
|
||||||
const hasNewImage = !!fabricImage;
|
|
||||||
|
|
||||||
if (!hasExistingObjects && !hasNewImage) {
|
|
||||||
// console.log("没有对象需要合并");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果只有一个新图像且图层为空,直接添加到图层
|
|
||||||
if (hasNewImage && !hasExistingObjects) {
|
|
||||||
this.layerManager.addObjectToLayer(fabricImage, activeLayer.id, options);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行高保真合并操作
|
|
||||||
try {
|
|
||||||
// console.log(`开始合并图层 ${activeLayer.name} 中的对象为组...`);
|
|
||||||
|
|
||||||
const command = await this.layerManager.LayerObjectsToGroup(
|
|
||||||
activeLayer,
|
|
||||||
fabricImage
|
|
||||||
);
|
|
||||||
|
|
||||||
// 设置命令的撤销状态
|
|
||||||
if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销
|
|
||||||
|
|
||||||
this.layerManager?.commandManager?.execute?.(command, {
|
|
||||||
name: `合并图层 ${activeLayer.name} 中的对象为组`,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("合并图层对象时发生错误:", error);
|
|
||||||
|
|
||||||
// 降级处理:如果合并失败,至少保证新图像能添加到图层
|
|
||||||
if (fabricImage && this.layerManager) {
|
|
||||||
// console.log("执行降级处理:直接添加图像到图层");
|
|
||||||
this.layerManager.addObjectToLayer(fabricImage, activeLayer.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSelectedLayer(opt) {
|
updateSelectedLayer(opt) {
|
||||||
const selected = opt.selected[0];
|
const selected = opt.selected[0];
|
||||||
if (selected && opt.selected.length === 1) {
|
if (selected && opt.selected.length === 1) {
|
||||||
@@ -866,31 +761,6 @@ export class CanvasEventManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新图层缩略图
|
|
||||||
updateLayerThumbnail(layerId) {
|
|
||||||
if (!this.thumbnailManager || !layerId || !this.layers) return;
|
|
||||||
|
|
||||||
const layer = this.layers.value.find((l) => l.id === layerId);
|
|
||||||
if (layer) {
|
|
||||||
this.thumbnailManager.generateLayerThumbnail(layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新子元素组合缩略图
|
|
||||||
updateLayerChidrenThumbnail(layerId, fabricObject) {
|
|
||||||
if (!this.thumbnailManager || !fabricObject || !this.layers) return;
|
|
||||||
|
|
||||||
// 查找对应的图层(现在元素就是图层)
|
|
||||||
const layer = this.layers.value.find(
|
|
||||||
(l) => l.fabricObjects && l.fabricObjects?.[0]?.id === layerId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (layer) {
|
|
||||||
// 生成图层缩略图
|
|
||||||
this.thumbnailManager.generateLayerThumbnail(layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 精确检测设备类型,区分 PC、Mac、平板和移动设备
|
* 精确检测设备类型,区分 PC、Mac、平板和移动设备
|
||||||
|
|||||||
@@ -93,3 +93,157 @@ export function angleBetweenPointsDegrees(x1, y1, x2, y2) {
|
|||||||
|
|
||||||
return deg;
|
return deg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对象黑白通道画布
|
||||||
|
* @param {fabric.Object} object - 要处理的 fabric 对象
|
||||||
|
* @param {ImageData} revData - 相反的ImageData,白通道的相同位置是否为透明,revData为白色为透明,黑色为不透明
|
||||||
|
* @param {number} diff - 差值,默认 25
|
||||||
|
* @param {Object} rgba - 自定义 rgba 值,默认 { r: 255, g: 255, b: 255, a: 255 }
|
||||||
|
* @param {boolean} isMerge - 是否合并,true=合并revData,false=反转revData
|
||||||
|
* @returns {HTMLCanvasElement|null} 包含黑白通道的画布,或 null 如果失败
|
||||||
|
*/
|
||||||
|
export function getObjectAlphaToCanvas(object, revData, diff = 30, rgba = { r: 255, g: 255, b: 255, a: 255 }, isMerge = false) {
|
||||||
|
const image = object.getElement();
|
||||||
|
if (image.nodeName !== "IMG" && image.nodeName !== "CANVAS") {
|
||||||
|
console.warn("对象不是图片");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { width, height } = image;
|
||||||
|
if (!width || !height) {
|
||||||
|
console.warn("对象没有元素");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
||||||
|
ctx.drawImage(image, 0, 0, width, height);
|
||||||
|
const data = ctx.getImageData(0, 0, width, height);
|
||||||
|
for (let i = 0; i < data.data.length; i += 4) {
|
||||||
|
const r = data.data[i + 0];
|
||||||
|
const g = data.data[i + 1];
|
||||||
|
const b = data.data[i + 2];
|
||||||
|
const a = data.data[i + 3];
|
||||||
|
const revR = revData?.data[i + 0] || 0;
|
||||||
|
const revG = revData?.data[i + 1] || 0;
|
||||||
|
const revB = revData?.data[i + 2] || 0;
|
||||||
|
const revA = revData?.data[i + 3] || 0;
|
||||||
|
let isHave = false;
|
||||||
|
if (r || g || b || a) {
|
||||||
|
if (revR > diff || revG > diff || revB > diff || revA > diff) {
|
||||||
|
isHave = false;
|
||||||
|
} else {
|
||||||
|
isHave = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isMerge && (revR || revG || revB || revA)) isHave = true;
|
||||||
|
if (isHave) {
|
||||||
|
data.data[i + 0] = rgba.r;
|
||||||
|
data.data[i + 1] = rgba.g;
|
||||||
|
data.data[i + 2] = rgba.b;
|
||||||
|
data.data[i + 3] = rgba.a;
|
||||||
|
} else {
|
||||||
|
data.data[i + 0] = 0;
|
||||||
|
data.data[i + 1] = 0;
|
||||||
|
data.data[i + 2] = 0;
|
||||||
|
data.data[i + 3] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
ctx.putImageData(data, 0, 0);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片边界跟踪算法(透明底)
|
||||||
|
* @param {HTMLCanvasElement} canvas - canvas元素
|
||||||
|
* @param {Number} scale - 缩放比例
|
||||||
|
* @returns {Array} 边界点数组 [{x, y}, ...]
|
||||||
|
*/
|
||||||
|
export function traceImageContour(canvas) {
|
||||||
|
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
||||||
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
const data = imageData.data;
|
||||||
|
const width = canvas.width;
|
||||||
|
const height = canvas.height;
|
||||||
|
|
||||||
|
// 查找起始点(第一个不透明像素)
|
||||||
|
let startX = -1;
|
||||||
|
let startY = -1;
|
||||||
|
|
||||||
|
outer: for (let y = 0; y < height; y++) {
|
||||||
|
for (let x = 0; x < width; x++) {
|
||||||
|
const index = (y * width + x) * 4;
|
||||||
|
if (data[index + 3] > 0) {
|
||||||
|
startX = x;
|
||||||
|
startY = y;
|
||||||
|
break outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startX === -1) return []; // 没有不透明像素
|
||||||
|
|
||||||
|
// Moore-Neighbor边界跟踪算法
|
||||||
|
const contour = [];
|
||||||
|
const visited = new Set();
|
||||||
|
const directions = [
|
||||||
|
[-1, 0],
|
||||||
|
[-1, -1],
|
||||||
|
[0, -1],
|
||||||
|
[1, -1],
|
||||||
|
[1, 0],
|
||||||
|
[1, 1],
|
||||||
|
[0, 1],
|
||||||
|
[-1, 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
let currentX = startX;
|
||||||
|
let currentY = startY;
|
||||||
|
let backtrackDir = 4; // 起始方向:右
|
||||||
|
|
||||||
|
do {
|
||||||
|
const pointKey = `${currentX},${currentY}`;
|
||||||
|
if (!visited.has(pointKey)) {
|
||||||
|
contour.push({ x: currentX, y: currentY });
|
||||||
|
visited.add(pointKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从右方向开始顺时针查找
|
||||||
|
let found = false;
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
const dir = (backtrackDir + i) % 8;
|
||||||
|
const dx = directions[dir][0];
|
||||||
|
const dy = directions[dir][1];
|
||||||
|
const checkX = currentX + dx;
|
||||||
|
const checkY = currentY + dy;
|
||||||
|
|
||||||
|
if (
|
||||||
|
checkX >= 0 &&
|
||||||
|
checkX < width &&
|
||||||
|
checkY >= 0 &&
|
||||||
|
checkY < height
|
||||||
|
) {
|
||||||
|
const index = (checkY * width + checkX) * 4;
|
||||||
|
if (data[index + 3] > 0) {
|
||||||
|
currentX = checkX;
|
||||||
|
currentY = checkY;
|
||||||
|
backtrackDir = (dir + 5) % 8; // 下一个开始查找的方向
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) break;
|
||||||
|
} while (
|
||||||
|
!(currentX === startX && currentY === startY) &&
|
||||||
|
visited.size < width * height
|
||||||
|
);
|
||||||
|
|
||||||
|
return contour;
|
||||||
|
}
|
||||||
@@ -29,6 +29,12 @@ export async function exportObjectsToImage(objects = [], isDetails = false) {
|
|||||||
left: obj.left - boundingBox.left,
|
left: obj.left - boundingBox.left,
|
||||||
top: obj.top - boundingBox.top,
|
top: obj.top - boundingBox.top,
|
||||||
})
|
})
|
||||||
|
if (obj.clipPath && obj.clipPath.absolutePositioned) {
|
||||||
|
obj.clipPath.set({
|
||||||
|
left: obj.clipPath.left - boundingBox.left,
|
||||||
|
top: obj.clipPath.top - boundingBox.top,
|
||||||
|
})
|
||||||
|
}
|
||||||
staticCanvas.add(obj)
|
staticCanvas.add(obj)
|
||||||
})
|
})
|
||||||
// 导出图片
|
// 导出图片
|
||||||
|
|||||||
@@ -68,3 +68,11 @@ export const BlendMode = {
|
|||||||
DESTINATION_IN: "destination-in", // 目标内
|
DESTINATION_IN: "destination-in", // 目标内
|
||||||
DESTINATION_OUT: "destination-out", // 目标外
|
DESTINATION_OUT: "destination-out", // 目标外
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 智能框选工具类型枚举 */
|
||||||
|
export const AI_SELECTBOX_TYPE = {
|
||||||
|
ADD: "add", // 添加模式
|
||||||
|
REMOVE: "remove", // 删除模式
|
||||||
|
DRAW: "draw", // 绘画模式
|
||||||
|
ERASER: "eraser", // 橡皮擦模式
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,15 +11,12 @@
|
|||||||
<div class="item" @mousedown="(e) => stateManager.setActiveNodeID(node.id)">
|
<div class="item" @mousedown="(e) => stateManager.setActiveNodeID(node.id)">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="add" @mousedown.stop v-if="isAdd" @click="onAdd">
|
|
||||||
<svg-icon name="add" size="14" size-unit="px" />
|
|
||||||
</div>
|
|
||||||
<div class="mask" v-show="mask"></div>
|
<div class="mask" v-show="mask"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Handle, Position } from '@vue-flow/core'
|
import { Handle, Position } from '@vue-flow/core'
|
||||||
import { NODE_DATATYPE, NODE_DATATIER, NODE_TYPE } from '../tools/index.d'
|
import { NODE_DATATYPE, NODE_TYPE } from '../tools/index.d'
|
||||||
import { computed, ref, inject } from 'vue'
|
import { computed, ref, inject } from 'vue'
|
||||||
const handles = ref({
|
const handles = ref({
|
||||||
[NODE_TYPE.INPUT]: [{ id: 'Right', type: 'source', position: Position.Right }],
|
[NODE_TYPE.INPUT]: [{ id: 'Right', type: 'source', position: Position.Right }],
|
||||||
@@ -48,39 +45,6 @@
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const stateManager = inject('stateManager') as any
|
|
||||||
const nodes = computed(() => props.stateManager.nodes.value)
|
|
||||||
const isSubord = computed(() => nodes.value.some((v) => v.data.superiorID === props.node.id))
|
|
||||||
const tier = computed(() => Number(props.node?.data?.tier || 0))
|
|
||||||
//只有3d模型才有三级菜单,目前三级菜单内容少直接禁用按钮
|
|
||||||
const isAdd3d = computed(() => (tier.value === 2 && props.node?.data?.superiorNodeType === NODE_DATATYPE.TO_3D_MODEL) || props.node?.data?.superiorNodeType !== NODE_DATATYPE.TO_3D_MODEL)
|
|
||||||
const isReturned = computed(() => {
|
|
||||||
return (
|
|
||||||
props.node.data.type == NODE_DATATYPE.RESULT_IMAGE &&
|
|
||||||
props.node.data.data.imageProcessTasks[0].status == 'RETURNED'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
const isAdd = computed(
|
|
||||||
() =>
|
|
||||||
!isSubord.value &&
|
|
||||||
NODE_DATATYPE.RESULT_IMAGE === props.node.data.type &&
|
|
||||||
!(tier.value === NODE_DATATIER.TO_3VIEW) &&
|
|
||||||
isReturned.value &&
|
|
||||||
isAdd3d.value
|
|
||||||
)
|
|
||||||
const onAdd = () => {
|
|
||||||
const tier_ = tier.value + 1
|
|
||||||
// 从data中获取originalImage
|
|
||||||
let nodeData = props.node?.data?.data.imageProcessTasks.filter(
|
|
||||||
(v) => v.taskId === props.node?.data?.data.selectTaskId
|
|
||||||
)
|
|
||||||
const originalImage = nodeData[0]?.url
|
|
||||||
if (!originalImage) console.log('originalImage 找不到原始图片')
|
|
||||||
props.stateManager.nodeManager.createCardsSelect({
|
|
||||||
data: { tier: tier_, superiorID: props.node.id, originalImage }
|
|
||||||
})
|
|
||||||
stateManager.setActiveNodeID('')
|
|
||||||
}
|
|
||||||
const posCenter = computed(() => {
|
const posCenter = computed(() => {
|
||||||
const arr = [NODE_DATATYPE.RESULT_IMAGE]
|
const arr = [NODE_DATATYPE.RESULT_IMAGE]
|
||||||
return arr.includes(props.node?.data?.type)
|
return arr.includes(props.node?.data?.type)
|
||||||
@@ -106,25 +70,6 @@
|
|||||||
> .item {
|
> .item {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
> .add {
|
|
||||||
position: absolute;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border: 2px solid #fff;
|
|
||||||
top: var(--top);
|
|
||||||
right: -16px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
background-color: #ed8936;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 25px;
|
|
||||||
box-shadow: 0 8px 20px 0 #71809633;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 20;
|
|
||||||
}
|
|
||||||
&.center {
|
&.center {
|
||||||
--top: 50%;
|
--top: 50%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,25 +69,26 @@
|
|||||||
const onClickItem = (v) => {
|
const onClickItem = (v) => {
|
||||||
const id = props.node.id
|
const id = props.node.id
|
||||||
if (!id) return
|
if (!id) return
|
||||||
|
const superiorID = props.node.data.superiorID
|
||||||
stateManager.deleteNode(id)
|
stateManager.deleteNode(id)
|
||||||
console.log(props.node)
|
|
||||||
if(v.secondaryMenu){
|
if(v.secondaryMenu){
|
||||||
nodeManager.createCardsSelect({
|
nodeManager.createCardsSelect({
|
||||||
data: {
|
data: {
|
||||||
tier: props.node.data?.tier,
|
tier: props.node.data?.tier,
|
||||||
superiorID: props.node.data?.superiorID,
|
superiorID,
|
||||||
originalImage: props.node.data?.originalImage,
|
isActive: props.node.data?.isActive,
|
||||||
secondaryMenu: v.secondaryMenu
|
secondaryMenu: v.secondaryMenu,
|
||||||
|
createIndexPosition: props.node.data.createIndexPosition,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}else{
|
}else{
|
||||||
const superiorID = props.node.data.superiorID
|
|
||||||
nodeManager.createCardNode({
|
nodeManager.createCardNode({
|
||||||
data: {
|
data: {
|
||||||
tier: v.tier,
|
tier: v.tier,
|
||||||
type: v.type,
|
type: v.type,
|
||||||
superiorID,
|
superiorID,
|
||||||
originalImage: props.node.data?.originalImage,
|
isActive: props.node.data?.isActive,
|
||||||
|
createIndexPosition: props.node.data.createIndexPosition,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,24 +9,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, inject, useAttrs } from 'vue'
|
import { reactive, inject, useAttrs, computed } from 'vue'
|
||||||
import myEvent from '@/utils/myEvent'
|
import myEvent from '@/utils/myEvent'
|
||||||
import { getCurrentTime } from '../../../../tools/tools.ts'
|
import { getCurrentTime } from '../../../../tools/tools.ts'
|
||||||
import { NODE_DATATIER } from '../../../tools/index.d'
|
import { NODE_DATATIER } from '../../../tools/index.d'
|
||||||
const attrs = useAttrs()
|
const attrs = useAttrs()
|
||||||
const data = reactive({
|
|
||||||
url: attrs.node?.data?.originalImage,
|
|
||||||
})
|
|
||||||
const stateManager = inject('stateManager') as any
|
const stateManager = inject('stateManager') as any
|
||||||
const nodeManager = inject('nodeManager') as any
|
const nodeManager = inject('nodeManager') as any
|
||||||
const eventManager = inject('eventManager') as any
|
const eventManager = inject('eventManager') as any
|
||||||
|
const data = reactive({
|
||||||
|
url: computed(()=>stateManager.getSuperiorNodeImage(attrs.node?.data?.superiorID)),
|
||||||
|
})
|
||||||
const getApiData = ()=>{
|
const getApiData = ()=>{
|
||||||
return {
|
return {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const opCanvas = ()=>{
|
const opCanvas = ()=>{
|
||||||
|
const superiorNodeUrl = stateManager.getSuperiorNodeImage(attrs?.node?.data?.superiorID || null)
|
||||||
|
if (!superiorNodeUrl) console.log('superiorNodeUrl 找不到原始图片')
|
||||||
const data = {
|
const data = {
|
||||||
url:attrs?.node?.data?.originalImage,
|
url:superiorNodeUrl,
|
||||||
canvasId: attrs?.node?.data?.canvasId || null,
|
canvasId: attrs?.node?.data?.canvasId || null,
|
||||||
sketchId: stateManager.sketchId.value,
|
sketchId: stateManager.sketchId.value,
|
||||||
onWorkbench:(options)=>{
|
onWorkbench:(options)=>{
|
||||||
|
|||||||
@@ -42,12 +42,15 @@
|
|||||||
import ColorPalette from './color-palette.vue'
|
import ColorPalette from './color-palette.vue'
|
||||||
import To3View from './to-3view.vue'
|
import To3View from './to-3view.vue'
|
||||||
import To3DModel from './to-3d-model.vue'
|
import To3DModel from './to-3d-model.vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
import { toRealStyleApi, toColorPaletteApi, toSceneCompositionApi, sketchAddPrintApi, sketchToThreeApi, threeToThreeViewsApi } from '@/api/flow-canvas'
|
import { toRealStyleApi, toColorPaletteApi, toSceneCompositionApi, sketchAddPrintApi, sketchToThreeApi, threeToThreeViewsApi } from '@/api/flow-canvas'
|
||||||
|
|
||||||
// import ToVideo from './to-video.vue'
|
// import ToVideo from './to-video.vue'
|
||||||
// import AddPrint from './add-print.vue'
|
// import AddPrint from './add-print.vue'
|
||||||
// import ToCAD from './to-cad.vue'
|
// import ToCAD from './to-cad.vue'
|
||||||
|
const { t } = useI18n()
|
||||||
const attrs = useAttrs()
|
const attrs = useAttrs()
|
||||||
const componentRef = ref(null)
|
const componentRef = ref(null)
|
||||||
const components = [
|
const components = [
|
||||||
@@ -143,14 +146,13 @@
|
|||||||
|
|
||||||
const onGenerateClick = async () => {
|
const onGenerateClick = async () => {
|
||||||
const data = componentRef.value?.getApiData?.() || {}
|
const data = componentRef.value?.getApiData?.() || {}
|
||||||
const subordNode = stateManager.getSubordNodeById(attrs.node.id)
|
|
||||||
const subordNodes = stateManager.getSubordNodes(attrs.node.id)
|
const subordNodes = stateManager.getSubordNodes(attrs.node.id)
|
||||||
|
const superiorNodeUrl = stateManager.getSuperiorNodeImage(attrs.node.data.superiorID)
|
||||||
|
if(!superiorNodeUrl)return console.log('superiorNodeUrl 找不到原始图片')
|
||||||
emit('update-data', componentRef.value?.data)
|
emit('update-data', componentRef.value?.data)
|
||||||
if(!attrs.node?.data?.originalImage)console.log('originalImage 找不到原始图片')
|
|
||||||
|
|
||||||
const apiData = {
|
const apiData = {
|
||||||
sketchId: props.sketchId,
|
sketchId: props.sketchId,
|
||||||
imageUrl: attrs.node?.data?.originalImage,
|
imageUrl: superiorNodeUrl,
|
||||||
...data,
|
...data,
|
||||||
}
|
}
|
||||||
const taskList = await currentComponent.value.api(apiData).then((rv)=>{
|
const taskList = await currentComponent.value.api(apiData).then((rv)=>{
|
||||||
@@ -178,8 +180,9 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
//删除功能卡片
|
//删除功能卡片
|
||||||
const onDeleteClick = ()=>{
|
const onDeleteClick = async ()=>{
|
||||||
stateManager.deleteNode(attrs.node.id,{isElMessageBox:true})
|
console.log(stateManager.nodes)
|
||||||
|
stateManager.getSubordinateAllNodes(attrs.node.id,{ isElMessageBox: true })
|
||||||
}
|
}
|
||||||
const setDate = () => {
|
const setDate = () => {
|
||||||
for (const key in props.data) {
|
for (const key in props.data) {
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, onMounted, useAttrs } from 'vue'
|
import { reactive, inject, useAttrs, computed } from 'vue'
|
||||||
import uploadFile from '../../tools/upload-file.vue'
|
import uploadFile from '../../tools/upload-file.vue'
|
||||||
const attrs = useAttrs()
|
const attrs = useAttrs()
|
||||||
|
const stateManager = inject('stateManager') as any
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
url: attrs.node?.data?.originalImage,
|
url: computed(()=>stateManager.getSuperiorNodeImage(attrs.node?.data?.superiorID)),
|
||||||
})
|
})
|
||||||
const getApiData = ()=>{
|
const getApiData = ()=>{
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
const attrs = useAttrs()
|
const attrs = useAttrs()
|
||||||
const stateManager = inject('stateManager') as any
|
const stateManager = inject('stateManager') as any
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
url: attrs.node.data.originalImage,
|
url: computed(()=>stateManager.getSuperiorNodeImage(attrs.node?.data?.superiorID)),
|
||||||
})
|
})
|
||||||
const getApiData = ()=>{
|
const getApiData = ()=>{
|
||||||
let glbUrl = null
|
let glbUrl = null
|
||||||
const superiorNode = stateManager.nodes.value.filter((item:any)=>item.id === attrs.node?.data?.superiorID)[0]
|
const superiorNode = stateManager.nodes.value.filter((item:any)=>item.id === attrs.node?.data?.superiorID)[0]
|
||||||
|
|||||||
@@ -53,6 +53,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="add" @mousedown.stop v-if="isAdd" @click="onAdd">
|
||||||
|
<svg-icon name="add" size="14" size-unit="px" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -61,7 +64,7 @@
|
|||||||
import { downloadImage } from '../../../tools/tools'
|
import { downloadImage } from '../../../tools/tools'
|
||||||
import { reactive, ref, onBeforeUnmount, useAttrs, inject, watch, computed, onMounted } from 'vue'
|
import { reactive, ref, onBeforeUnmount, useAttrs, inject, watch, computed, onMounted } from 'vue'
|
||||||
import HighlightAdmin from '@/components/highlightAdmin.vue'
|
import HighlightAdmin from '@/components/highlightAdmin.vue'
|
||||||
import { NODE_DATATYPE } from '../../tools/index.d'
|
import { NODE_DATATIER, NODE_DATATYPE } from '../../tools/index.d'
|
||||||
const openImagePreview = inject('openImagePreview') as (url: string) => void
|
const openImagePreview = inject('openImagePreview') as (url: string) => void
|
||||||
const openThreeModelPreview = inject('openThreeModelPreview') as (url: string) => void
|
const openThreeModelPreview = inject('openThreeModelPreview') as (url: string) => void
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -229,6 +232,38 @@
|
|||||||
eventManager.removeEvents()
|
eventManager.removeEvents()
|
||||||
myEvent.emit('openDepthCanvas', data)
|
myEvent.emit('openDepthCanvas', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSubord = computed(() => props.node.id == stateManager.activeNodeID.value)
|
||||||
|
const tier = computed(() => Number(props.node?.data?.tier || 0))
|
||||||
|
//只有3d模型才有三级菜单,目前三级菜单内容少直接禁用按钮
|
||||||
|
const isAdd3d = computed(() => (tier.value === 2 && props.node?.data?.superiorNodeType === NODE_DATATYPE.TO_3D_MODEL) || props.node?.data?.superiorNodeType !== NODE_DATATYPE.TO_3D_MODEL)
|
||||||
|
const isReturned = computed(() => {
|
||||||
|
return (
|
||||||
|
props.node.data.type == NODE_DATATYPE.RESULT_IMAGE &&
|
||||||
|
props.node.data.data.imageProcessTasks[0].status == 'RETURNED'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const isAdd = computed(
|
||||||
|
() =>
|
||||||
|
NODE_DATATYPE.RESULT_IMAGE === props.node.data.type &&
|
||||||
|
!(tier.value === NODE_DATATIER.TO_3VIEW) &&
|
||||||
|
isReturned.value &&
|
||||||
|
isAdd3d.value &&
|
||||||
|
isSubord.value
|
||||||
|
)
|
||||||
|
const onAdd = () => {
|
||||||
|
const tier_ = tier.value + 1
|
||||||
|
const subordNodes = stateManager.getSubordNodes(props.node.id)
|
||||||
|
stateManager.nodeManager.createCardsSelect({
|
||||||
|
data: {
|
||||||
|
tier: tier_,
|
||||||
|
superiorID: props.node.id,
|
||||||
|
createIndexPosition: subordNodes.length + 1,
|
||||||
|
isActive: subordNodes.length == 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stateManager.setActiveNodeID('')
|
||||||
|
}
|
||||||
document.addEventListener('mousedown', hideMenu)
|
document.addEventListener('mousedown', hideMenu)
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
document.removeEventListener('mousedown', hideMenu)
|
document.removeEventListener('mousedown', hideMenu)
|
||||||
@@ -250,6 +285,25 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.add {
|
||||||
|
position: absolute;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
top: var(--top);
|
||||||
|
right: -16px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background-color: #ed8936;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 25px;
|
||||||
|
box-shadow: 0 8px 20px 0 #71809633;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
.result-image {
|
.result-image {
|
||||||
width: 244px;
|
width: 244px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
|||||||
@@ -170,7 +170,7 @@
|
|||||||
/** 点击节点 */
|
/** 点击节点 */
|
||||||
const clickNode = (event) => {
|
const clickNode = (event) => {
|
||||||
let node = event.node
|
let node = event.node
|
||||||
stateManager.showNodeConnections(node.id)
|
// stateManager.showNodeConnections(node.id)
|
||||||
}
|
}
|
||||||
/** 删除节点 */
|
/** 删除节点 */
|
||||||
const deleteNode = (id) => {
|
const deleteNode = (id) => {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export class EventManager {
|
|||||||
handleDelete(event: any, activeNodeID: string) {
|
handleDelete(event: any, activeNodeID: string) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (!activeNodeID) return console.warn('没有选中节点')
|
if (!activeNodeID) return console.warn('没有选中节点')
|
||||||
this.stateManager.deleteNode(activeNodeID, { isElMessageBox: true })
|
this.stateManager.getSubordinateAllNodes(activeNodeID, { isElMessageBox: true })
|
||||||
}
|
}
|
||||||
/** 处理键盘事件 */
|
/** 处理键盘事件 */
|
||||||
_handleKeyDown: any
|
_handleKeyDown: any
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ interface NodeData {
|
|||||||
superiorNodeType?: string// 上级节点类型
|
superiorNodeType?: string// 上级节点类型
|
||||||
disableDelete?: boolean// 是否禁用删除
|
disableDelete?: boolean// 是否禁用删除
|
||||||
disableCopy?: boolean// 是否禁用复制
|
disableCopy?: boolean// 是否禁用复制
|
||||||
originalImage?: string// 要进行生成的图片
|
|
||||||
createIndexPosition?: number// 创建索引位置
|
createIndexPosition?: number// 创建索引位置
|
||||||
isActive?: boolean// 是否激活
|
isActive?: boolean// 是否激活
|
||||||
}
|
}
|
||||||
@@ -35,7 +34,7 @@ export class NodeManager {
|
|||||||
|
|
||||||
/** 删除节点 */
|
/** 删除节点 */
|
||||||
deleteNode(id: string) {
|
deleteNode(id: string) {
|
||||||
this.stateManager.deleteNode(id)
|
this.stateManager.getSubordinateAllNodes(id, { isElMessageBox: true })
|
||||||
}
|
}
|
||||||
/** 添加节点 */
|
/** 添加节点 */
|
||||||
addNode(node: any) {
|
addNode(node: any) {
|
||||||
@@ -45,27 +44,35 @@ export class NodeManager {
|
|||||||
/** 创建节点 */
|
/** 创建节点 */
|
||||||
createNode(options: NodeOptions) {
|
createNode(options: NodeOptions) {
|
||||||
const superiorID = options?.data?.superiorID
|
const superiorID = options?.data?.superiorID
|
||||||
const snode = superiorID ? this.stateManager.flowManager.getNodeById(superiorID) : this.stateManager.flowManager.getLastNode();
|
|
||||||
//获取上级节点所生成的最后一个node,设置位置为最后一个节点的xy 加上 节点间距
|
//获取上级节点所生成的最后一个node,设置位置为最后一个节点的xy 加上 节点间距
|
||||||
const superiorGenerateNodes = this.stateManager.getSubordNodes(superiorID)
|
const superiorGenerateNodes = this.stateManager.getSubordNodes(superiorID)
|
||||||
|
const currentNode = superiorGenerateNodes.find((node) => {
|
||||||
|
return node.data.createIndexPosition === options?.data?.createIndexPosition
|
||||||
|
})
|
||||||
const endGenerateNode = superiorGenerateNodes.reduce((max, current) => {
|
const endGenerateNode = superiorGenerateNodes.reduce((max, current) => {
|
||||||
return current.data.createIndexPosition > max.data.createIndexPosition ? current : max
|
return current.data.createIndexPosition > max.data.createIndexPosition ? current : max
|
||||||
}, superiorGenerateNodes[0])
|
}, superiorGenerateNodes[0])
|
||||||
|
const snode = superiorID ? this.stateManager.flowManager.getNodeById(superiorID) : this.stateManager.flowManager.getLastNode();
|
||||||
|
console.log(snode)
|
||||||
const id = options.id || createId()
|
const id = options.id || createId()
|
||||||
const positionX = options.positionX || 0
|
const positionX = options.positionX || 0
|
||||||
const positionY = options.positionY || 0
|
const positionY = options.positionY || 0
|
||||||
const position = options.position ||
|
const position = options.position ||
|
||||||
(
|
(
|
||||||
endGenerateNode?
|
currentNode ?
|
||||||
|
currentNode.position :
|
||||||
|
endGenerateNode ?
|
||||||
{
|
{
|
||||||
x: endGenerateNode.position.x + positionX,
|
x: endGenerateNode.position.x + positionX,
|
||||||
y: endGenerateNode.position.y + positionY + this.ranksep + 200
|
y: endGenerateNode.position.y + positionY + this.ranksep + 200
|
||||||
} :
|
} : snode ?
|
||||||
!snode ?
|
|
||||||
{ x: positionX, y: positionY } :
|
|
||||||
{
|
{
|
||||||
x: snode.position.x + snode.dimensions.width + this.nodesep + positionX,
|
x: snode.position.x + snode.dimensions.width + this.nodesep + positionX,
|
||||||
y: snode.position.y + positionY
|
y: snode.position.y + positionY
|
||||||
|
} :
|
||||||
|
{
|
||||||
|
x: positionX,
|
||||||
|
y: positionY
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const data = options?.data || {}
|
const data = options?.data || {}
|
||||||
@@ -103,6 +110,7 @@ export class NodeManager {
|
|||||||
data: {
|
data: {
|
||||||
tier: NODE_DATATIER.CARDS_SELECT,
|
tier: NODE_DATATIER.CARDS_SELECT,
|
||||||
type: NODE_DATATYPE.CARDS_SELECT,
|
type: NODE_DATATYPE.CARDS_SELECT,
|
||||||
|
createIndexPosition: options?.data?.createIndexPosition || 1,
|
||||||
...(options?.data || {}),
|
...(options?.data || {}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -113,7 +121,7 @@ export class NodeManager {
|
|||||||
const options_ = {
|
const options_ = {
|
||||||
...(options ? options : {}),
|
...(options ? options : {}),
|
||||||
component: NODE_COMPONENT.CARD,
|
component: NODE_COMPONENT.CARD,
|
||||||
data: {
|
data: {
|
||||||
...(options?.data || {}),
|
...(options?.data || {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export class StateManager {
|
|||||||
source: source,
|
source: source,
|
||||||
target: target,
|
target: target,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
visible: (node.data.type !== NODE_DATATYPE.RESULT_IMAGE || node.data.isActive),
|
visible: (node.data.isActive),
|
||||||
type: 'default'
|
type: 'default'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ export class StateManager {
|
|||||||
|
|
||||||
}
|
}
|
||||||
/** 设置激活节点 */
|
/** 设置激活节点 */
|
||||||
setActiveNodeID(id: string) { this.activeNodeID.value = id }
|
setActiveNodeID(id: string) { this.activeNodeID.value = id;this.showNodeConnections(id) }
|
||||||
/** 添加节点 */
|
/** 添加节点 */
|
||||||
addNode(node: NodesItem) {
|
addNode(node: NodesItem) {
|
||||||
this.nodes.value.push(node);
|
this.nodes.value.push(node);
|
||||||
@@ -119,37 +119,69 @@ export class StateManager {
|
|||||||
this.exportFlow()
|
this.exportFlow()
|
||||||
}
|
}
|
||||||
/** 删除节点 */
|
/** 删除节点 */
|
||||||
async deleteNode(id: string, { isElMessageBox } = { isElMessageBox: false }) {
|
async deleteNode(id: string) {
|
||||||
const node = this.getNodeById(id)
|
|
||||||
if (!node) return console.warn(`没有找到指定id:${id}`)
|
|
||||||
if (node.data.disableDelete) return console.warn('该节点禁用删除')
|
|
||||||
let deletePromise: any = true
|
|
||||||
if (isElMessageBox) {
|
|
||||||
deletePromise = await new Promise<void>((resolve, reject) => {
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
t('flowCanvas.deleteCardConfirm'),
|
|
||||||
'',
|
|
||||||
{
|
|
||||||
confirmButtonText: t('flowCanvas.confirm'),
|
|
||||||
cancelButtonText: t('flowCanvas.cancel'),
|
|
||||||
}
|
|
||||||
).then(() => {
|
|
||||||
resolve(true)
|
|
||||||
}).catch(() => {
|
|
||||||
resolve(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!deletePromise) return console.log('删除操作被取消')
|
|
||||||
this.nodes.value = this.nodes.value.filter((node: NodesItem) => node.id !== id)
|
this.nodes.value = this.nodes.value.filter((node: NodesItem) => node.id !== id)
|
||||||
this.recordState()
|
|
||||||
this.exportFlow()
|
|
||||||
}
|
}
|
||||||
/** 获取节点 */
|
/** 获取节点 */
|
||||||
getNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.id === id) }
|
getNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.id === id) }
|
||||||
/** 获取下级节点 */
|
/** 获取下级节点 */
|
||||||
getSubordNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.data.superiorID === id) }
|
getSubordNodeById(id: string) { return this.nodes.value.find((node: NodesItem) => node.data.superiorID === id) }
|
||||||
getLastNode() { console.log(this.nodes.value); return this.nodes.value[this.nodes.value.length - 1] }
|
getLastNode() { console.log(this.nodes.value); return this.nodes.value[this.nodes.value.length - 1] }
|
||||||
|
|
||||||
|
/** 获取上级生成节点的图片 */
|
||||||
|
getSuperiorNodeImage(superiorID: string) {
|
||||||
|
const superiorNode = this.getNodeById(superiorID)
|
||||||
|
if(!superiorNode){
|
||||||
|
ElMessage.error(t('flowCanvas.cannotFindSuperiorImage'))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const superiorNodeUrl = superiorNode.data.data.imageProcessTasks.filter((item)=>{
|
||||||
|
return item.taskId == superiorNode.data.data.selectTaskId
|
||||||
|
})[0]?.url
|
||||||
|
return superiorNodeUrl
|
||||||
|
}
|
||||||
|
/** 获取下级所有子级节点 */
|
||||||
|
async getSubordinateAllNodes(id: string,{ isElMessageBox } = { isElMessageBox: false }) {
|
||||||
|
const node = this.getNodeById(id)
|
||||||
|
if (!node) return console.warn(`没有找到指定id:${id}`)
|
||||||
|
if (node.data.disableDelete) return ElMessage.error(t('flowCanvas.initialNodeProhibited'))
|
||||||
|
|
||||||
|
const result = [node]
|
||||||
|
const findChildren = (parentId: string) => {
|
||||||
|
const children = this.nodes.value.filter(item => item.data.superiorID === parentId)
|
||||||
|
children.forEach(child => {
|
||||||
|
if(child.data.type !== NODE_DATATYPE.RESULT_IMAGE){
|
||||||
|
result.push(child)
|
||||||
|
}
|
||||||
|
findChildren(child.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let deletePromise: any = true
|
||||||
|
if (isElMessageBox && result.length > 1) {
|
||||||
|
deletePromise = await new Promise<void>((resolve, reject) => {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
t('flowCanvas.deleteSubordinateCard'),
|
||||||
|
'',
|
||||||
|
{
|
||||||
|
confirmButtonText: t('flowCanvas.confirm'),
|
||||||
|
cancelButtonText: t('flowCanvas.cancel'),
|
||||||
|
}
|
||||||
|
).then(() => {resolve(true)
|
||||||
|
}).catch(() => {
|
||||||
|
resolve(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(!deletePromise) return console.log('删除操作被取消')
|
||||||
|
|
||||||
|
this.deleteNode(id)
|
||||||
|
result.forEach(item => {
|
||||||
|
this.deleteNode(item.id)
|
||||||
|
})
|
||||||
|
this.recordState()
|
||||||
|
this.exportFlow()
|
||||||
|
}
|
||||||
/** 设置工具 */
|
/** 设置工具 */
|
||||||
setTool(tool: string) { this.tool.value = tool }
|
setTool(tool: string) { this.tool.value = tool }
|
||||||
/** 设置光标 */
|
/** 设置光标 */
|
||||||
@@ -222,9 +254,9 @@ export class StateManager {
|
|||||||
|
|
||||||
/** 显示指定子节点和父节点连接线,隐藏父节点和其他子节点链接线, */
|
/** 显示指定子节点和父节点连接线,隐藏父节点和其他子节点链接线, */
|
||||||
showNodeConnections(id: string) {
|
showNodeConnections(id: string) {
|
||||||
|
if(!id) return
|
||||||
const node = this.getNodeById(id)
|
const node = this.getNodeById(id)
|
||||||
if(node.data.component != NODE_DATATYPE.RESULT_IMAGE && node.data.superiorID) return
|
if(!node?.data?.superiorID) return
|
||||||
let edges_ = JSON.parse(JSON.stringify(this.edges.value))
|
|
||||||
this.nodes.value.forEach((nodeItem) => {
|
this.nodes.value.forEach((nodeItem) => {
|
||||||
if(node.data.superiorID === nodeItem.data.superiorID && nodeItem.id == id) {
|
if(node.data.superiorID === nodeItem.data.superiorID && nodeItem.id == id) {
|
||||||
nodeItem.data.isActive = true
|
nodeItem.data.isActive = true
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import { computed } from "vue";
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
default: "",
|
||||||
|
// required: true,
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -30,7 +31,7 @@ const props = defineProps({
|
|||||||
default: 'rem',
|
default: 'rem',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const iconName = computed(() => `#icon-${props.name}`);
|
const iconName = computed(() => `#icon-${props?.name}`);
|
||||||
const svgClass = computed(() => {
|
const svgClass = computed(() => {
|
||||||
if (props.name) return `svg-icon icon-${props.name}`;
|
if (props.name) return `svg-icon icon-${props.name}`;
|
||||||
return "svg-icon";
|
return "svg-icon";
|
||||||
|
|||||||
@@ -173,21 +173,17 @@ export default {
|
|||||||
deleteHint:'Once deleted, you won’t be able to view this conversation again.',
|
deleteHint:'Once deleted, you won’t be able to view this conversation again.',
|
||||||
restoreChat:'Restore chat?',
|
restoreChat:'Restore chat?',
|
||||||
restoreHint:'Once deleted, you won’t be able to view this conversation again.',
|
restoreHint:'Once deleted, you won’t be able to view this conversation again.',
|
||||||
cancel: 'cancel',
|
Cancel: 'Cancel',
|
||||||
Confirm: 'Confirm',
|
Confirm: 'Confirm',
|
||||||
export: 'Export',
|
export: 'Export',
|
||||||
},
|
},
|
||||||
//generateSketch
|
|
||||||
generateSketch: {
|
|
||||||
restore: 'Restore',
|
|
||||||
delete: 'Delete',
|
|
||||||
edit: 'Edit'
|
|
||||||
},
|
|
||||||
flowCanvas: {
|
flowCanvas: {
|
||||||
deleteCardConfirm: 'Are you sure you want to delete this function card?',
|
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
confirmLeave: 'Are you sure you want to leave? You may have unsaved changes.',
|
confirmLeave: 'Are you sure you want to leave? You may have unsaved changes.',
|
||||||
|
cannotFindSuperiorImage: 'Cannot find the superior image',
|
||||||
|
deleteSubordinateCard: 'After deletion, all the function cards will also be deleted.',
|
||||||
|
initialNodeProhibited: 'Initial node is prohibited from being deleted.',
|
||||||
},
|
},
|
||||||
assistant: {
|
assistant: {
|
||||||
inputPlaceholder: 'Ask anything',
|
inputPlaceholder: 'Ask anything',
|
||||||
|
|||||||
@@ -168,9 +168,9 @@ export default {
|
|||||||
deleteHint: '删除后将无法恢复该对话。',
|
deleteHint: '删除后将无法恢复该对话。',
|
||||||
restoreChat: '恢复对话?',
|
restoreChat: '恢复对话?',
|
||||||
restoreHint: '恢复后将显示该对话。',
|
restoreHint: '恢复后将显示该对话。',
|
||||||
cancel: '取消',
|
Cancel: '取消',
|
||||||
Confirm: '确认',
|
Confirm: '确认',
|
||||||
export: '导出'
|
export: '导出',
|
||||||
},
|
},
|
||||||
//generateSketch
|
//generateSketch
|
||||||
generateSketch: {
|
generateSketch: {
|
||||||
@@ -179,10 +179,11 @@ export default {
|
|||||||
edit: '编辑'
|
edit: '编辑'
|
||||||
},
|
},
|
||||||
flowCanvas: {
|
flowCanvas: {
|
||||||
deleteCardConfirm: '确定要删除该功能卡片吗?',
|
|
||||||
confirm: '确认',
|
confirm: '确认',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
confirmLeave: '您可能有未保存的更改,确定要离开吗?',
|
confirmLeave: '您可能有未保存的更改,确定要离开吗?',
|
||||||
|
cannotFindSuperiorImage: '找不到上级图片',
|
||||||
|
initialNodeProhibited: 'Initial node is prohibited from being deleted.',
|
||||||
},
|
},
|
||||||
assistant: {
|
assistant: {
|
||||||
inputPlaceholder: '请输入'
|
inputPlaceholder: '请输入'
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ const setVersionsList = (res)=>{
|
|||||||
}
|
}
|
||||||
traverseArray(res,'',(item,i,father)=>{
|
traverseArray(res,'',(item,i,father)=>{
|
||||||
item.versionId = father?`${father.versionId}-${i+1}`:'1'
|
item.versionId = father?`${father.versionId}-${i+1}`:'1'
|
||||||
|
if(item.id == projectStore.state.nodeId){
|
||||||
|
selectItem.value = {...item}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
versionsList.value = res
|
versionsList.value = res
|
||||||
}
|
}
|
||||||
@@ -110,10 +113,6 @@ const versionDelete = (versionDetail)=>{
|
|||||||
treeKey.value++
|
treeKey.value++
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(()=>projectStore.state.nodeId,(newVal,oldVal)=>{
|
|
||||||
if(!newVal || newVal === selectItem?.value?.id)return
|
|
||||||
selectItem.value = {id:newVal}
|
|
||||||
})
|
|
||||||
|
|
||||||
let data = reactive({})
|
let data = reactive({})
|
||||||
// onMounted(() => {setVersionsList('')})
|
// onMounted(() => {setVersionsList('')})
|
||||||
@@ -126,7 +125,6 @@ const {} = toRefs(data)
|
|||||||
<el-drawer
|
<el-drawer
|
||||||
v-model="versionTreeData.drawer"
|
v-model="versionTreeData.drawer"
|
||||||
:close-on-press-escape="false"
|
:close-on-press-escape="false"
|
||||||
:close-on-click-modal="false"
|
|
||||||
:size="treeState ? '73.5rem' : '73.5rem'"
|
:size="treeState ? '73.5rem' : '73.5rem'"
|
||||||
body-class="versionTreeBody"
|
body-class="versionTreeBody"
|
||||||
:with-header="false"
|
:with-header="false"
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import InputNode from './InputNode.vue'//主
|
|||||||
import SecondaryNode from './secondaryNode.vue'//分支
|
import SecondaryNode from './secondaryNode.vue'//分支
|
||||||
import { useLayout } from '@/utils/treeDiagram'
|
import { useLayout } from '@/utils/treeDiagram'
|
||||||
import dialogVue from "../../components/dialog.vue";
|
import dialogVue from "../../components/dialog.vue";
|
||||||
|
import { ElMessageBox } from 'element-plus'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
selectItem: {
|
selectItem: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -22,7 +24,7 @@ const emit = defineEmits([
|
|||||||
'versionRestore',
|
'versionRestore',
|
||||||
'versionDelete',
|
'versionDelete',
|
||||||
])
|
])
|
||||||
|
const {t:$t} = useI18n()
|
||||||
const dialogDeleteRef = ref()
|
const dialogDeleteRef = ref()
|
||||||
const dialogRestoreRef = ref()
|
const dialogRestoreRef = ref()
|
||||||
|
|
||||||
@@ -89,9 +91,8 @@ watch(()=>props.treeList.length, (newVal, oldVal) => {
|
|||||||
watch(()=>props.selectItem.versionId, (newVal, oldVal) => {
|
watch(()=>props.selectItem.versionId, (newVal, oldVal) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const versionRestore = ()=>{
|
const versionRestore = async ()=>{
|
||||||
emit('versionRestore')
|
dialogDeleteRef.value?.open()
|
||||||
// dialogRestoreRef.value?.open()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const versionDelete = ()=>{
|
const versionDelete = ()=>{
|
||||||
@@ -139,24 +140,24 @@ defineExpose({push})
|
|||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <dialogVue
|
<dialogVue
|
||||||
:textData="{
|
:textData="{
|
||||||
title: $t('VersionTree.deleteChat'),
|
title: $t('VersionTree.deleteChat'),
|
||||||
text: $t('VersionTree.deleteHint'),
|
text: $t('VersionTree.deleteHint'),
|
||||||
submitText: $t('VersionTree.delete'),
|
submitText: $t('VersionTree.delete'),
|
||||||
cancelText: $t('VersionTree.cancel'),
|
cancelText: $t('VersionTree.Cancel'),
|
||||||
}"
|
}"
|
||||||
:styleData="{
|
:styleData="{
|
||||||
width: '40.6rem'
|
width: '40.6rem'
|
||||||
}"
|
}"
|
||||||
:callBack="()=>emit('versionDelete')"
|
:callBack="()=>emit('versionRestore')"
|
||||||
ref="dialogDeleteRef" />
|
ref="dialogDeleteRef" />
|
||||||
<dialogVue
|
<!-- <dialogVue
|
||||||
:textData="{
|
:textData="{
|
||||||
title: $t('VersionTree.restoreChat'),
|
title: $t('VersionTree.restoreChat'),
|
||||||
text: $t('VersionTree.restoreHint'),
|
text: $t('VersionTree.restoreHint'),
|
||||||
submitText: $t('VersionTree.confirm'),
|
submitText: $t('VersionTree.confirm'),
|
||||||
cancelText: $t('VersionTree.cancel'),
|
cancelText: $t('VersionTree.Cancel'),
|
||||||
}"
|
}"
|
||||||
:styleData="{
|
:styleData="{
|
||||||
width: '40.6rem'
|
width: '40.6rem'
|
||||||
@@ -225,10 +226,9 @@ defineExpose({push})
|
|||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: none;
|
display: none;
|
||||||
&.active{
|
&.active{
|
||||||
background-color: #f5f5f5;
|
display: flex;
|
||||||
pointer-events: auto;
|
|
||||||
}
|
}
|
||||||
&:hover{
|
&:hover{
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<span class="label" v-show="!item.edit">{{ item.name }}</span>
|
<span class="label" v-show="!item.edit">{{ item.name }}</span>
|
||||||
<el-popover
|
<el-popover
|
||||||
placement="right"
|
placement="right"
|
||||||
trigger="click"
|
trigger="contextmenu"
|
||||||
width="10rem"
|
width="10rem"
|
||||||
popper-style="
|
popper-style="
|
||||||
padding: .6rem 0.7rem;
|
padding: .6rem 0.7rem;
|
||||||
@@ -54,7 +54,9 @@
|
|||||||
v-model:visible="item.visible"
|
v-model:visible="item.visible"
|
||||||
>
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<span @click.stop class="icon"><svg-icon name="more" size="16" /></span>
|
<span @click.stop="item.visible = !item.visible" class="icon">
|
||||||
|
<svg-icon name="more" size="16" />
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<div class="history-item-menu">
|
<div class="history-item-menu">
|
||||||
<div class="rename" @click="onRenameHistoryItem(item)">
|
<div class="rename" @click="onRenameHistoryItem(item)">
|
||||||
|
|||||||
Reference in New Issue
Block a user