fix
This commit is contained in:
96
src/components/Canvas/CanvasTest.vue
Normal file
96
src/components/Canvas/CanvasTest.vue
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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 })
|
||||
|
||||
80
src/components/Canvas/FlowCanvas/components/header-tools.vue
Normal file
80
src/components/Canvas/FlowCanvas/components/header-tools.vue
Normal 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>
|
||||
36
src/components/Canvas/FlowCanvas/components/node.vue
Normal file
36
src/components/Canvas/FlowCanvas/components/node.vue
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
116
src/components/Canvas/FlowCanvas/flow-canvas.vue
Normal file
116
src/components/Canvas/FlowCanvas/flow-canvas.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
24
src/components/Canvas/FlowCanvas/manager/EventManager.ts
Normal file
24
src/components/Canvas/FlowCanvas/manager/EventManager.ts
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
22
src/components/Canvas/FlowCanvas/manager/FlowManager.ts
Normal file
22
src/components/Canvas/FlowCanvas/manager/FlowManager.ts
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
90
src/components/Canvas/FlowCanvas/manager/StateManager.ts
Normal file
90
src/components/Canvas/FlowCanvas/manager/StateManager.ts
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
57
src/components/Canvas/components/zoom.vue
Normal file
57
src/components/Canvas/components/zoom.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user