Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/FiDA_Front
This commit is contained in:
@@ -53,7 +53,7 @@
|
|||||||
zoom: { default: 1, type: Number },
|
zoom: { default: 1, type: Number },
|
||||||
step: { default: 0.1, type: Number }
|
step: { default: 0.1, type: Number }
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['export', 'export-local', 'import-local', 'export-close', 'workbench'])
|
const emit = defineEmits(['export', 'workbench'])
|
||||||
const importLocalImage = inject('importLocalImage') as (isRecord?: boolean) => void
|
const importLocalImage = inject('importLocalImage') as (isRecord?: boolean) => void
|
||||||
const stateManager = inject('stateManager') as any
|
const stateManager = inject('stateManager') as any
|
||||||
const toolManager = inject('toolManager') as any
|
const toolManager = inject('toolManager') as any
|
||||||
@@ -178,7 +178,8 @@
|
|||||||
}
|
}
|
||||||
const onWorkbench = async () => {
|
const onWorkbench = async () => {
|
||||||
exportCanvasToImage(canvasManager.canvas).then((url) => {
|
exportCanvasToImage(canvasManager.canvas).then((url) => {
|
||||||
emit('workbench', { url })
|
const json = canvasManager.getCanvasJSON()
|
||||||
|
emit('workbench', { url, json })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fill-repeat">
|
<div class="fill-repeat h">
|
||||||
<div>
|
<div>
|
||||||
<div class="title">Image</div>
|
<div class="title">Image</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -99,6 +99,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, inject, computed, nextTick, onBeforeUnmount } from 'vue'
|
import { ref, inject, computed, nextTick, onBeforeUnmount } from 'vue'
|
||||||
import DepthOffsetTool from '../tools/depth-offset-tool.vue'
|
import DepthOffsetTool from '../tools/depth-offset-tool.vue'
|
||||||
|
import DepthSlider from '../tools/depth-slider.vue'
|
||||||
|
import DepthInput from '../tools/depth-input.vue'
|
||||||
import { getTransformScaleAngle } from '../../manager/ObjectManager'
|
import { getTransformScaleAngle } from '../../manager/ObjectManager'
|
||||||
const objectManager = inject('objectManager') as any
|
const objectManager = inject('objectManager') as any
|
||||||
const stateManager = inject('stateManager') as any
|
const stateManager = inject('stateManager') as any
|
||||||
@@ -159,7 +161,7 @@
|
|||||||
const options = {
|
const options = {
|
||||||
opacity: opacity.value / 100
|
opacity: opacity.value / 100
|
||||||
}
|
}
|
||||||
objectManager.updateOpacity(id.value, options, isRecord)
|
objectManager.updateProperty(id.value, options, isRecord)
|
||||||
}
|
}
|
||||||
|
|
||||||
stateManager.event.add('canvas:undo', updateData)
|
stateManager.event.add('canvas:undo', updateData)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<div class="content" v-if="isShow" v-show="show">
|
<div class="content" v-if="isShow" v-show="show">
|
||||||
<!-- <basic-info :object="activeObject" /> -->
|
<!-- <basic-info :object="activeObject" /> -->
|
||||||
<fill-repeat :object="activeObject" v-if="isRepeat" />
|
<fill-repeat :object="activeObject" v-if="isRepeat" />
|
||||||
|
<!-- <shape-setting :object="activeObject" v-if="isShape && !isRepeat" /> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, inject, computed, watch, onMounted } from 'vue'
|
import { ref, inject, computed, watch, onMounted } from 'vue'
|
||||||
import FillRepeat from './fill-repeat.vue'
|
import FillRepeat from './fill-repeat.vue'
|
||||||
|
import ShapeSetting from './shape-setting.vue'
|
||||||
const props = defineProps({})
|
const props = defineProps({})
|
||||||
const layerManager = inject('layerManager') as any
|
const layerManager = inject('layerManager') as any
|
||||||
const canvasManager = inject('canvasManager') as any
|
const canvasManager = inject('canvasManager') as any
|
||||||
@@ -25,6 +27,8 @@
|
|||||||
const layers = computed(() => layerManager.layers.value)
|
const layers = computed(() => layerManager.layers.value)
|
||||||
const activeObject = ref(null)
|
const activeObject = ref(null)
|
||||||
|
|
||||||
|
// const shapes = ['rect', 'line', 'path', 'triangle', 'polygon', 'ellipse']
|
||||||
|
// const isShape = computed(() => shapes.includes(activeObject.value?.type))
|
||||||
const isRepeat = computed(() => activeObject.value?.fill?.repeat === 'repeat')
|
const isRepeat = computed(() => activeObject.value?.fill?.repeat === 'repeat')
|
||||||
const isShow = computed(() => isRepeat.value)
|
const isShow = computed(() => isRepeat.value)
|
||||||
|
|
||||||
@@ -85,7 +89,12 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
&:deep(> div) {
|
&:deep(> div) {
|
||||||
> div {
|
> div {
|
||||||
margin-bottom: 1.6rem;
|
margin-bottom: var(--details-item-margin-bottom, 1.6rem);
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.h > div {
|
||||||
> .title {
|
> .title {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -97,6 +106,20 @@
|
|||||||
padding: 0 1.4rem;
|
padding: 0 1.4rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.v > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
> .label {
|
||||||
|
min-width: 6rem;
|
||||||
|
margin-right: 0.8rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
> .value {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<div class="shape-setting v">
|
||||||
|
<!-- <div>
|
||||||
|
<div class="label">填充颜色</div>
|
||||||
|
<div class="value">
|
||||||
|
<el-color-picker
|
||||||
|
v-model="data.fill"
|
||||||
|
show-alpha
|
||||||
|
:predefine="['transparent', '#000', '#f00', '#0f0', '#00f']"
|
||||||
|
@change="onChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, inject, computed, nextTick, onBeforeUnmount, reactive } from 'vue'
|
||||||
|
import { ElColorPicker } from 'element-plus'
|
||||||
|
import { getTransformScaleAngle } from '../../manager/ObjectManager'
|
||||||
|
import DepthOffsetTool from '../tools/depth-offset-tool.vue'
|
||||||
|
import DepthSlider from '../tools/depth-slider.vue'
|
||||||
|
import DepthInput from '../tools/depth-input.vue'
|
||||||
|
const objectManager = inject('objectManager') as any
|
||||||
|
const stateManager = inject('stateManager') as any
|
||||||
|
const props = defineProps({
|
||||||
|
object: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const id = computed(() => props.object.info.id)
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
fill: '',
|
||||||
|
stroke: '',
|
||||||
|
strokeWidth: 0
|
||||||
|
})
|
||||||
|
const updateData = async () => {
|
||||||
|
await nextTick()
|
||||||
|
data.fill = props.object.fill
|
||||||
|
data.stroke = props.object.stroke
|
||||||
|
data.strokeWidth = props.object.strokeWidth
|
||||||
|
}
|
||||||
|
updateData()
|
||||||
|
|
||||||
|
const onInpot = () => setPriority(false)
|
||||||
|
const onChange = () => setPriority(true)
|
||||||
|
const setPriority = (isRecord: boolean) => {
|
||||||
|
const options = { ...data }
|
||||||
|
objectManager.updateProperty(id.value, options, isRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
stateManager.event.add('canvas:undo', updateData)
|
||||||
|
stateManager.event.add('canvas:redo', updateData)
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
stateManager.event.remove('canvas:undo', updateData)
|
||||||
|
stateManager.event.remove('canvas:redo', updateData)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.shape-setting {
|
||||||
|
--details-item-margin-bottom: 1rem;
|
||||||
|
> div {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,12 +6,7 @@
|
|||||||
<template v-if="isReady">
|
<template v-if="isReady">
|
||||||
<layer-panel />
|
<layer-panel />
|
||||||
<details-panel />
|
<details-panel />
|
||||||
<depth-header-tools
|
<depth-header-tools @export="exportCanvas" @workbench="(v) => emit('workbench', v)" />
|
||||||
@export="exportCanvas"
|
|
||||||
@export-local="exportCanvasToLocalStorage"
|
|
||||||
@import-local="importCanvasFromLocalStorage"
|
|
||||||
@workbench="(v) => emit('workbench', v)"
|
|
||||||
/>
|
|
||||||
<brush-control-panel :currentTool="toolManager.currentTool.value" />
|
<brush-control-panel :currentTool="toolManager.currentTool.value" />
|
||||||
<zoom
|
<zoom
|
||||||
:zoom="canvasManager.currentZoom.value / 100"
|
:zoom="canvasManager.currentZoom.value / 100"
|
||||||
@@ -44,6 +39,9 @@
|
|||||||
import { KeyEventManager } from './manager/events/KeyEventManager'
|
import { KeyEventManager } from './manager/events/KeyEventManager'
|
||||||
import { ObjectManager } from './manager/ObjectManager'
|
import { ObjectManager } from './manager/ObjectManager'
|
||||||
|
|
||||||
|
import { useGlobalStore } from '@/stores'
|
||||||
|
const globalStore = useGlobalStore()
|
||||||
|
|
||||||
const emit = defineEmits(['workbench', 'close'])
|
const emit = defineEmits(['workbench', 'close'])
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
config: {
|
config: {
|
||||||
@@ -88,15 +86,21 @@
|
|||||||
|
|
||||||
const observer = ref(null)
|
const observer = ref(null)
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
globalStore.setLoading(true)
|
||||||
keyEventManager.registerEvents()
|
keyEventManager.registerEvents()
|
||||||
|
const url = props.config.url || ''
|
||||||
|
const json = props.config.json || ''
|
||||||
await canvasManager.initCanvas({
|
await canvasManager.initCanvas({
|
||||||
canvasRef,
|
canvasRef,
|
||||||
canvasViewWidth: canvasContainerRef.value.clientWidth,
|
canvasViewWidth: canvasContainerRef.value.clientWidth,
|
||||||
canvasViewHeight: canvasContainerRef.value.clientHeight,
|
canvasViewHeight: canvasContainerRef.value.clientHeight,
|
||||||
canvasWidth: props.config.width || 750,
|
canvasWidth: props.config.width || 750,
|
||||||
canvasHeight: props.config.height || 600,
|
canvasHeight: props.config.height || 600,
|
||||||
url: props.config.url || ''
|
url: json ? '' : url
|
||||||
})
|
})
|
||||||
|
if (json) await canvasManager.loadJSON(json)
|
||||||
|
globalStore.setLoading(false)
|
||||||
|
|
||||||
stateManager.onMounted()
|
stateManager.onMounted()
|
||||||
canvasManager.onMounted()
|
canvasManager.onMounted()
|
||||||
layerManager.onMounted()
|
layerManager.onMounted()
|
||||||
@@ -164,24 +168,13 @@
|
|||||||
a.download = 'canvas.png'
|
a.download = 'canvas.png'
|
||||||
a.click()
|
a.click()
|
||||||
})
|
})
|
||||||
}
|
|
||||||
// 导出到本地存储
|
// console.log(canvasManager.getCanvasJSON())
|
||||||
const exportCanvasToLocalStorage = () => {
|
|
||||||
const json = canvasManager.getCanvasJSON()
|
// const object = canvasManager.getCanvasDisUrlJSON()
|
||||||
localStorage.setItem('canvasJSON', json)
|
// console.log(object)
|
||||||
}
|
// const canvas = canvasManager.processCanvasDisUrlJSON(object)
|
||||||
// 从本地存储导入
|
// console.log(canvas)
|
||||||
const importCanvasFromLocalStorage = () => {
|
|
||||||
const json = localStorage.getItem('canvasJSON')
|
|
||||||
if (!json) return
|
|
||||||
canvasManager.loadJSON(json, (success) => {
|
|
||||||
if (success) {
|
|
||||||
console.log('导入成功')
|
|
||||||
} else {
|
|
||||||
console.log('导入失败')
|
|
||||||
}
|
|
||||||
stateManager.clearState(true)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|||||||
@@ -7,23 +7,36 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { base64Tofile } from '../tools/tools'
|
||||||
|
import { uploadImage } from '@/api/upload'
|
||||||
import FullscreenDialog from '../components/fullscreen-dialog.vue'
|
import FullscreenDialog from '../components/fullscreen-dialog.vue'
|
||||||
import depthCanvas from './depth-canvas.vue'
|
import depthCanvas from './depth-canvas.vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const config = ref({
|
const config = ref({
|
||||||
id: '',
|
id: '',
|
||||||
url: ''
|
url: '',
|
||||||
|
json: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const open = (options) => {
|
const open = (options) => {
|
||||||
config.value = options
|
config.value = options
|
||||||
|
console.log(config.value)
|
||||||
|
config.value.json = sessionStorage.getItem('canvasJson_' + config.value.id)
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
// 工作区
|
// 工作区
|
||||||
const onWorkbench = (options) => {
|
const onWorkbench = async (options) => {
|
||||||
|
const json = options.json
|
||||||
|
|
||||||
|
sessionStorage.setItem('canvasJson_' + config.value.id, json)
|
||||||
|
|
||||||
|
const file = base64Tofile(options.url, 'canvas.png')
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
const url = await uploadImage(formData)
|
||||||
|
config.value.onWorkbench?.({ url })
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
config.value.onWorkbench?.(options)
|
|
||||||
}
|
}
|
||||||
// 关闭
|
// 关闭
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
|
|||||||
@@ -289,10 +289,7 @@ export class AnimationManager {
|
|||||||
* @param {Boolean} adaptive 是否自适应缩放
|
* @param {Boolean} adaptive 是否自适应缩放
|
||||||
*/
|
*/
|
||||||
async resetZoom(animated = true, adaptive = false) {
|
async resetZoom(animated = true, adaptive = false) {
|
||||||
const canvasViewWidth = this.canvasManager.canvasViewWidth;
|
const { canvasViewWidth, canvasViewHeight, canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize();
|
||||||
const canvasViewHeight = this.canvasManager.canvasViewHeight;
|
|
||||||
const canvasWidth = this.canvasManager.canvasWidth;
|
|
||||||
const canvasHeight = this.canvasManager.canvasHeight;
|
|
||||||
const scaleX = canvasViewWidth / canvasWidth * 0.8
|
const scaleX = canvasViewWidth / canvasWidth * 0.8
|
||||||
const scaleY = canvasViewHeight / canvasHeight * 0.8
|
const scaleY = canvasViewHeight / canvasHeight * 0.8
|
||||||
const scale = Math.min(scaleX, scaleY, 1)
|
const scale = Math.min(scaleX, scaleY, 1)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { fabric } from 'fabric-with-all'
|
import { fabric } from 'fabric-with-all'
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { createCanvas } from '../tools/canvasFactory'
|
import { createCanvas } from '../tools/canvasFactory'
|
||||||
import { AnimationManager } from './AnimationManager'
|
import { AnimationManager } from './AnimationManager'
|
||||||
import { detectDeviceType } from '../tools/index'
|
import { detectDeviceType } from '../tools/index'
|
||||||
@@ -44,10 +44,6 @@ export class CanvasManager {
|
|||||||
eventManager: any
|
eventManager: any
|
||||||
deviceInfo: any
|
deviceInfo: any
|
||||||
canvas: any
|
canvas: any
|
||||||
canvasViewWidth: number
|
|
||||||
canvasViewHeight: number
|
|
||||||
canvasWidth: number
|
|
||||||
canvasHeight: number
|
|
||||||
currentZoom: any
|
currentZoom: any
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.stateManager = options.stateManager;
|
this.stateManager = options.stateManager;
|
||||||
@@ -55,23 +51,31 @@ export class CanvasManager {
|
|||||||
this.currentZoom = ref(100)
|
this.currentZoom = ref(100)
|
||||||
}
|
}
|
||||||
onMounted() { }
|
onMounted() { }
|
||||||
|
getCanvasSize() {
|
||||||
|
return {
|
||||||
|
canvasViewWidth: this.canvas.width,
|
||||||
|
canvasViewHeight: this.canvas.height,
|
||||||
|
canvasWidth: this.canvas.clipPath.width,
|
||||||
|
canvasHeight: this.canvas.clipPath.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
setCanvasViewSize(options) {
|
setCanvasViewSize(options) {
|
||||||
this.canvasViewWidth = options.canvasViewWidth || 1920
|
var canvasViewWidth = options.canvasViewWidth || 1920
|
||||||
this.canvasViewHeight = options.canvasViewHeight || 1080
|
var canvasViewHeight = options.canvasViewHeight || 1080
|
||||||
this.canvas.setWidth(this.canvasViewWidth)
|
this.canvas.setWidth(canvasViewWidth)
|
||||||
this.canvas.setHeight(this.canvasViewHeight)
|
this.canvas.setHeight(canvasViewHeight)
|
||||||
}
|
}
|
||||||
/** 初始化画布 */
|
/** 初始化画布 */
|
||||||
async initCanvas(options: CanvasInitOptions) {
|
async initCanvas(options: CanvasInitOptions) {
|
||||||
this.layerManager = this.stateManager.layerManager
|
this.layerManager = this.stateManager.layerManager
|
||||||
this.canvasWidth = options.canvasWidth || 750
|
var canvasWidth = options.canvasWidth || 750
|
||||||
this.canvasHeight = options.canvasHeight || 600
|
var canvasHeight = options.canvasHeight || 600
|
||||||
var image = null;
|
var image = null;
|
||||||
if (options.url) {
|
if (options.url) {
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
fabric.Image.fromURL(options.url, async (img) => {
|
fabric.Image.fromURL(options.url, async (img) => {
|
||||||
this.canvasWidth = img.width
|
canvasWidth = img.width
|
||||||
this.canvasHeight = img.height
|
canvasHeight = img.height
|
||||||
img.set({
|
img.set({
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
@@ -104,8 +108,8 @@ export class CanvasManager {
|
|||||||
this.canvas.clipPath = new fabric.Rect({
|
this.canvas.clipPath = new fabric.Rect({
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
width: this.canvasWidth,
|
width: canvasWidth,
|
||||||
height: this.canvasHeight
|
height: canvasHeight
|
||||||
})
|
})
|
||||||
|
|
||||||
// 动画管理器
|
// 动画管理器
|
||||||
@@ -122,19 +126,10 @@ export class CanvasManager {
|
|||||||
|
|
||||||
/** 测试-开始 */
|
/** 测试-开始 */
|
||||||
// this.stateManager.setIsRecord(false)
|
// this.stateManager.setIsRecord(false)
|
||||||
// // 创建矩形
|
// const rect = await this.layerManager.createRectLayer({ left: 200 })
|
||||||
// const rect = await this.layerManager.createRectLayer({
|
// await this.layerManager.createStarLayer({ left: 400 })
|
||||||
// left: 400,
|
// await this.layerManager.createArrowLayer({ left: 600 })
|
||||||
// top: 100,
|
// this.layerManager.setActiveID(rect.info.id)
|
||||||
// })
|
|
||||||
// //创建圆形
|
|
||||||
// const circle = await this.layerManager.createCircleLayer({
|
|
||||||
// left: 200,
|
|
||||||
// top: 200,
|
|
||||||
// })
|
|
||||||
// // 文字
|
|
||||||
// const text = await this.layerManager.createTextLayer('Hello World');
|
|
||||||
// this.layerManager.setActiveID(text.info.id)
|
|
||||||
// this.stateManager.setIsRecord(true)
|
// this.stateManager.setIsRecord(true)
|
||||||
/** 测试-结束 */
|
/** 测试-结束 */
|
||||||
|
|
||||||
@@ -275,20 +270,59 @@ export class CanvasManager {
|
|||||||
return JSON.stringify(json)
|
return JSON.stringify(json)
|
||||||
}
|
}
|
||||||
/** 加载画布JSON */
|
/** 加载画布JSON */
|
||||||
loadJSON(json: string, callback?: (success: boolean) => void) {
|
loadJSON(json: string, rerecord = true) {
|
||||||
let jsonObj = null;
|
return new Promise((resolve) => {
|
||||||
try {
|
let jsonObj = null;
|
||||||
jsonObj = JSON.parse(json)
|
try {
|
||||||
} catch (error) {
|
jsonObj = JSON.parse(json)
|
||||||
console.error('JSON解析错误:', error)
|
} catch (error) {
|
||||||
}
|
console.error('JSON解析错误:', error)
|
||||||
if (!jsonObj) return callback?.(false);
|
}
|
||||||
this.canvas.loadFromJSON(jsonObj, () => {
|
if (!jsonObj) return resolve(false)
|
||||||
this.layerManager.updateLayers()
|
this.canvas.loadFromJSON(jsonObj, () => {
|
||||||
this.renderAll()
|
if (rerecord) this.stateManager.clearState()
|
||||||
callback?.(true)
|
this.resetZoom(false)
|
||||||
|
this.layerManager.updateLayers()
|
||||||
|
this.renderAll()
|
||||||
|
if (rerecord) this.stateManager.recordState()
|
||||||
|
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** 导出画布为处理图片的JSON */
|
||||||
|
getCanvasDisUrlJSON() {
|
||||||
|
const canvas = this.canvas.toJSON()
|
||||||
|
const images = [];
|
||||||
|
var i = 0;
|
||||||
|
const create = (url) => {
|
||||||
|
const logo = `xxxxxxxx_${i++}_xxxxxxxx`;
|
||||||
|
images.push({ index: logo, url })
|
||||||
|
return logo
|
||||||
|
}
|
||||||
|
canvas.objects.forEach((object: any) => {
|
||||||
|
if (object.thumbnail) {
|
||||||
|
object.thumbnail = create(object.thumbnail)
|
||||||
|
}
|
||||||
|
if (object.src) {
|
||||||
|
object.src = create(object.src)
|
||||||
|
}
|
||||||
|
if (object.fill?.source) {
|
||||||
|
object.fill.source = create(object.fill.source)
|
||||||
|
}
|
||||||
|
if (object.info?.fill?.source) {
|
||||||
|
object.info.fill.source = create(object.info.fill.source)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return { canvas: JSON.stringify(canvas), images }
|
||||||
|
}
|
||||||
|
/** 处理JSON为正常画布 */
|
||||||
|
processCanvasDisUrlJSON(obj: { canvas: string, images: Object[] }) {
|
||||||
|
var json = obj.canvas;
|
||||||
|
const images = obj.images || []
|
||||||
|
images.forEach((v: any) => json = json.replace(new RegExp(v.index), v.url))
|
||||||
|
return JSON.parse(json)
|
||||||
|
}
|
||||||
dispose() {
|
dispose() {
|
||||||
this.animationManager?.dispose()
|
this.animationManager?.dispose()
|
||||||
this.eventManager?.dispose()
|
this.eventManager?.dispose()
|
||||||
|
|||||||
@@ -121,8 +121,8 @@ export class LayerManager {
|
|||||||
|
|
||||||
/** 设置图层位置-不设置默认居中 */
|
/** 设置图层位置-不设置默认居中 */
|
||||||
setLayerPosition(object, options?: any) {
|
setLayerPosition(object, options?: any) {
|
||||||
const width = this.canvasManager.canvasWidth
|
const width = this.canvasManager.canvas.clipPath.width
|
||||||
const height = this.canvasManager.canvasHeight
|
const height = this.canvasManager.canvas.clipPath.height
|
||||||
|
|
||||||
if (options && options.top !== undefined) {
|
if (options && options.top !== undefined) {
|
||||||
object.set({ top: options.top })
|
object.set({ top: options.top })
|
||||||
@@ -173,6 +173,7 @@ export class LayerManager {
|
|||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
fill: '#000',
|
fill: '#000',
|
||||||
|
strokeWidth: 0,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
info: {
|
info: {
|
||||||
id: createId("rect"),
|
id: createId("rect"),
|
||||||
@@ -212,6 +213,7 @@ export class LayerManager {
|
|||||||
const ellipseObject = new fabric.Ellipse({
|
const ellipseObject = new fabric.Ellipse({
|
||||||
radius: 50,
|
radius: 50,
|
||||||
fill: '#000',
|
fill: '#000',
|
||||||
|
strokeWidth: 0,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
info: {
|
info: {
|
||||||
id: createId("ellipse"),
|
id: createId("ellipse"),
|
||||||
@@ -230,6 +232,7 @@ export class LayerManager {
|
|||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
fill: '#000',
|
fill: '#000',
|
||||||
|
strokeWidth: 0,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
info: {
|
info: {
|
||||||
id: createId("triangle"),
|
id: createId("triangle"),
|
||||||
@@ -249,6 +252,7 @@ export class LayerManager {
|
|||||||
delete options.points
|
delete options.points
|
||||||
const starObject = new fabric.Polygon(getStarArr(width, height), {
|
const starObject = new fabric.Polygon(getStarArr(width, height), {
|
||||||
fill: '#000',
|
fill: '#000',
|
||||||
|
strokeWidth: 0,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
info: {
|
info: {
|
||||||
id: createId("star"),
|
id: createId("star"),
|
||||||
@@ -290,8 +294,7 @@ export class LayerManager {
|
|||||||
|
|
||||||
/** 创建图片图层 */
|
/** 创建图片图层 */
|
||||||
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true) {
|
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true) {
|
||||||
const canvasWidth = this.canvasManager.canvasWidth
|
const { canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize();
|
||||||
const canvasHeight = this.canvasManager.canvasHeight
|
|
||||||
|
|
||||||
const imageObject = await new Promise((resolve) => {
|
const imageObject = await new Promise((resolve) => {
|
||||||
const url = typeof imgOrUrl === 'string' ? imgOrUrl : imgOrUrl.src
|
const url = typeof imgOrUrl === 'string' ? imgOrUrl : imgOrUrl.src
|
||||||
@@ -340,10 +343,11 @@ export class LayerManager {
|
|||||||
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)
|
this.setActiveID(mergedImage.info.id, false)
|
||||||
await this.canvasManager.add(mergedImage);
|
await this.canvasManager.add(mergedImage, false);
|
||||||
this.canvasManager.canvas.moveTo(mergedImage, index);
|
this.canvasManager.canvas.moveTo(mergedImage, index);
|
||||||
this.canvasManager.renderAll()
|
this.canvasManager.renderAll()
|
||||||
this.updateLayers()
|
this.updateLayers()
|
||||||
|
this.stateManager.recordState()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/** 设置激活对象可擦除 */
|
/** 设置激活对象可擦除 */
|
||||||
|
|||||||
@@ -207,18 +207,15 @@ export class ObjectManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 修改属性
|
||||||
/** 修改透明度
|
|
||||||
* @param id 目标对象ID
|
* @param id 目标对象ID
|
||||||
* @param options 透明度参数
|
* @param options 参数
|
||||||
* @param options.opacity 透明度
|
|
||||||
* @param isRecord 是否记录
|
* @param isRecord 是否记录
|
||||||
*/
|
*/
|
||||||
async updateOpacity(id: string, options: any, isRecord: boolean) {
|
async updateProperty(id: string, options: any, isRecord: boolean) {
|
||||||
const object = this.getFillRepeatObject(id)
|
const object = this.canvasManager.getObjectById(id)
|
||||||
if (!object) return null
|
if (!object) return null
|
||||||
const opacity = options.opacity
|
object.set(options);
|
||||||
object.set("opacity", opacity);
|
|
||||||
this.canvasManager.renderAll()
|
this.canvasManager.renderAll()
|
||||||
if (isRecord) {
|
if (isRecord) {
|
||||||
this.stateManager.recordState()
|
this.stateManager.recordState()
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
import { OperationType, OperationTypes } from "../tools/layerHelper";
|
|
||||||
import { fabric } from 'fabric-with-all'
|
|
||||||
/** 矩形工具管理器 */
|
|
||||||
export class RectToolManager {
|
|
||||||
// 管理器
|
|
||||||
canvasManager: any
|
|
||||||
stateManager: any
|
|
||||||
layerManager: any
|
|
||||||
|
|
||||||
isDragging: boolean = false
|
|
||||||
startX: number = 0
|
|
||||||
startY: number = 0
|
|
||||||
demoObject: any
|
|
||||||
tools = [
|
|
||||||
OperationType.RECTANGLE
|
|
||||||
]
|
|
||||||
constructor(options) {
|
|
||||||
this.canvasManager = options.canvasManager
|
|
||||||
this.stateManager = options.stateManager
|
|
||||||
this.layerManager = options.layerManager
|
|
||||||
}
|
|
||||||
mouseDownEvent(e) {
|
|
||||||
this.isDragging = true
|
|
||||||
this.startX = e.absolutePointer.x
|
|
||||||
this.startY = e.absolutePointer.y
|
|
||||||
const rect = new fabric.Rect({
|
|
||||||
left: this.startX,
|
|
||||||
top: this.startY,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
fill: '#000',
|
|
||||||
evented: false,
|
|
||||||
})
|
|
||||||
this.demoObject = rect
|
|
||||||
this.canvasManager.canvas.add(rect)
|
|
||||||
this.canvasManager.canvas.renderAll()
|
|
||||||
}
|
|
||||||
mouseMoveEvent(e) {
|
|
||||||
if (!this.isDragging) return;
|
|
||||||
var width = e.absolutePointer.x - this.startX
|
|
||||||
var height = e.absolutePointer.y - this.startY
|
|
||||||
var left = this.startX
|
|
||||||
var top = this.startY
|
|
||||||
if (width < 0) {
|
|
||||||
left += width
|
|
||||||
width = -width
|
|
||||||
}
|
|
||||||
if (height < 0) {
|
|
||||||
top += height
|
|
||||||
height = -height
|
|
||||||
}
|
|
||||||
this.demoObject.set({ width, height, left, top })
|
|
||||||
this.canvasManager.canvas.renderAll()
|
|
||||||
}
|
|
||||||
mouseUpEvent(e) {
|
|
||||||
if (!this.isDragging) return;
|
|
||||||
this.isDragging = false;
|
|
||||||
const object = this.demoObject.toJSON("evented")
|
|
||||||
if (object.width === 0) object.width = 100
|
|
||||||
if (object.height === 0) object.height = 100
|
|
||||||
this.layerManager.createRectLayer(object, true)
|
|
||||||
this.canvasManager.canvas.remove(this.demoObject)
|
|
||||||
this.canvasManager.canvas.renderAll()
|
|
||||||
|
|
||||||
}
|
|
||||||
dispose() { }
|
|
||||||
}
|
|
||||||
@@ -120,6 +120,7 @@ export class ShapeToolManager {
|
|||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
fill: '#000',
|
fill: '#000',
|
||||||
|
strokeWidth: 0,
|
||||||
})
|
})
|
||||||
return rect
|
return rect
|
||||||
}
|
}
|
||||||
@@ -159,6 +160,7 @@ export class ShapeToolManager {
|
|||||||
left: this.startX,
|
left: this.startX,
|
||||||
top: this.startY,
|
top: this.startY,
|
||||||
fill: '#000',
|
fill: '#000',
|
||||||
|
strokeWidth: 0,
|
||||||
})
|
})
|
||||||
return circle
|
return circle
|
||||||
}
|
}
|
||||||
@@ -180,6 +182,7 @@ export class ShapeToolManager {
|
|||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
fill: '#000',
|
fill: '#000',
|
||||||
|
strokeWidth: 0,
|
||||||
})
|
})
|
||||||
return triangle
|
return triangle
|
||||||
}
|
}
|
||||||
@@ -203,6 +206,7 @@ export class ShapeToolManager {
|
|||||||
fill: '#000',
|
fill: '#000',
|
||||||
strokeLineJoin: 'round', // 圆角连接
|
strokeLineJoin: 'round', // 圆角连接
|
||||||
strokeLineCap: 'round', // 圆角端点
|
strokeLineCap: 'round', // 圆角端点
|
||||||
|
strokeWidth: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return star
|
return star
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export class StateManager {
|
|||||||
if (!state) return
|
if (!state) return
|
||||||
this.running.value = true
|
this.running.value = true
|
||||||
this.historyIndex.value = index
|
this.historyIndex.value = index
|
||||||
this.canvasManager.loadJSON(state.canvas, () => {
|
this.canvasManager.loadJSON(state.canvas, false).then(() => {
|
||||||
this.event.emit('canvas:undo', state)
|
this.event.emit('canvas:undo', state)
|
||||||
this.running.value = false
|
this.running.value = false
|
||||||
})
|
})
|
||||||
@@ -105,7 +105,7 @@ export class StateManager {
|
|||||||
if (!state) return
|
if (!state) return
|
||||||
this.running.value = true
|
this.running.value = true
|
||||||
this.historyIndex.value = index
|
this.historyIndex.value = index
|
||||||
this.canvasManager.loadJSON(state.canvas, () => {
|
this.canvasManager.loadJSON(state.canvas, false).then(() => {
|
||||||
this.event.emit('canvas:redo', state)
|
this.event.emit('canvas:redo', state)
|
||||||
this.running.value = false
|
this.running.value = false
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,10 +11,14 @@
|
|||||||
<svg-icon :name="v.icon" :size="v.iconSize" />
|
<svg-icon :name="v.icon" :size="v.iconSize" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<button class="export" @click="emit('export')">
|
<div>
|
||||||
<span class="icon"><svg-icon name="export" size="11" /></span>
|
<button class="export" @click="emit('export')">
|
||||||
<span class="text">Export</span>
|
<span class="icon"><svg-icon name="export" size="11" /></span>
|
||||||
</button>
|
<span class="text">Export</span>
|
||||||
|
</button>
|
||||||
|
<div v-loading="true" class="mask" v-if="downloadData.status == 'loading'"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- <button class="import" @click="emit('import')">
|
<!-- <button class="import" @click="emit('import')">
|
||||||
<span class="text">Import</span>
|
<span class="text">Import</span>
|
||||||
</button> -->
|
</button> -->
|
||||||
@@ -26,7 +30,8 @@
|
|||||||
import { TOOLS } from '../manager/ToolManager'
|
import { TOOLS } from '../manager/ToolManager'
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
zoom: { default: 1, type: Number },
|
zoom: { default: 1, type: Number },
|
||||||
step: { default: 0.1, type: Number }
|
step: { default: 0.1, type: Number },
|
||||||
|
downloadData: { default: {}, type: Object }
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['export', 'import'])
|
const emit = defineEmits(['export', 'import'])
|
||||||
const stateManager = inject('stateManager') as any
|
const stateManager = inject('stateManager') as any
|
||||||
@@ -109,8 +114,21 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
> button {
|
> div{
|
||||||
width: 10rem;
|
position: relative;
|
||||||
|
> .mask{
|
||||||
|
position: absolute !important;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
zoom: .6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
// width: 10rem;
|
||||||
|
padding: 0 2.4rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -118,12 +136,15 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 0.8rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.8rem;
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
&:active {
|
&:active {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@
|
|||||||
const nodes = computed(() => props.stateManager.nodes.value)
|
const nodes = computed(() => props.stateManager.nodes.value)
|
||||||
const isSubord = computed(() => nodes.value.some((v) => v.data.superiorID === props.node.id))
|
const isSubord = computed(() => nodes.value.some((v) => v.data.superiorID === props.node.id))
|
||||||
const tier = computed(() => Number(props.node?.data?.tier || 0))
|
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) || tier.value != 2)
|
||||||
const isReturned = computed(() => {
|
const isReturned = computed(() => {
|
||||||
return (
|
return (
|
||||||
props.node.data.type == NODE_DATATYPE.RESULT_IMAGE &&
|
props.node.data.type == NODE_DATATYPE.RESULT_IMAGE &&
|
||||||
@@ -63,7 +65,8 @@
|
|||||||
!isSubord.value &&
|
!isSubord.value &&
|
||||||
NODE_DATATYPE.RESULT_IMAGE === props.node.data.type &&
|
NODE_DATATYPE.RESULT_IMAGE === props.node.data.type &&
|
||||||
!(tier.value === NODE_DATATIER.TO_3VIEW) &&
|
!(tier.value === NODE_DATATIER.TO_3VIEW) &&
|
||||||
isReturned.value
|
isReturned.value &&
|
||||||
|
isAdd3d.value
|
||||||
)
|
)
|
||||||
const onAdd = () => {
|
const onAdd = () => {
|
||||||
const tier_ = tier.value + 1
|
const tier_ = tier.value + 1
|
||||||
|
|||||||
@@ -155,12 +155,6 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
stateManager.recordState()
|
|
||||||
// } else {
|
|
||||||
// subordNode.data.data.url =
|
|
||||||
// 'https://s3-alpha-sig.figma.com/img/8ce2/f1a4/12b93da90e5f17109e7430f14837fd14?Expires=1773619200&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=kmLsTFtXJqfvuxj6husWlDkRDMOIRDjzUUjb7zh79GkBKihUHc0f59k5OAImHTPdaiEREUCCpn~8sQ-si5lenuauJpApCmAU~NsxjfQhuh9m5O~GiHenr2fKu0DIJ75-oCE3859fyxoSFXQgZ9PRmeb98kikMR6uRX9nI5TPUHgKO8ZgkhDBTW~iyaDT~1ybnoK7elPa6T2VzfO-bpIyY-MZ71VRq3RxwmZRxduqHEb3Dh-jjrHyh2SoQsHmUjSJOf-uYilfvpGUResZAjAq8ZVLEjvhzKC2bmCNZIp3RmhYO8ctU7pd5t91J6Xaa6jBLtGfMxbqIm652EC79K0RoA__'
|
|
||||||
// setTimeout(() => stateManager.recordState())
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
//删除功能卡片
|
//删除功能卡片
|
||||||
const onDeleteClick = ()=>{
|
const onDeleteClick = ()=>{
|
||||||
|
|||||||
@@ -28,23 +28,23 @@
|
|||||||
const shortcutList = ref([
|
const shortcutList = ref([
|
||||||
{
|
{
|
||||||
label: 'Change the...',
|
label: 'Change the...',
|
||||||
value: 'Change the...'
|
value: 'Change the style to a realistic design. '
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Bright Colors...',
|
label: 'Bright Colors...',
|
||||||
value: 'Bright Colors...'
|
value: 'Bright colors with modern patterns, change the style to a realistic furniture design. '
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Make the...',
|
label: 'Make the...',
|
||||||
value: 'Make the...'
|
value: 'Make the structure more refined and balanced, change the style to a realistic furniture style. '
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Imagine...',
|
label: 'Imagine...',
|
||||||
value: 'Imagine...'
|
value: 'Imagine this furniture with detailed fabric textures, change the style to a realistic design. '
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Wood Materials with...',
|
label: 'Wood Materials with...',
|
||||||
value: 'Wood Materials with...'
|
value: 'Wood materials with natural oak texture and soft fabric, change the style to a realistic furniture design.'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
const modeList = ref([
|
const modeList = ref([
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
|
v-loadimg="false"
|
||||||
class="image"
|
class="image"
|
||||||
v-if="item.status == 'RETURNED'"
|
v-if="item.status == 'RETURNED'"
|
||||||
:src="item?.url"
|
:src="item?.url"
|
||||||
@@ -58,10 +59,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import myEvent from '@/utils/myEvent'
|
import myEvent from '@/utils/myEvent'
|
||||||
import { downloadImage } from '../../../tools/tools'
|
import { downloadImage, base64Tofile } 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_DATATYPE } from '../../tools/index.d'
|
||||||
|
import { uploadImage } from '@/api/upload'
|
||||||
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({
|
||||||
@@ -161,6 +163,7 @@
|
|||||||
item.scale.x = -item.scale.x
|
item.scale.x = -item.scale.x
|
||||||
})
|
})
|
||||||
stateManager.recordState()
|
stateManager.recordState()
|
||||||
|
stateManager.exportFlow(stateManager.saveCanvasTimeInterval)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -174,6 +177,7 @@
|
|||||||
item.scale.y = -item.scale.y
|
item.scale.y = -item.scale.y
|
||||||
})
|
})
|
||||||
stateManager.recordState()
|
stateManager.recordState()
|
||||||
|
stateManager.exportFlow(stateManager.saveCanvasTimeInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@@ -201,11 +205,19 @@
|
|||||||
eventManager.registerEvents()
|
eventManager.registerEvents()
|
||||||
}
|
}
|
||||||
const depthCanvasWorkbench = (options)=>{
|
const depthCanvasWorkbench = (options)=>{
|
||||||
data.imageProcessTasks.forEach((item) => {
|
console.log(options)
|
||||||
if(item.taskId == options.taskId){
|
// 1. 提取 MIME 类型和 Base64 数据
|
||||||
item.url = options.url
|
const file = base64Tofile(options.url,'image.png')
|
||||||
}
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
uploadImage(formData).then((res) => {
|
||||||
|
data.imageProcessTasks.forEach((item) => {
|
||||||
|
if(item.taskId == options.taskId){
|
||||||
|
item.url = res
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onEdit = (item: any) => {
|
const onEdit = (item: any) => {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</VueFlow>
|
</VueFlow>
|
||||||
</div>
|
</div>
|
||||||
<header-tools @export="exportFlow" @import="importFlow" />
|
<header-tools @export="exportFlow" @import="importFlow" :downloadData="downloadData" />
|
||||||
<zoom
|
<zoom
|
||||||
:zoom="stateManager.zoom.value"
|
:zoom="stateManager.zoom.value"
|
||||||
:step="0.1"
|
:step="0.1"
|
||||||
@@ -107,9 +107,8 @@
|
|||||||
|
|
||||||
const vueFlow = ref<any>()
|
const vueFlow = ref<any>()
|
||||||
const nodeTypes = ref([NODE_TYPE.INPUT, NODE_TYPE.SECONDARY, NODE_TYPE.OUTPUT, NODE_TYPE.ALONE])
|
const nodeTypes = ref([NODE_TYPE.INPUT, NODE_TYPE.SECONDARY, NODE_TYPE.OUTPUT, NODE_TYPE.ALONE])
|
||||||
|
|
||||||
// 状态管理器
|
// 状态管理器
|
||||||
const stateManager = new StateManager({ vueFlow })
|
const stateManager = new StateManager({ vueFlow,sketchId:props.config.imgId })
|
||||||
provide('stateManager', stateManager)
|
provide('stateManager', stateManager)
|
||||||
|
|
||||||
// 事件管理器
|
// 事件管理器
|
||||||
@@ -191,29 +190,34 @@
|
|||||||
}
|
}
|
||||||
// 导出流程
|
// 导出流程
|
||||||
const getFlowJson = () => {
|
const getFlowJson = () => {
|
||||||
if(!stateManager.isSave.value)return ''
|
|
||||||
return JSON.stringify(stateManager.nodes.value)
|
return JSON.stringify(stateManager.nodes.value)
|
||||||
}
|
}
|
||||||
const exportFlow = () => {
|
const downloadData = ref<any>({
|
||||||
|
amount: 0,
|
||||||
|
progress: 0,
|
||||||
|
status: 'success',//success
|
||||||
|
})
|
||||||
|
const exportFlow = async () => {
|
||||||
// console.log(vueFlow.value)
|
// console.log(vueFlow.value)
|
||||||
// console.log(vueFlow.value.toImage)
|
// console.log(vueFlow.value.toImage)
|
||||||
let arr = stateManager.nodes.value.filter((v) => v.data.type === NODE_COMPONENT.RESULT_IMAGE)
|
let arr = stateManager.nodes.value.filter((v) => v.data.type === NODE_COMPONENT.RESULT_IMAGE)
|
||||||
let imgList = []
|
let imgList = []
|
||||||
arr.forEach((v) => {
|
arr.forEach((v,i) => {
|
||||||
v.data.data.imageProcessTasks.forEach((item,index) => {
|
v.data.data.imageProcessTasks.forEach((item,index) => {
|
||||||
let url = item.url
|
let url = item.url
|
||||||
let name = url?.split(".").pop().split("?").shift();
|
let name = url?.split(".").pop().split("?").shift();
|
||||||
imgList.push({url:url,name:`${v.data.type}${index == 0?'':index}.${name}`})
|
imgList.push({url:url,name:`${v.data.type}${i}-${index == 0?'':index}.${name}`})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
downImgListToZip(imgList)
|
downloadData.value.amount = imgList.length
|
||||||
console.log(imgList)
|
downloadData.value.status = 'loading'
|
||||||
|
await downImgListToZip(imgList,(progress)=>{
|
||||||
|
downloadData.value.progress = progress
|
||||||
|
if(progress == downloadData.value.amount){
|
||||||
|
downloadData.value.status = 'success'
|
||||||
|
}
|
||||||
|
})
|
||||||
return
|
return
|
||||||
// flowManager.exportFlow()
|
|
||||||
const str = getFlowJson()
|
|
||||||
stateManager.isSave.value = false
|
|
||||||
emit('exportFlow', str)
|
|
||||||
// localStorage.setItem('flow_json', str)
|
|
||||||
}
|
}
|
||||||
// 导入流程
|
// 导入流程
|
||||||
const importFlow = async (json) => {
|
const importFlow = async (json) => {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<fullscreen-dialog v-model="dialogVisible" @close="close" hide-destroy>
|
<fullscreen-dialog v-model="dialogVisible" @close="close" hide-destroy>
|
||||||
<flow-canvas ref="flowCanvasRef" :config="config" @exportFlow="exportFlow" />
|
<flow-canvas ref="flowCanvasRef" :config="config" />
|
||||||
</fullscreen-dialog>
|
</fullscreen-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import FullscreenDialog from '../components/fullscreen-dialog.vue'
|
import FullscreenDialog from '../components/fullscreen-dialog.vue'
|
||||||
import flowCanvas from './flow-canvas.vue'
|
import flowCanvas from './flow-canvas.vue'
|
||||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { getSketchFlowCanvas, putSketchFlowCanvas } from '@/api/flow-canvas'
|
import { getSketchFlowCanvas, putSketchFlowCanvas } from '@/api/flow-canvas'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
@@ -31,43 +31,12 @@
|
|||||||
config.value.json = json
|
config.value.json = json
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
const exportFlow = async (str) => {
|
|
||||||
if(!config.value.imgId || !str)return
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
putSketchFlowCanvas({
|
|
||||||
id: config.value.imgId,
|
|
||||||
canvasData: str },true).then(() => {
|
|
||||||
resolve(true)
|
|
||||||
}).catch(() => {
|
|
||||||
resolve(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const close = async () => {
|
const close = async () => {
|
||||||
const str = flowCanvasRef.value?.getFlowJson()
|
|
||||||
await exportFlow(str)
|
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
}
|
}
|
||||||
const handleBeforeUnload = (event) => {
|
|
||||||
const str = flowCanvasRef.value?.getFlowJson()
|
|
||||||
if (str) {
|
|
||||||
event.preventDefault()
|
|
||||||
event.returnValue = $t('flowCanvas.confirmLeave')
|
|
||||||
return $t('flowCanvas.confirmLeave')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onMounted(() => {
|
|
||||||
// 添加事件监听
|
|
||||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
||||||
})
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
||||||
})
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
open,
|
open,
|
||||||
close,
|
close,
|
||||||
exportFlow
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export class EventManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.stateManager.recordState()
|
this.stateManager.recordState()
|
||||||
|
this.stateManager.exportFlow(this.stateManager.saveCanvasTimeInterval)
|
||||||
}
|
}
|
||||||
/** 处理点击 */
|
/** 处理点击 */
|
||||||
handleClick(event: any) {
|
handleClick(event: any) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { ref, computed } from "vue";
|
|||||||
import { NODE_TYPE, NODE_DATATYPE } from '../tools/index.d'
|
import { NODE_TYPE, NODE_DATATYPE } from '../tools/index.d'
|
||||||
import { ElMessageBox } from 'element-plus'
|
import { ElMessageBox } from 'element-plus'
|
||||||
import i18n from '@/lang'
|
import i18n from '@/lang'
|
||||||
|
import { putSketchFlowCanvas } from '@/api/flow-canvas'
|
||||||
|
|
||||||
const t = i18n.global.t
|
const t = i18n.global.t
|
||||||
|
|
||||||
export interface NodesItem {
|
export interface NodesItem {
|
||||||
@@ -37,8 +39,12 @@ export class StateManager {
|
|||||||
toolManager: any
|
toolManager: any
|
||||||
generateManager: any
|
generateManager: any
|
||||||
|
|
||||||
// 是否有数据没保存
|
// 保存画布数据定时器
|
||||||
isSave: any
|
saveCanvasTime: any
|
||||||
|
// 保存画布数据定时器时间间隔
|
||||||
|
saveCanvasTimeInterval: any
|
||||||
|
// 打开画布线稿id
|
||||||
|
sketchId: any
|
||||||
// 设置管理器
|
// 设置管理器
|
||||||
setManager(options) {
|
setManager(options) {
|
||||||
options.eventManager && (this.eventManager = options.eventManager)
|
options.eventManager && (this.eventManager = options.eventManager)
|
||||||
@@ -57,7 +63,10 @@ export class StateManager {
|
|||||||
this.mxHistory = ref(50)
|
this.mxHistory = ref(50)
|
||||||
this.historyList = ref([])
|
this.historyList = ref([])
|
||||||
this.historyIndex = ref(0)
|
this.historyIndex = ref(0)
|
||||||
this.isSave = ref(false)
|
|
||||||
|
this.sketchId = options.sketchId
|
||||||
|
this.saveCanvasTimeInterval = 6000
|
||||||
|
this.saveCanvasTime = null
|
||||||
|
|
||||||
this.activeNodeID = ref("")
|
this.activeNodeID = ref("")
|
||||||
this.nodes = ref<NodesItem[]>([]);
|
this.nodes = ref<NodesItem[]>([]);
|
||||||
@@ -100,8 +109,6 @@ export class StateManager {
|
|||||||
})
|
})
|
||||||
return arr
|
return arr
|
||||||
})
|
})
|
||||||
window.nodes = this.nodes
|
|
||||||
window.aaa = this
|
|
||||||
|
|
||||||
}
|
}
|
||||||
/** 设置激活节点 */
|
/** 设置激活节点 */
|
||||||
@@ -110,6 +117,7 @@ export class StateManager {
|
|||||||
addNode(node: NodesItem) {
|
addNode(node: NodesItem) {
|
||||||
this.nodes.value.push(node);
|
this.nodes.value.push(node);
|
||||||
this.recordState()
|
this.recordState()
|
||||||
|
this.exportFlow()
|
||||||
}
|
}
|
||||||
/** 删除节点 */
|
/** 删除节点 */
|
||||||
async deleteNode(id: string, { isElMessageBox } = { isElMessageBox: false }) {
|
async deleteNode(id: string, { isElMessageBox } = { isElMessageBox: false }) {
|
||||||
@@ -136,6 +144,7 @@ export class StateManager {
|
|||||||
if (!deletePromise) return console.log('删除操作被取消')
|
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.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) }
|
||||||
@@ -176,8 +185,22 @@ export class StateManager {
|
|||||||
const size = this.historyList.value.length - this.mxHistory.value
|
const size = this.historyList.value.length - this.mxHistory.value
|
||||||
if (size > 0) this.historyList.value.splice(0, size)
|
if (size > 0) this.historyList.value.splice(0, size)
|
||||||
this.historyIndex.value = this.historyList.value.length - 1
|
this.historyIndex.value = this.historyList.value.length - 1
|
||||||
|
}
|
||||||
this.isSave.value = true
|
/** 画布数据存储 */
|
||||||
|
async exportFlow (time:number = 0){
|
||||||
|
if(!this.sketchId)return
|
||||||
|
clearTimeout(this.saveCanvasTime)
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
this.saveCanvasTime = setTimeout(()=>{
|
||||||
|
putSketchFlowCanvas({
|
||||||
|
id: this.sketchId,
|
||||||
|
canvasData: JSON.stringify(this.nodes.value) }).then(() => {
|
||||||
|
resolve(true)
|
||||||
|
}).catch(() => {
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
},time)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/** 撤回状态 */
|
/** 撤回状态 */
|
||||||
undoState() {
|
undoState() {
|
||||||
@@ -186,6 +209,7 @@ export class StateManager {
|
|||||||
if (!state) return
|
if (!state) return
|
||||||
this.historyIndex.value = index
|
this.historyIndex.value = index
|
||||||
this.nodes.value = JSON.parse(state.nodes)
|
this.nodes.value = JSON.parse(state.nodes)
|
||||||
|
this.exportFlow(this.saveCanvasTimeInterval)
|
||||||
}
|
}
|
||||||
/** 重做状态 */
|
/** 重做状态 */
|
||||||
redoState() {
|
redoState() {
|
||||||
@@ -194,6 +218,7 @@ export class StateManager {
|
|||||||
if (!state) return
|
if (!state) return
|
||||||
this.historyIndex.value = index
|
this.historyIndex.value = index
|
||||||
this.nodes.value = JSON.parse(state.nodes)
|
this.nodes.value = JSON.parse(state.nodes)
|
||||||
|
this.exportFlow(this.saveCanvasTimeInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 显示指定子节点和父节点连接线,隐藏父节点和其他子节点链接线, */
|
/** 显示指定子节点和父节点连接线,隐藏父节点和其他子节点链接线, */
|
||||||
@@ -211,7 +236,7 @@ export class StateManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.isSave.value = false
|
clearTimeout(this.saveCanvasTime)
|
||||||
this.historyList.value = []
|
this.historyList.value = []
|
||||||
this.historyIndex.value = 0
|
this.historyIndex.value = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ export const downloadImage = (url: string, name: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 批量下载图片 */
|
/** 批量下载图片 */
|
||||||
export const downImgListToZip = async (imagesParams) => {
|
export const downImgListToZip = async (imagesParams,callback) => {
|
||||||
const zip = new JSZip()
|
const zip = new JSZip()
|
||||||
const promises = []
|
const promises = []
|
||||||
// 遍历下载每个图片
|
// 遍历下载每个图片
|
||||||
|
let progress = 0
|
||||||
imagesParams.forEach((img, index) => {
|
imagesParams.forEach((img, index) => {
|
||||||
const promise = new Promise((resolve, reject) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest()
|
const xhr = new XMLHttpRequest()
|
||||||
@@ -32,18 +33,17 @@ export const downImgListToZip = async (imagesParams) => {
|
|||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
const fileName = img.name
|
const fileName = img.name
|
||||||
zip.file(fileName, xhr.response)
|
zip.file(fileName, xhr.response)
|
||||||
|
progress++
|
||||||
|
callback(progress)
|
||||||
resolve('')
|
resolve('')
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`下载失败: ${img.url}`))
|
reject(new Error(`下载失败: ${img.url}`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xhr.onerror = () => reject(new Error(`网络错误: ${img.url}`))
|
xhr.onerror = () => reject(new Error(`网络错误: ${img.url}`))
|
||||||
xhr.send()
|
xhr.send()
|
||||||
})
|
})
|
||||||
|
|
||||||
promises.push(promise)
|
promises.push(promise)
|
||||||
console.log(promises,zip)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 等待所有图片下载完成
|
// 等待所有图片下载完成
|
||||||
@@ -56,7 +56,25 @@ export const downImgListToZip = async (imagesParams) => {
|
|||||||
link.href = URL.createObjectURL(content)
|
link.href = URL.createObjectURL(content)
|
||||||
link.download = 'DesignFiles'
|
link.download = 'DesignFiles'
|
||||||
link.click()
|
link.click()
|
||||||
URL.revokeObjectURL(link.href)
|
// URL.revokeObjectURL(link.href)
|
||||||
})
|
})
|
||||||
.catch((error) => console.error('下载失败:', error))
|
.catch((error) => console.error('下载失败:', error))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** base64转二进制 */
|
||||||
|
export const base64Tofile = (base64: string,name: string) => {
|
||||||
|
const arr = base64.split(',')
|
||||||
|
const mime = arr[0].match(/:(.*?);/)[1]
|
||||||
|
const bstr = atob(arr[1])
|
||||||
|
// 2. 转换为 Uint8Array
|
||||||
|
let n = bstr.length
|
||||||
|
const u8arr = new Uint8Array(n)
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = bstr.charCodeAt(n)
|
||||||
|
}
|
||||||
|
// 3. 创建 Blob
|
||||||
|
const blob = new Blob([u8arr], { type: mime })
|
||||||
|
// 4. 创建 File 对象(可选)
|
||||||
|
const file = new File([blob], name, { type: mime })
|
||||||
|
return file
|
||||||
|
}
|
||||||
15
src/main.ts
15
src/main.ts
@@ -30,6 +30,19 @@ app.use(router)
|
|||||||
.use(i18n)
|
.use(i18n)
|
||||||
.mount('#app')
|
.mount('#app')
|
||||||
|
|
||||||
|
const vLoadimg = (el, binding) => {
|
||||||
|
const src = binding.value
|
||||||
|
if (el.src === src) return
|
||||||
|
const img = new Image()
|
||||||
|
img.src = src
|
||||||
|
img.onload = () => {
|
||||||
|
el.src = src
|
||||||
|
}
|
||||||
|
img.onerror = () => {
|
||||||
|
console.log('图片加载失败:', src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 注册
|
||||||
|
app.directive('loadimg', vLoadimg)
|
||||||
flexible();
|
flexible();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user