This commit is contained in:
2026-02-26 11:45:32 +08:00
parent 11d7093af6
commit 708b1f7a36
53 changed files with 1101 additions and 348 deletions

View File

@@ -0,0 +1,96 @@
<template>
<div class="canvas-test">
<div class="canvas-main" ref="canvasMain" @mousedown="onMouseDown" @wheel="onMouseWheel">
<div
class="canvas-content"
:style="{
top: `${data.y}px`,
left: `${data.x}px`,
transform: `scale(${data.scale})`
}"
>
<card type="cards-select" />
<card type="to-real-style" />
<card type="surface-edit" />
<card type="scene-composition" />
<card type="color-palette" />
<card type="to-3d-model" />
<card type="to-3view" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import card from './FlowCanvas/components/cards/index.vue'
import { computed, ref, markRaw, onMounted, reactive, nextTick } from 'vue'
const data = reactive({
x: 100,
y: 200,
scale: 1
})
const onMouseDown = (e: MouseEvent) => {
if (e.button !== 1) return
const ox = data.x
const oy = data.y
const X = e.clientX
const Y = e.clientY
const onMouseMove = (e: MouseEvent) => {
const dx = e.clientX - X
const dy = e.clientY - Y
data.x = ox + dx
data.y = oy + dy
}
const onMouseUp = (e: MouseEvent) => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
}
const onMouseWheel = (e: WheelEvent) => {
var delta = e.deltaY
var scale = data.scale - delta / 1000
if (scale < 0.2) scale = 0.2
if (scale > 10) scale = 10
data.scale = scale
}
</script>
<style lang="less" scoped>
.canvas-test {
// overflow-y: auto;
// display: flex;
// flex-wrap: wrap;
// align-content: flex-start;
// align-items: flex-start;
// padding: 2rem;
// gap: 2rem;
width: 100%;
height: 100%;
background-color: #fcf8f1;
position: relative;
overflow: hidden;
> .canvas-main {
width: 100%;
height: 100%;
position: relative;
> .canvas-content {
position: absolute;
width: auto;
height: auto;
display: flex;
align-items: flex-start;
gap: 5rem;
transform-origin: top left;
}
}
> .vue-flow {
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,65 @@
<template>
<!-- 高级工具选择 -->
<div class="cards-select">
<div v-for="v in list" :key="v.type">
<span class="icon">
<svg-icon :name="v.type + '-2'" size="15" size-unit="px" />
</span>
<span class="title">{{ v.title }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const list = ref([
{
type: 'to-real-style',
title: 'To Real Style'
},
{
type: 'surface-edit',
title: 'Surface Edit'
},
{
type: 'scene-composition',
title: 'Scene Composition'
},
{
type: 'color-palette',
title: 'Color Palette'
},
{
type: 'to-3view',
title: 'To 3-View'
},
{
type: 'to-3d-model',
title: 'To 3D Model'
}
])
defineExpose({})
</script>
<style lang="less" scoped>
.cards-select {
> div {
display: flex;
align-items: center;
padding: 6px 5px;
border-radius: 5px;
cursor: pointer;
&:hover {
background-color: rgb(225, 225, 225);
}
> .icon {
margin-right: 10px;
}
> .title {
font-size: 14px;
color: #000;
}
}
}
</style>

View File

@@ -8,8 +8,12 @@
v-for="(v, i) in data.colors"
:key="i"
:style="{ background: v }"
></div>
<div class="add" @click="addColor">
>
<span class="del" @click="delColor(i)">
<svg-icon name="close-white" size="7" size-unit="px" />
</span>
</div>
<div class="add" @click="addColor" v-if="data.colors.length < maxColor">
<svg-icon name="add" size="12" size-unit="px" color="#fff" />
<input type="color" ref="colorInput" @change="changeColor" />
</div>
@@ -20,11 +24,16 @@
<script setup lang="ts">
import { reactive, onMounted, ref } from 'vue'
const data = reactive({
colors: ['#FF4747', '#F96060', '#FFB1B1', '#FA7B7B', '#FF9090']
colors: []
})
const maxColor = 5
const delColor = (i: number) => {
data.colors.splice(i, 1)
}
const colorInput = ref<HTMLInputElement>()
// 添加颜色
const addColor = () => {
if (data.colors.length >= maxColor) return
colorInput.value?.click()
}
const changeColor = (e: Event) => {
@@ -45,6 +54,25 @@
> div {
width: 35px;
height: 35px;
position: relative;
&:hover {
> .del {
display: flex;
}
}
> .del {
display: none;
position: absolute;
width: 15px;
height: 15px;
top: -5px;
right: -5px;
background-color: #000;
border-radius: 50%;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
> .add {
border: 1px solid rgba(0, 0, 0, 0.1);

View File

@@ -1,16 +1,22 @@
<template>
<div class="card">
<div class="header">
<svg-icon :name="currentComponent?.type" color="#fff" size="16" size-unit="px" />
<svg-icon
v-if="!currentComponent?.hideIcon"
:name="currentComponent?.type"
color="#fff"
size="16"
size-unit="px"
/>
<span>{{ currentComponent?.title }}</span>
<div class="add" @click="emit('add')">
<!-- <div class="add" @click="emit('add')" @mousedown.stop>
<svg-icon name="add" size="14" size-unit="px" />
</div>
</div> -->
</div>
<div class="body">
<div class="body" @mousedown.stop>
<component :is="currentComponent?.component" ref="componentRef" />
</div>
<div class="footer">
<div class="footer" @mousedown.stop v-if="!currentComponent?.hideFooter">
<button @click="onGenerateClick">
<svg-icon name="xingxing" size="16" size-unit="px" />
<span>Generate</span>
@@ -21,20 +27,35 @@
<script setup lang="ts">
import { computed, ref, markRaw, onMounted } from 'vue'
import CardsSelect from './cards-select.vue'
import ToRealStyle from './to-real-style.vue'
import SurfaceEdit from './surface-edit.vue'
import SceneComposition from './scene-composition.vue'
import ColorPalette from './color-palette.vue'
import ToVideo from './to-video.vue'
import To3View from './to-3view.vue'
import To3DModel from './to-3d-model.vue'
import ToVideo from './to-video.vue'
import AddPrint from './add-print.vue'
import ToCAD from './to-cad.vue'
import EditMaterial from './edit-material.vue'
const components = [
{
type: 'cards-select',
title: 'Advanced Tools',
component: CardsSelect,
hideFooter: true,
hideIcon: true
},
{
type: 'to-real-style',
title: 'To Real Style',
component: ToRealStyle
},
{
type: 'surface-edit',
title: 'Surface Edit',
component: SurfaceEdit
},
{
type: 'scene-composition',
title: 'Scene Composition',
@@ -46,43 +67,42 @@
component: ColorPalette
},
{
type: 'to-video',
title: 'To Video',
component: ToVideo
type: 'to-3view',
title: 'To 3-View',
component: To3View
},
{
type: 'to-3d-model',
title: 'To 3D Model',
component: To3DModel
},
{
type: 'to-cad',
title: 'To CAD',
component: ToCAD
},
{
type: 'add-print',
title: 'Add Print',
component: AddPrint
},
{
type: 'edit-material',
title: 'Edit Material',
component: EditMaterial
}
// {
// type: 'to-video',
// title: 'To Video',
// component: ToVideo
// },
// {
// type: 'to-cad',
// title: 'To CAD',
// component: ToCAD
// },
// {
// type: 'add-print',
// title: 'Add Print',
// component: AddPrint
// }
]
const emit = defineEmits(['add', 'generate'])
const props = defineProps({
type: {
type: String as () =>
| 'cards-select'
| 'to-real-style'
| 'surface-edit'
| 'scene-composition'
| 'color-palette'
| 'to-video'
| 'to-3d-model'
| 'to-cad'
| 'add-print'
| 'edit-material',
| 'to-3view',
default: 'to-real-style'
}
})
@@ -107,7 +127,6 @@
box-shadow: 0 15px 21px 0 rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
> .header {
border-radius: var(--border-radius) var(--border-radius) 0 0;
height: 50px;
@@ -142,9 +161,11 @@
font-size: 25px;
box-shadow: 0 8px 20px 0 #71809633;
cursor: pointer;
z-index: 20;
}
}
> .body {
cursor: initial;
padding: 16px 13px;
&:deep(> *) {
width: 100%;
@@ -162,10 +183,11 @@
}
}
> .footer {
margin-bottom: 16px;
cursor: initial;
display: flex;
flex-direction: row-reverse;
padding: 0 13px;
padding-bottom: 16px;
> button {
display: flex;
align-items: center;

View File

@@ -1,7 +1,7 @@
<template>
<!-- 编辑素材 -->
<div class="edit-material">
<p class="label">Material</p>
<div class="surface-edit">
<p class="label">Image</p>
<upload-file v-model="data.file" />
<p class="label">Prompt</p>
<my-textarea v-model="data.prompt" />
@@ -21,6 +21,6 @@
</script>
<style lang="less" scoped>
.edit-material {
.surface-edit {
}
</style>

View File

@@ -2,18 +2,16 @@
<!-- 转3D模型 -->
<div class="to-3d-model">
<p class="label">Image</p>
<upload-file v-model="data.file" />
<p class="label">Prompt</p>
<my-textarea v-model="data.prompt" />
<div class="image">
<img src="https://s3-alpha-sig.figma.com/img/ea2f/590e/9638f62a2fc91e31f33db0022db1642c?Expires=1773014400&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=M0B8oJJOk~dGG0aZAqOIocAp7T0LFdJ9FYmCrEZVTCRzYxM6SJRNtYMTX-rTO3Z~s14QINh~o-S41XiZnBv-0zcKjuWot~VVaNHfd0~1LesfNe2KwvCinT~72btFut1pheLnKE-wWCX5ewtonxU77bnw386YPMTqv7DBZzksf2udsJA7NmOYD6~TUG3Q2dWSt~zPH~lkaidscPqpCnCbqzljCEi4RiHY4U3A45l5XypcX2umqn1UaYUFCTqV9471J4qdB6Dg2pcKocdp-7-3s1De6Q~2SmBOrSgDQ~KEADCB2lhKfhxgWmy0lwMvhTd4l90ygVZDWZRABgjHNrGUvg__" alt="">
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted } from 'vue'
import myTextarea from '../tools/my-textarea.vue'
import uploadFile from '../tools/upload-file.vue'
const data = reactive({
prompt: '',
file: null
})
@@ -22,5 +20,14 @@
<style lang="less" scoped>
.to-3d-model {
> .image {
padding: 18px;
border-radius: 10px;
background-color: #f0f0f0;
>img{
width: 100%;
height: auto;
}
}
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<!-- 转3-View -->
<div class="to-3view">
<p class="label">3D Model</p>
<div class="image">
<img src="https://s3-alpha-sig.figma.com/img/ea2f/590e/9638f62a2fc91e31f33db0022db1642c?Expires=1773014400&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=M0B8oJJOk~dGG0aZAqOIocAp7T0LFdJ9FYmCrEZVTCRzYxM6SJRNtYMTX-rTO3Z~s14QINh~o-S41XiZnBv-0zcKjuWot~VVaNHfd0~1LesfNe2KwvCinT~72btFut1pheLnKE-wWCX5ewtonxU77bnw386YPMTqv7DBZzksf2udsJA7NmOYD6~TUG3Q2dWSt~zPH~lkaidscPqpCnCbqzljCEi4RiHY4U3A45l5XypcX2umqn1UaYUFCTqV9471J4qdB6Dg2pcKocdp-7-3s1De6Q~2SmBOrSgDQ~KEADCB2lhKfhxgWmy0lwMvhTd4l90ygVZDWZRABgjHNrGUvg__" alt="">
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted } from 'vue'
const data = reactive({})
defineExpose({ data })
</script>
<style lang="less" scoped>
.to-3view {
> .image {
padding: 18px;
border-radius: 10px;
background-color: #f0f0f0;
>img{
width: 100%;
height: auto;
}
}
}
</style>

View File

@@ -13,6 +13,8 @@
{{ v.label }}
</div>
</div>
<p class="label">Mode</p>
<my-select v-model="data.mode" :list="modeList" />
<p class="label">Size</p>
<pixel-ratio-selection v-model="data.pixelRatio" />
</div>
@@ -21,6 +23,7 @@
<script setup lang="ts">
import { computed, ref, reactive, onMounted } from 'vue'
import myTextarea from '../tools/my-textarea.vue'
import mySelect from '../tools/my-select.vue'
import pixelRatioSelection from '../tools/pixel-ratio-selection.vue'
const shortcutList = ref([
{
@@ -44,10 +47,14 @@
value: 'Wood Materials with...'
}
])
const modeList = ref([
{ value: 'Fast', label: 'Fast' },
{ value: 'Normal', label: 'Normal' }
])
const data = reactive({
prompt: '',
pixelRatio: '1:1',
file: null
mode: 'Normal'
})
defineExpose({ data })

View File

@@ -0,0 +1,80 @@
<template>
<div class="header-tools">
<span class="icon"><svg-icon name="c-mouse" size="16" /></span>
<span class="icon"><svg-icon name="c-hand" size="18" /></span>
<span class="icon"><svg-icon name="c-t" size="18" /></span>
<span class="line"></span>
<span class="icon"><svg-icon name="c-undo" size="18" /></span>
<span class="icon"><svg-icon name="c-redo" size="18" /></span>
<button class="export">
<span class="icon"><svg-icon name="export" size="11" /></span>
<span class="text">Export</span>
</button>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
const props = defineProps({
zoom: { default: 1, type: Number },
step: { default: 0.1, type: Number }
})
const emit = defineEmits(['add', 'sub'])
</script>
<style lang="less" scoped>
.header-tools {
position: absolute;
top: 10.6rem;
left: 50%;
transform: translateX(-50%);
height: 5rem;
padding: 0.7rem 2.5rem;
border: 0.2rem solid #ebebeb;
background: #ffffff;
border-radius: 1.1rem;
box-shadow: 0 1.66rem 2.33rem 0 rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
justify-content: center;
user-select: none;
gap: 1.4rem;
> .line {
width: 0;
height: 100%;
border-left: 0.2rem solid #d9d9d9;
border-radius: 0.2rem;
margin: 0 0.6rem;
}
> .icon {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
--svg-icon-color: #000;
border-radius: 0.4rem;
&:hover {
background-color: #dfdfdf;
}
}
> .export {
width: 10rem;
height: 3rem;
border-radius: 0.4rem;
border: none;
background-color: #ff7a51;
color: #fff;
font-size: 1.1rem;
display: flex;
align-items: center;
justify-content: center;
gap: 0.8rem;
&:active {
opacity: 0.8;
}
}
}
</style>

View File

@@ -0,0 +1,36 @@
<script lang="ts" setup>
import { Handle, Position } from '@vue-flow/core'
import { ref } from 'vue'
const props = defineProps({
type: {
type: String as () => 'InputNode' | 'SecondaryNode',
default: 'InputNode'
}
})
</script>
<template>
<div class="node start">
<template v-if="type === 'InputNode'">
<Handle type="source" id="Right" :position="Position.Right" />
</template>
<template v-else-if="type === 'SecondaryNode'">
<Handle type="target" id="Left" :position="Position.Left" />
<Handle type="source" id="Right" :position="Position.Right" />
</template>
<div class="item">
<slot></slot>
</div>
</div>
</template>
<style lang="less" scoped>
.node {
.vue-flow__handle {
width: 5px;
height: 5px;
top: 50px;
z-index: 10;
}
}
</style>

View File

@@ -0,0 +1,52 @@
<template>
<div class="my-select">
<el-select :model-value="modelValue" @change="onChange" v-bind="attrs">
<el-option v-for="v in list" :key="v.value" :label="v.label" :value="v.value" />
</el-select>
</div>
</template>
<script setup lang="ts">
import { ref, useAttrs, watch } from 'vue'
const props = defineProps({
modelValue: { required: true },
list: { default: () => [], type: [Array, Object] }
})
const attrs = useAttrs()
const emit = defineEmits(['update:modelValue', 'change'])
const onChange = (value) => {
emit('update:modelValue', value)
emit('change', value)
}
</script>
<style scoped lang="less">
.my-select {
&:deep(.el-select) {
--el-select-input-font-size: 12px;
.el-select__wrapper {
font-size: 12px;
min-height: 0;
height: 28px;
padding: 0 8px;
}
.el-select__selected-item,
.el-select__input-wrapper,
.el-select__placeholder {
line-height: normal;
}
.el-select__input {
height: 24px;
}
}
}
.el-popper {
.el-select-dropdown {
li {
padding-left: 8px;
height: 30px;
line-height: 30px;
font-size: 12px;
}
}
}
</style>

View File

@@ -9,7 +9,7 @@
<div class="control" v-else>
<div class="icon"><svg-icon name="upload" size="17" size-unit="px" /></div>
<div class="txt">{{ tip }}</div>
<button @click="onSelectFile">Select File</button>
<div class="btn" @click="onSelectFile">Select File</div>
</div>
</div>
</template>
@@ -59,8 +59,11 @@
font-size: 8px;
color: #7c7c7c;
}
> button {
box-shadow: 0px 0.75px 0px 0px #00000005;
> .btn {
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0px 0.75px 0px 0px rgba(0, 0, 0, 0.02);
min-width: 39px;
height: 13px;
border-radius: 2.3px;

View File

@@ -0,0 +1,116 @@
<template>
<div class="flow-canvas">
<VueFlow
ref="vueFlow"
:nodes="nodes"
:edges="edges"
:min-zoom="0.1"
:max-zoom="10"
:nodes-draggable="true"
@nodes-initialized="layoutGraph('LR')"
@node-drag-stop="(e) => eventManager.handleNodeDragStop(e)"
@viewport-change="(e) => eventManager.handleViewportChange(e)"
>
<template #node-InputNode="nodeProps">
<node type="InputNode">
<component :is="nodeProps.data.component" v-bind="nodeProps.data" />
</node>
</template>
<template #node-SecondaryNode="nodeProps">
<node type="SecondaryNode">
<component :is="nodeProps.data.component" v-bind="nodeProps.data" />
</node>
</template>
</VueFlow>
</div>
<header-tools />
<zoom
:zoom="stateManager.zoom.value"
:step="0.1"
@add="(e) => flowManager.setZoom(e)"
@sub="(e) => flowManager.setZoom(e)"
/>
</template>
<script setup lang="ts">
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { useLayout } from '@/utils/treeDiagram'
// 组件
import headerTools from './components/header-tools.vue'
import zoom from '../components/zoom.vue'
import node from './components/node.vue'
import card from './components/cards/index.vue'
import { computed, ref, markRaw, onMounted, reactive, nextTick } from 'vue'
// 管理器
import { StateManager } from './manager/StateManager'
import { EventManager } from './manager/EventManager'
import { FlowManager } from './manager/FlowManager'
const vueFlow = ref<any>()
const stateManager = new StateManager({ vueFlow })
const nodes = computed(() => stateManager.nodes.value)
const edges = computed(() => stateManager.edges.value)
const eventManager = new EventManager({
stateManager,
vueFlow
})
const flowManager = new FlowManager({
stateManager,
vueFlow
})
const { fitView } = useVueFlow()
const { layout } = useLayout()
const index = ref(0)
async function layoutGraph(direction) {
if (index.value > 0) return
index.value++
setTimeout(() => {
stateManager.nodes.value = layout(
stateManager.nodes.value,
stateManager.edges.value,
direction
)
nextTick(() => {
fitView()
})
}, 0)
}
onMounted(() => {
window.vueFlow = vueFlow
window.nodes = nodes
window.addaaaaa = () => {
const lastNode = vueFlow.value.getNode(nodes.value[nodes.value.length - 1].id)
const width = lastNode.dimensions.width
const x = lastNode.position.x
const y = lastNode.position.y
nodes.value.push({
id: nodes.value.length + 1 + '',
type: 'SecondaryNode',
class: 'custom-node',
data: { component: card, type_: 'to-3d-model' },
position: {
x: width + x + 50,
y: y
}
})
}
})
</script>
<style lang="less">
@import '@vue-flow/core/dist/style.css';
@import '@vue-flow/core/dist/theme-default.css';
</style>
<style lang="less" scoped>
.flow-canvas {
width: 100%;
height: 100%;
> .vue-flow {
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -1,151 +1,25 @@
<template>
<fullscreen-dialog v-model="dialogVisible">
<div class="flow-canvas">
<VueFlow
ref="vueFlow"
:nodes="nodes"
:edges="edges"
:nodes-draggable="true"
@nodes-initialized="layoutGraph('LR')"
@node-drag-stop="handleNodeDragStop"
>
<template #node-InputNode="nodeProps">
<inputNode v-bind="nodeProps">
<template v-slot:content>
<component
:is="nodeProps.data.component"
:type="nodeProps.data.type_"
/>
</template>
</inputNode>
</template>
<template #node-SecondaryNode="nodeProps">
<secondaryNode v-bind="nodeProps">
<template v-slot:content>
<component
:is="nodeProps.data.component"
:type="nodeProps.data.type_"
/>
</template>
</secondaryNode>
</template>
</VueFlow>
</div>
<fullscreen-dialog v-model="dialogVisible" hide-destroy>
<flow-canvas />
</fullscreen-dialog>
</template>
<script setup lang="ts">
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { useLayout } from '@/utils/treeDiagram'
import secondaryNode from '../components/node/secondaryNode.vue'
import inputNode from '../components/node/InputNode.vue'
import FullscreenDialog from '../components/fullscreen-dialog.vue'
import card from './components/cards/index.vue'
import { computed, ref, markRaw, onMounted, reactive, nextTick } from 'vue'
import { useGlobalStore } from '@/stores'
const globalStore = useGlobalStore()
import flowCanvas from './flow-canvas.vue'
import { ref } from 'vue'
const dialogVisible = ref(false)
const position = { x: 0, y: 0 }
const nodes = ref<any[]>([
{
id: '1',
type: 'InputNode',
class: 'custom-node start',
position: { x: 0, y: 0 },
data: { component: card, type_: 'to-real-style' }
},
{
id: '2',
type: 'SecondaryNode',
class: 'custom-node',
position: { x: 0, y: 0 },
data: { component: card, type_: 'scene-composition' }
},
{
id: '3',
type: 'SecondaryNode',
class: 'custom-node',
position: { x: 0, y: 0 },
data: { component: card, type_: 'to-3d-model' }
}
])
const edges = computed(() => {
const arr = []
nodes.value.forEach((node, index) => {
if (index < nodes.value.length - 1) {
const id = node.id
const target = nodes.value[index + 1].id
arr.push({
id: `el-${id}-${target}`,
source: id,
target: target,
type: 'smoothstep'
})
}
})
return arr
})
const vueFlow = ref<any>()
onMounted(() => {
window.vueFlow = vueFlow
window.nodes = nodes
window.test = () => {
return vueFlow.value
}
window.addaaaaa = () => {
const lastNode = vueFlow.value.getNode(nodes.value[nodes.value.length - 1].id)
const width = lastNode.dimensions.width
const x = lastNode.position.x
const y = lastNode.position.y
nodes.value.push({
id: '4',
type: 'SecondaryNode',
class: 'custom-node',
data: { id: '主 1', component: card, type_: 'to-3d-model' },
position: {
x: width + x + 50,
y: y
}
})
}
})
const handleNodeDragStop = (e: any) => {
const { node } = e
const { id, position } = node
nodes.value.forEach((item) => {
if (item.id === id) {
item.position.x = position.x
item.position.y = position.y
}
})
const open = () => {
console.log('open')
dialogVisible.value = true
}
const { fitView } = useVueFlow()
const { layout } = useLayout()
const index = ref(0)
async function layoutGraph(direction) {
if (index.value > 0) return
index.value++
setTimeout(() => {
nodes.value = layout(nodes.value, edges.value, direction)
console.log(nodes.value)
nextTick(() => {
fitView()
})
}, 0)
const close = () => {
dialogVisible.value = false
}
defineExpose({
open,
close
})
</script>
<style lang="less">
@import '@vue-flow/core/dist/style.css';
@import '@vue-flow/core/dist/theme-default.css';
</style>
<style lang="less" scoped>
.flow-canvas {
width: 100%;
height: 100%;
> .vue-flow {
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,24 @@
export class EventManager {
stateManager: any
vueFlow: any
zoom: any
constructor(options) {
this.stateManager = options.stateManager;
this.vueFlow = options.vueFlow
this.zoom = this.stateManager.zoom
}
handleViewportChange(e: any) {
const { zoom } = e
this.zoom.value = zoom
}
handleNodeDragStop(e: any) {
const { node } = e
const { id, position } = node
this.stateManager.nodes.value.forEach((item) => {
if (item.id === id) {
item.position.x = position.x
item.position.y = position.y
}
})
}
}

View File

@@ -0,0 +1,22 @@
export class FlowManager {
stateManager: any
vueFlow: any
constructor(options) {
this.stateManager = options.stateManager;
this.vueFlow = options.vueFlow
}
setZoom(zoom: number) {
this.stateManager.zoom.value = zoom
this.vueFlow.value.zoomTo(zoom)
}
handleNodeDragStop(e: any) {
const { node } = e
const { id, position } = node
this.stateManager.nodes.value.forEach((item) => {
if (item.id === id) {
item.position.x = position.x
item.position.y = position.y
}
})
}
}

View File

@@ -0,0 +1,90 @@
import { ref, computed } from "vue";
import card from '../components/cards/index.vue'
export class StateManager {
vueFlow: any
nodes: any
edges: any
zoom: any
constructor(options) {
this.vueFlow = options.vueFlow
this.nodes = ref<any[]>([
{
id: '1',
type: 'InputNode',
class: 'custom-node start',
position: { x: 0, y: 0 },
data: { component: card, type: 'to-real-style' }
},
{
id: '2',
type: 'SecondaryNode',
class: 'custom-node',
position: { x: 0, y: 0 },
data: { component: card, type: 'scene-composition' }
},
{
id: '3',
type: 'SecondaryNode',
class: 'custom-node',
position: { x: 0, y: 0 },
data: { component: card, type: 'color-palette' }
},
{
id: '4',
type: 'SecondaryNode',
class: 'custom-node',
position: { x: 0, y: 0 },
data: { component: card, type: 'to-video' }
},
{
id: '5',
type: 'SecondaryNode',
class: 'custom-node',
position: { x: 0, y: 0 },
data: { component: card, type: 'to-3d-model' }
},
{
id: '6',
type: 'SecondaryNode',
class: 'custom-node',
position: { x: 0, y: 0 },
data: { component: card, type: 'to-cad' }
},
{
id: '7',
type: 'SecondaryNode',
class: 'custom-node',
position: { x: 0, y: 0 },
data: { component: card, type: 'add-print' }
},
{
id: '8',
type: 'SecondaryNode',
class: 'custom-node',
position: { x: 0, y: 0 },
data: { component: card, type: 'edit-material' }
}
]);
this.edges = computed(() => {
const arr = []
this.nodes.value.forEach((node, index) => {
if (index < this.nodes.value.length - 1) {
const id = node.id
const target = this.nodes.value[index + 1].id
arr.push({
id: `el-${id}-${target}`,
source: id,
target: target,
type: 'smoothstep'
})
}
})
return arr
})
this.zoom = ref(1)
}
}

View File

@@ -11,6 +11,9 @@
>
<slot></slot>
<my-info />
<div class="close-btn" @click="close">
<svg-icon name="back-white" color="#fff" size="18" />
</div>
</div>
</template>
@@ -25,7 +28,6 @@
const emit = defineEmits(['update:modelValue', 'closed'])
const show = ref(props.modelValue)
const show_ = ref(props.modelValue)
console.log(props.modelValue)
const timeout = ref(null)
watch(
() => props.modelValue,
@@ -44,6 +46,9 @@
}
}
)
const close = () => {
emit('update:modelValue', false)
}
</script>
<style lang="less" scoped>
@@ -55,7 +60,6 @@
left: 0;
overflow: hidden;
z-index: 1000;
background-color: #FFFCF4;
transition: opacity var(--transition-time);
opacity: 0;
&.show {
@@ -66,5 +70,33 @@
top: 3rem;
right: 3rem;
}
> .close-btn {
cursor: pointer;
position: absolute;
top: 3.4rem;
left: 3rem;
width: 5.1rem;
height: 5.1rem;
border-radius: 50%;
background-color: #ff7a51;
display: flex;
align-items: center;
justify-content: center;
}
}
.fullscreen-dialog {
--size: 0.25rem;
--width: 4rem;
--color: #bfbfbf;
background-color: #fffcf4;
background-image: repeating-radial-gradient(
circle at 50% 50%,
var(--color) 0,
var(--color) var(--size),
transparent var(--size),
transparent var(--width)
);
background-position: center center;
background-size: var(--width) var(--width); /* 设置平铺的大小 */
}
</style>

View File

@@ -1,27 +0,0 @@
<script lang="ts" setup>
import { Handle, Position } from '@vue-flow/core'
import { ref } from 'vue'
const props = defineProps<{
data: {
type: Object
default: () => {
id: ''
}
}
}>()
</script>
<template>
<div class="node start">
<Handle type="source" style="top: 40px" id="Right" :position="Position.Right" />
<div class="item">
<slot name="content"></slot>
</div>
</div>
</template>
<style lang="less" scoped>
.node {
cursor: initial;
}
</style>

View File

@@ -1,31 +0,0 @@
<script lang="ts" setup>
import { Handle, Position } from '@vue-flow/core'
import { ref } from 'vue'
const props = defineProps<{
data: {
type: Object
default: () => {
id: ''
}
}
}>()
</script>
<!-- source输入target输出 -->
<template>
<div class="node">
<Handle type="target" style="top: 40px" id="Left" :position="Position.Left" />
<Handle type="source" style="top: 40px" id="Right" :position="Position.Right" />
<!-- <Handle type="source" id="Right" :position="Position.Right" />
<Handle type="target" id="Left" :position="Position.Left" /> -->
<!-- <div>{{ props.data.id }}</div> -->
<div class="item">
<slot name="content"></slot>
</div>
</div>
</template>
<style lang="less" scoped>
.node {
cursor: initial;
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<div class="zoom">
<span class="icon" @click="zoomIn"><svg-icon name="add" size="14" /></span>
<span class="value">{{ Math.round(zoom * 100) }}%</span>
<span class="icon" @click="zoomOut"><svg-icon name="sub" size="14" /></span>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
const props = defineProps({
zoom: { default: 1, type: Number },
step: { default: 0.1, type: Number }
})
const emit = defineEmits(['add', 'sub'])
const zoomIn = () => {
emit('add', Number(props.zoom) + props.step)
}
const zoomOut = () => {
emit('sub', Number(props.zoom) - props.step)
}
</script>
<style lang="less" scoped>
.zoom {
position: absolute;
bottom: 4rem;
right: 3rem;
padding: 0.7rem 1rem;
min-width: 12.8rem;
height: 4.2rem;
border-radius: 0.8rem;
border: 0.1rem solid #ffcf90;
background: #ffffff;
box-shadow: 0 1.5rem 2.1rem 0 rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
justify-content: center;
color: #000;
user-select: none;
> .icon {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 2.8rem;
height: 2.8rem;
--svg-icon-color: #0d0d0d;
}
> .value {
flex: 1;
text-align: center;
font-family: Medium;
font-size: 1.6rem;
}
}
</style>

View File

@@ -29,7 +29,7 @@
watch(codeStr, (newVal) => {
emit('update:modelValue', newVal)
})
const resetCode = (size) => {
const resetCode = (size = props.length) => {
code.value = []
for (let i = 0; i < size; i++) {
code.value.push('')
@@ -83,6 +83,9 @@
onMounted(() => {
focusLast()
})
defineExpose({
resetCode
})
</script>
<style lang="less" scoped>