上传字体包 版本树页面

This commit is contained in:
X1627315083@163.com
2026-02-05 10:43:18 +08:00
parent 29baa2f786
commit 58c7e4187b
48 changed files with 566 additions and 246 deletions

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"tabWidth": 4,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"

View File

@@ -7,7 +7,7 @@
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> -->
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
<link rel="stylesheet" href="/css/woff/fontFamily.css">
<link rel="stylesheet" href="/fonts/general/css/general-sans.css">
<title>FiDA</title>
</head>

9
package-lock.json generated
View File

@@ -40,7 +40,6 @@
"less": "^4.3.0",
"lint-staged": "^13.2.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.4",
"typescript": "~4.8.4",
"unplugin-auto-import": "^0.15.3",
"unplugin-vue-components": "^0.24.1",
@@ -6256,11 +6255,16 @@
"resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
@@ -13280,7 +13284,8 @@
"version": "2.8.8",
"resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true
"dev": true,
"peer": true
},
"prettier-linter-helpers": {
"version": "1.0.0",

View File

@@ -44,7 +44,6 @@
"less": "^4.3.0",
"lint-staged": "^13.2.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.4",
"typescript": "~4.8.4",
"unplugin-auto-import": "^0.15.3",
"unplugin-vue-components": "^0.24.1",

View File

@@ -1,50 +0,0 @@
/* cyrillic-ext */
@font-face {
font-family: 'satoshiRegular';
font-style: italic;
font-weight: 700;
src: url("./Satoshi/Satoshi-Regular.ttf") format('woff2'), url("./Satoshi/Satoshi-Regular.woff") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'satoshiBold';
font-style: italic;
font-weight: 700;
src: url("./Satoshi/Satoshi-Bold.ttf") format('woff2'), url("./Satoshi/Satoshi-Bold.woff") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'satoshiMedium';
font-style: italic;
font-weight: 700;
src: url("./Satoshi/Satoshi-Medium.ttf") format('woff2'), url("./Satoshi/Satoshi-Medium.woff") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'mazzardHRegular';
font-style: italic;
font-weight: 700;
src: url("./Mazzard/MazzardH-Regular.otf") format('opentype');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'robotoBold';
font-style: italic;
font-weight: 700;
src: url("./Roboto/Roboto-Bold.ttf") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'robotoRegular';
font-style: italic;
font-weight: 700;
src: url("./Roboto/Roboto-Regular.ttf") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'boskaRegular';
font-style: italic;
font-weight: 700;
src: url("./Boska/Boska-Regular.ttf") format('woff2'), url("./Boska/Boska-Regular.woff") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}

View File

@@ -0,0 +1,70 @@
/**
* @license
*
* Font Family: General Sans
* Designed by: Frode Helland
* URL: https://www.fontshare.com/fonts/general-sans
* © 2026 Indian Type Foundry
*
* General Sans Extralight
* General Sans ExtralightItalic
* General Sans Light
* General Sans LightItalic
* General Sans Regular
* General Sans Italic
* General Sans Medium
* General Sans MediumItalic
* General Sans Semibold
* General Sans SemiboldItalic
* General Sans Bold
* General Sans BoldItalic
* General Sans Variable (Variable font)
* General Sans VariableItalic (Variable font)
*
*/
@font-face {
font-family: 'Regular';
src:url('../fonts/GeneralSans-Regular.otf') format('opentype');
font-weight: 400;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Medium';
src:url('../fonts/GeneralSans-Medium.otf') format('opentype'),
url('../fonts/GeneralSans-MediumItalic.otf') format('opentype');
font-weight: 500;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'SemiBold';
src:url('../fonts/GeneralSans-Semibold.otf') format('opentype'),
url('../fonts/GeneralSans-SemiboldItalic.otf') format('opentype');
font-weight: 600;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Bold';
src:url('../fonts/GeneralSans-Bold.otf') format('opentype'),
url('../fonts/GeneralSans-BoldItalic.otf') format('opentype');
font-weight: 700;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Migra-Extrabold';
src:url('../fonts/Migra-Extrabold.otf') format('opentype'),
url('../fonts/Migra-Extrabold.ttf') format('truetype'),
url('../fonts/Migra-Extrabold.woff') format('woff'),
url('../fonts/Migra-Extrabold.woff2') format('woff2');
font-weight: 700;
font-display: swap;
font-style: normal;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -12,6 +12,7 @@ p {
}
* {
box-sizing: border-box;
font-family: 'Medium';
}
html,
body,
@@ -33,32 +34,3 @@ body,
background-image: url('@/assets/images/home-bg.png');
background-size: 100% 100%;
}
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.flex-center {
justify-content: center;
align-items: center;
}
.flex-1 {
flex: 1;
}
.align-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.space-between {
justify-content: space-between;
}

View File

@@ -13,6 +13,7 @@ p {
* {
box-sizing: border-box;
font-family: 'Medium';
}
html,

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

View File

@@ -97,5 +97,14 @@ export default {
japan: 'Japan',
canada: 'Canada',
germany: 'Germany'
},
// Version Tree
VersionTree: {
versionInformation: 'Version Information',
input: 'Input',
userRequest: 'User Request',
sketch: 'Sketch',
generateResult: 'Generate Result',
}
}

View File

@@ -0,0 +1,103 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import userHead from '@/assets/images/chatVersion/userHead.png'
import robotHead from '@/assets/images/chatVersion/robotHead.png'
import { useI18n } from 'vue-i18n'
const { t: $t } = useI18n()
const props = defineProps({
type: {
type: String,
default: 'user'
},
})
//const emit = defineEmits([
//])
let data = reactive({
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="ChatHistory">
<div class="titleInfo">
<div class="userInfo">
<img :src="type == 'user'? userHead:robotHead" alt="">
<span>{{ type == 'user'? $t('VersionTree.input'): $t('VersionTree.sketch') }}</span>
</div>
<div class="time">
<span>12:00:00</span>
</div>
</div>
<div class="infoTitle">{{ type == 'user'? $t('VersionTree.userRequest'): $t('VersionTree.generateResult') }}</div>
<div class="history">
Design a modern yellow sofa that combines comfort, elegance, and contemporary aesthetics. The sofa features a warm, soft yellow tone (mustard or light ochre), with a minimalist silhouette and clean lines. Upholstered in high-quality fabric with a subtle texture, offering a cozy and inviting feel. The seat is deep and plush, with generous cushioning for relaxation, while the backrest and armrests are softly rounded to enhance comfort.
Design a modern yellow sofa that combines comfort, elegance, and contemporary aesthetics. The sofa features a warm, soft yellow tone (mustard or light ochre), with a minimalist silhouette and clean lines. Upholstered in high-quality fabric with a subtle texture, offering a cozy and inviting feel. The seat is deep and plush, with generous cushioning for relaxation, while the backrest and armrests are softly rounded to enhance comfort.
</div>
</div>
</template>
<style lang="less" scoped>
.ChatHistory{
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
> .titleInfo{
height: 2.4rem;
display: flex;
justify-content: space-between;
> .userInfo{
display: flex;
align-items: center;
> img{
margin-right: .2rem;
}
> span{
font-family: 'SemiBold';
font-weight: 600;
font-size: 1.4rem;
line-height: 2rem;
color: #000;
opacity: .5;
}
}
> .time{
font-family: 'SemiBold';
font-weight: 600;
font-size: 1.4rem;
line-height: 2rem;
opacity: .5;
}
}
> .infoTitle{
margin-top: .6rem;
font-size: 1.2rem;
line-height: 2rem;
margin-left: 3rem;
}
> .history{
margin-top: .8rem;
font-family: 'Regular';
font-weight: 400;
font-size: 1.2rem;
line-height: 1.4rem;
letter-spacing: Letter Spacing;
flex: 1;
overflow-y: auto;
margin-left: 3rem;
scrollbar-width: thin;
scrollbar-color: #ababab #f1f5f9;
-ms-overflow-style: -ms-autohiding-scrollbar;
&::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,71 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import VersionDetail from './versionDetail.vue'
import ChatHistory from './chatHistory.vue'
//const props = defineProps({
//})
//const emit = defineEmits([
//])
const detailData = ref({
id:1,
versionDetail:{
version:'1.0.0',
versionTime:'2023-08-01 10:00:00',
versionSketch:'Version 1 - Sketch',
versionSketchTime:'2023-08-01 10:00:00',
},
userChatHistory:{
}
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
// const {} = toRefs(data);
</script>
<template>
<div class="detailBox">
<div class="versionDetail">
<VersionDetail :versionDetail="detailData.versionDetail"></VersionDetail>
</div>
<div class="useInput">
<ChatHistory type="user"></ChatHistory>
</div>
<div class="systemInput">
<ChatHistory type="robot"></ChatHistory>
</div>
</div>
</template>
<style lang="less" scoped>
.detailBox{
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
> div{
border-radius: var(--border-radius, 1rem);
border: 1px solid #D9D9D9;
background-color: #f7f7f7;
margin-bottom: 3.6rem;
padding: 1.5rem 1.4rem;
width: 100%;
&.versionDetail{
height: 21rem;
}
&.useInput{
height: 21.5rem;
}
&.systemInput{
flex: 1;
}
&:last-child{
margin-bottom: 0;
}
}
}
</style>

View File

@@ -0,0 +1,77 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import { useI18n } from 'vue-i18n'
const { t: $t } = useI18n()
const props = defineProps({
versionDetail: {
type: Object,
default: () => ({})
}
})
//const emit = defineEmits([
//])
let data = reactive({
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="versionDetail">
<div class="title">
<span class="titleText">
{{ $t('VersionTree.versionInformation') }}
</span>
<span class="icon"><svg-icon name="more" size="25" /></span>
</div>
<div class="version">{{versionDetail.version}}</div>
<div class="time marBott1">{{versionDetail.versionTime}}</div>
<div class="version gray">{{versionDetail.versionSketch}}</div>
<div class="time gray">{{versionDetail.versionSketchTime}}</div>
</div>
</template>
<style lang="less" scoped>
.versionDetail{
width: 100%;
height: 100%;
position: relative;
> .title{
line-height: 2rem;
font-size: 1.4rem;
color: #000;
display: flex;
justify-content: space-between;
height: 2rem;
margin-bottom: 2.8rem;
> .titleText{
opacity: .5;
font-weight: 600;
font-family: 'SemiBold';
}
> .icon{
color: #5a5a5a;
}
}
> .version{
font-family: 'SemiBold';
font-weight: 600;
font-size: 1.4rem;
margin-bottom: .2rem;
line-height: 2rem;
}
> .marBott1{
margin-bottom: 1.2rem;
}
> .time{
font-weight: 500;
font-size: 1.2rem;
line-height: 2rem;
}
> .gray{
color: #C1C1C1;
}
}
</style>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import Tree from './tree/index.vue'
import Detail from './detail/index.vue'
const props = defineProps({
versionTreeData:{
type:Object,
@@ -16,6 +17,8 @@ const props = defineProps({
//])
const treeState = ref(true)//
const selectItem = ref({})
const openTree = ()=>{
treeState.value = !treeState.value
@@ -47,16 +50,16 @@ const {} = toRefs(data);
</div>
</div>
</div>
<div style="display: flex;">
<div style="display: flex;" class="expandBtnBox">
<el-button class="expandBtn" @click="openTree" style="width: 5rem;">+</el-button>
<el-button class="expandBtn" @click="openTree" style="width: 5rem;">-</el-button>
</div>
<div class="versionTreeBox">
<div class="tree">
<Tree :treeState="treeState"></Tree>
<Tree :treeState="treeState" v-model:selectItem="selectItem"></Tree>
</div>
<div class="detail">
<Detail v-model:selectItem="selectItem"></Detail>
</div>
</div>
</el-drawer>
@@ -83,17 +86,21 @@ const {} = toRefs(data);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 .8rem 0 1.2rem;
padding: 0 .8rem 0 2.4rem;
border-bottom: 1px solid #C9C9C9;
> span{
font-size: 2rem;
font-weight: 600;
font-family: 'SemiBold';
}
> .closeBtn{
width: 2.4rem;
height: 2.4rem;
cursor: pointer;
}
}
.expandBtnBox{
}
.versionTreeBox{
flex: 1;
@@ -103,14 +110,13 @@ const {} = toRefs(data);
flex: 1;
height: 100%;
overflow: hidden;
padding: 1.8rem 0 1.8rem 2.1rem;
padding: 2.1rem 0 5.4rem 2.2rem;
}
> .detail{
width: 35rem;
margin: 1.4rem 3rem 0 3.4rem;
height: 100%;
overflow: auto;
background-color: #F5F5F5;
margin: 2.1rem 3rem 5.4rem 3.4rem;
height: calc(100% - 2.1rem - 5.4rem);
overflow: hidden;
}
}
}

View File

@@ -8,12 +8,18 @@ const props = defineProps({
treeState:{
default:false,
},
selectItem: {
type: Object,
default: () => ({})
} as any,
})
//const emit = defineEmits([
//])
const emit = defineEmits([
'update:selectItem'
])
let data = reactive({
})
const isLoad = ref(false)
const treeStateTime = ref(true)
watch(()=>props.treeState,(newVal,oldVal)=>{
@@ -28,7 +34,7 @@ const pushView2Item = (item)=>{
view2Ref.value.push(item)
}
const view1List = ref([
const treeList = ref([
{
name:'P1',
},{
@@ -37,9 +43,34 @@ const view1List = ref([
name:'V1-1',
}
])
function traverseArray(items, callback) {
for (let i = 0; i < items.length; i++) {
const item = items[i]
callback(item, i)
if (item.child && Array.isArray(item.child) && item.child.length > 0) {
traverseArray(item.child, callback)
}
}
}
const initialize = ()=>{
isLoad.value = false
setSelectItem(versionsList[0])
treeList.value = []
traverseArray(versionsList, (item, index) => {
treeList.value.push(item)
})
isLoad.value = true
}
const setSelectItem = (item)=>{
console.log(item[0])
emit('update:selectItem', {...item})
}
onMounted(()=>{
// addView2Item()
view2Ref.value.init(versionsList)
initialize()
})
onUnmounted(()=>{
})
@@ -47,12 +78,17 @@ defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="tree" v-show="treeStateTime">
<div class="tree" v-show="treeStateTime" v-if="isLoad">
<div v-show="!treeState" class="box view1">
<view1Item v-for="item in view1List" :key="item.name" :item="item"></view1Item>
<view1Item v-for="item in treeList" :key="item.name" :item="item" @click="emit('selectItem', item)"></view1Item>
</div>
<div v-show="treeState" class="box view2">
<view2 ref="view2Ref"></view2>
<view2
ref="view2Ref"
@setSelectItem="setSelectItem"
:treeList="treeList"
:selectItem="props.selectItem"
></view2>
</div>
</div>
</template>
@@ -67,7 +103,9 @@ const {} = toRefs(data);
box-sizing: border-box;
&.view1{
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
}
&.view2{
border: 1px solid #D9D9D9;

View File

@@ -13,11 +13,9 @@ const props = defineProps<{
</script>
<template>
<div class="node">
<Handle type="target" id="top" :position="Position.Top" />
<Handle type="source" id="bottom" :position="Position.Bottom" />
<Handle type="source" id="right" :position="Position.Right" />
<div>{{ props.data.id }}</div>
<div class="node start">
<Handle type="source" id="Bottom" :position="Position.Bottom" />
<div>index</div>
</div>
</template>

View File

@@ -1,39 +1,40 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, nextTick } from "vue";
import { ref, onMounted, onUnmounted, reactive, nextTick, watch } from "vue";
import type { Node, Edge } from '@vue-flow/core'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import SpecialEdge from './speciaiEdge.vue'
import PrimaryNode from './primaryNode.vue'//主
import InputNode from './InputNode.vue'//主
import SecondaryNode from './secondaryNode.vue'//分支
import { useLayout } from './tools/tools'
const props = defineProps({
item: {
selectItem: {
type: Object,
default: () => ({})
} as any,
treeList: {
type: Array,
default: () => []
}
})
//const emit = defineEmits([
//])
let selectId = ref(2)
const isLoad = ref(false)
const emit = defineEmits([
'setSelectItem',
])
// 节点类型input、output、default、custom
// input:开始点output结尾点default普通节点custom自定义节点
const position = { x: 0, y: 0 }
const nodes = ref<Node[]>([
// { id: '1', type: 'input', label: 'Node 1', class: 'custom-node start', position, sourcePosition: 'bottom' },
// { id: '2', type: 'PrimaryNode', class: 'custom-node', data: { id: '主 1' }, position },
// { id: '2-1', type: 'SecondaryNode', class: 'custom-node', data: { id: '分 1-1' }, position },
// { id: '3', type: 'PrimaryNode', class: 'custom-node', data: { id: '主 2' }, position },
// { id: '1', type: 'input', label: 'Node 1', class: 'custom-node start', position },
// { id: '2', type: 'SecondaryNode', class: 'custom-node', data: { id: '主 1' }, position },
// { id: '2-1', type: 'SecondaryNode', class: 'custom-node', data: { id: '主 2' }, position },
// { id: '3', type: 'SecondaryNode', class: 'custom-node', data: { id: '主 3' }, position },
])
// 边类型custom、default
// custom自定义边default普通边step直角边smoothstep平滑边
const edges = ref<Edge[]>([
// { id: 'e1-2', source: '1', target: '2', type: 'smoothstep' },
// { id: 'e1-3', source: '2', target: '2-1', type: 'smoothstep', sourceHandle:'right',},
// { id: 'e1-4', source: '2', target: '3', type: 'smoothstep',sourceHandle:'bottom', animated: true },
// { id: 'e1-3', source: '2', target: '2-1', type: 'smoothstep', },
// { id: 'e1-4', source: '2', target: '3', type: 'smoothstep', animated: true },
])
const { fitView } = useVueFlow()
const { layout } = useLayout()
@@ -47,37 +48,19 @@ async function layoutGraph(direction) {
}, 0)
}
let elIndex = 1
const push = (item)=>{
if(nodes.value.length == 0){
nodes.value.push({ id: '0', type: 'input', label: 'Node 1', class: 'custom-node start', position, sourcePosition: 'bottom' })
nodes.value.push({ id: '0', type: 'InputNode', class: 'custom-node', position })
}
let className = 'custom-node'
let className = `custom-node item${item.id.replace(/-/g, "_")}`
let id = item.id
let target = edges.value.length == 0?'0':item.id.slice(0, -2)
let source = edges.value.length == 0?'0':item.id.slice(0, -2)
nodes.value.push({id,type:'SecondaryNode',class:className,position,data:item})
edges.value.push({ id, source: id, target, type: 'smoothstep' })
console.log()
edges.value.push({ id, target:id, source, type: 'smoothstep' })
}
function traverseArray(items, callback) {
for (let i = 0; i < items.length; i++) {
const item = items[i]
callback(item, i)
if (item.child && Array.isArray(item.child) && item.child.length > 0) {
traverseArray(item.child, callback)
}
}
}
const init = (list)=>{
isLoad.value = false
traverseArray(list, (item, index) => {
console.log()
push(item)
})
isLoad.value = true
console.log(nodes.value,edges.value)
const initialized = ()=>{
layoutGraph('TB')
}
//是否可拖动节点
@@ -86,29 +69,39 @@ const toggleNodesDraggable = () => {
nodesDraggable.value = !nodesDraggable.value
}
const handleVueFlowNodeClick = (node: Node) => {
console.log(node)
const handleVueFlowNodeClick = ({node}) => {
if(node.data.id)emit('setSelectItem', node.data)
}
watch(()=>props.treeList.length, (newVal, oldVal) => {
nodes.value = []
edges.value = []
props.treeList.forEach(item=>{
push(item)
})
},{immediate:true})
watch(()=>props.selectItem.id, (newVal, oldVal) => {
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({init,push})
defineExpose({push})
// const {} = toRefs(data);
</script>
<template>
<div class="view2">
<div class="vueFlowBox" v-if="isLoad">
<div class="vueFlowBox">
<div @click="toggleNodesDraggable">拖拽节点</div>
<VueFlow :nodes="nodes" @nodes-initialized="layoutGraph('LR')" :edges="edges" @node-click="handleVueFlowNodeClick" :nodes-draggable="nodesDraggable">
<template #node-PrimaryNode="nodeProps">
<PrimaryNode v-bind="nodeProps" />
<VueFlow :nodes="nodes" @nodes-initialized="initialized" :edges="edges" @node-click="handleVueFlowNodeClick" :nodes-draggable="nodesDraggable">
<template #node-InputNode="nodeProps">
<InputNode v-bind="nodeProps" />
</template>
<template #node-SecondaryNode="nodeProps">
<SecondaryNode
v-bind="nodeProps"
:selectId="selectId"
:selectItem="props.selectItem"
/>
</template>
@@ -135,6 +128,7 @@ defineExpose({init,push})
overflow: hidden;
}
:deep(.custom-node){
.node{
--vf-handle: #000;
--vf-node-color: #000;
--vf-box-shadow: #000;
@@ -151,18 +145,6 @@ defineExpose({init,push})
cursor: pointer;
background-color: var(--treeItem-background);
box-sizing: border-box;
.vue-flow__handle-right{
transform: translate(calc(50% + 2px), -50%);
}
.vue-flow__handle-left{
transform: translate(calc(-50% - 2px), -50%);
}
.vue-flow__handle-top{
transform: translate(-50%, calc(-50% - 2px));
}
.vue-flow__handle-bottom{
transform: translate(-50%, calc(50% + 2px));
}
&.active{
background-color: var(--treeItem-active-background);
}
@@ -173,4 +155,5 @@ defineExpose({init,push})
}
}
}
}
</style>

View File

@@ -7,15 +7,21 @@ const props = defineProps<{
default: () => ({
id: '',
})
}
},
selectItem: {
type: Object,
default: () => {},
},
}>()
</script>
<!-- source输入target输出 -->
<template>
<div class="node">
<Handle type="target" id="left" :position="Position.Left" />
<Handle type="source" id="right" :position="Position.Right" />
<div class="node" :class="{active:props.selectItem.id == props.data.id}">
<Handle type="target" id="Top" :position="Position.Top" />
<Handle type="source" id="Bottom" :position="Position.Bottom" />
<!-- <Handle type="target" id="Bottom" :position="Position.Bottom" />
<Handle type="source" id="Top" :position="Position.Top" /> -->
<div>{{ props.data.id }}</div>
</div>
</template>

View File

@@ -14,7 +14,11 @@ export function useLayout() {
const previousDirection = ref('LR')
function layout(nodes, edges, direction) {
function layout(nodes, edges, direction = 'LR') {
// 验证和规范化方向参数
const validDirections = ['TB', 'BT', 'LR', 'RL']
const layoutDirection = validDirections.includes(direction) ? direction : 'LR'
// we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
const dagreGraph = new dagre.graphlib.Graph()
@@ -22,10 +26,11 @@ export function useLayout() {
dagreGraph.setDefaultEdgeLabel(() => ({}))
const isHorizontal = direction === 'LR'
dagreGraph.setGraph({ rankdir: direction })
// 根据方向判断是否为水平布局
const isHorizontal = layoutDirection === 'LR' || layoutDirection === 'RL'
dagreGraph.setGraph({ rankdir: layoutDirection })
previousDirection.value = direction
previousDirection.value = layoutDirection
for (const node of nodes) {
// if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
@@ -44,10 +49,34 @@ export function useLayout() {
return nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id)
// 根据方向动态计算连接点位置
let targetPosition, sourcePosition
switch (layoutDirection) {
case 'BT': // 从上到下 (Top to Bottom)
targetPosition = Position.Bottom // 目标节点连接点在下方
sourcePosition = Position.Top // 源节点连接点在上方
break
case 'TB': // 从下到上 (Bottom to Top)
targetPosition = Position.Top // 目标节点连接点在上方
sourcePosition = Position.Bottom // 源节点连接点在下方
break
case 'LR': // 从左到右 (Left to Right)
targetPosition = Position.Left
sourcePosition = Position.Right
break
case 'RL': // 从右到左 (Right to Left)
targetPosition = Position.Right
sourcePosition = Position.Left
break
default:
targetPosition = Position.Top
sourcePosition = Position.Bottom
}
return {
...node,
targetPosition: isHorizontal ? Position.Left : Position.Top,
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
targetPosition,
sourcePosition,
position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
}
})

View File

@@ -12,7 +12,8 @@ export const versionsList = [
name:'V1-1-1',
}
]
},{
},
{
id: '1-2',
name:'V1-2',
child:[
@@ -24,7 +25,8 @@ export const versionsList = [
name:'V1-2-2',
}
]
},{
},
{
id: '1-2',
name:'V1-2',
child:[
@@ -51,7 +53,8 @@ export const versionsList = [
]
},
]
},{
},
{
id: '1-3',
name:'V1-3',
}