mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2025-08-23 08:04:50 +00:00
支持图层处理
This commit is contained in:
@@ -79,7 +79,8 @@ const handleComponentClick = (component) => {
|
|||||||
type: component.type,
|
type: component.type,
|
||||||
label: component.name,
|
label: component.name,
|
||||||
position: { x: 100, y: 100 }, // 默认位置
|
position: { x: 100, y: 100 }, // 默认位置
|
||||||
data: { componentType: component.type }
|
data: { componentType: component.type },
|
||||||
|
style: { background: '#fff', border: '2px solid black',width: '150px', height: '150px' },
|
||||||
};
|
};
|
||||||
|
|
||||||
// 发出添加节点事件
|
// 发出添加节点事件
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, shallowRef, markRaw } from 'vue';
|
import { ref, onMounted, shallowRef, markRaw, onUnmounted } from 'vue';
|
||||||
import { VueFlow, useVueFlow, Panel, NodeTypes } from '@vue-flow/core';
|
import { VueFlow, useVueFlow, Panel, NodeTypes } from '@vue-flow/core';
|
||||||
import { Background } from '@vue-flow/background';
|
import { Background } from '@vue-flow/background';
|
||||||
import { Controls } from '@vue-flow/controls';
|
import { Controls } from '@vue-flow/controls';
|
||||||
@@ -47,34 +47,42 @@ const { nodes, edges, onNodesChange, onEdgesChange, onConnect, addNodes, setTran
|
|||||||
nodeTypes
|
nodeTypes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 右键菜单相关
|
||||||
|
const contextMenu = ref({
|
||||||
|
show: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
nodeId: null
|
||||||
|
});
|
||||||
|
|
||||||
// 处理拖拽放置
|
// 处理拖拽放置
|
||||||
const handleDrop = (event) => {
|
const handleDrop = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取拖拽数据
|
// 获取拖拽数据
|
||||||
const nodeData = JSON.parse(event.dataTransfer.getData('application/json'));
|
const nodeData = JSON.parse(event.dataTransfer.getData('application/json'));
|
||||||
|
|
||||||
// 获取画布元素
|
// 获取画布元素
|
||||||
const flowContainer = event.currentTarget;
|
const flowContainer = event.currentTarget;
|
||||||
const bounds = flowContainer.getBoundingClientRect();
|
const bounds = flowContainer.getBoundingClientRect();
|
||||||
|
|
||||||
// 获取画布的缩放和偏移信息
|
// 获取画布的缩放和偏移信息
|
||||||
const { x: viewportX, y: viewportY, zoom } = getViewport();
|
const { x: viewportX, y: viewportY, zoom } = getViewport();
|
||||||
|
|
||||||
// 计算相对于画布的位置,并考虑缩放和偏移
|
// 计算相对于画布的位置,并考虑缩放和偏移
|
||||||
const position = {
|
const position = {
|
||||||
x: (event.clientX - bounds.left - viewportX) / zoom,
|
x: (event.clientX - bounds.left - viewportX) / zoom,
|
||||||
y: (event.clientY - bounds.top - viewportY) / zoom
|
y: (event.clientY - bounds.top - viewportY) / zoom
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建新节点
|
// 创建新节点
|
||||||
const newNode = {
|
const newNode = {
|
||||||
...nodeData,
|
...nodeData,
|
||||||
position,
|
position,
|
||||||
selected: true // 设置节点为选中状态
|
selected: true // 设置节点为选中状态
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加节点
|
// 添加节点
|
||||||
handleAddNode(newNode);
|
handleAddNode(newNode);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -96,7 +104,7 @@ const handleAddNode = (newNode) => {
|
|||||||
updateNode(node.id, { selected: false });
|
updateNode(node.id, { selected: false });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 根据节点类型设置初始数据
|
// 根据节点类型设置初始数据
|
||||||
let initialData = {};
|
let initialData = {};
|
||||||
switch (newNode.type) {
|
switch (newNode.type) {
|
||||||
@@ -149,7 +157,7 @@ const handleAddNode = (newNode) => {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加新节点,并设置为选中状态
|
// 添加新节点,并设置为选中状态
|
||||||
addNodes([{
|
addNodes([{
|
||||||
...newNode,
|
...newNode,
|
||||||
@@ -159,7 +167,7 @@ const handleAddNode = (newNode) => {
|
|||||||
...initialData
|
...initialData
|
||||||
}
|
}
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
// 重新设置视图,使新节点可见
|
// 重新设置视图,使新节点可见
|
||||||
setTransform({ x: 0, y: 0, zoom: 1 });
|
setTransform({ x: 0, y: 0, zoom: 1 });
|
||||||
};
|
};
|
||||||
@@ -179,8 +187,84 @@ const handleOpenPropertySelect = (node) => {
|
|||||||
emit('open-property-select', node);
|
emit('open-property-select', node);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理节点右键点击
|
||||||
|
const handleNodeContextMenu = (event) => {
|
||||||
|
const { event: mouseEvent, node } = event;
|
||||||
|
mouseEvent.preventDefault();
|
||||||
|
mouseEvent.stopPropagation();
|
||||||
|
|
||||||
|
contextMenu.value = {
|
||||||
|
show: true,
|
||||||
|
x: mouseEvent.clientX,
|
||||||
|
y: mouseEvent.clientY,
|
||||||
|
nodeId: node.id
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理画布右键点击
|
||||||
|
const handlePaneContextMenu = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
contextMenu.value.show = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击其他地方时关闭菜单
|
||||||
|
const handleClickOutside = (event) => {
|
||||||
|
if (!event.target.closest('.context-menu')) {
|
||||||
|
contextMenu.value.show = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理图层顺序调整
|
||||||
|
const handleLayerOrder = (action) => {
|
||||||
|
if (!contextMenu.value.nodeId) return;
|
||||||
|
|
||||||
|
const nodeId = contextMenu.value.nodeId;
|
||||||
|
const nodeIndex = nodes.value.findIndex(n => n.id === nodeId);
|
||||||
|
if (nodeIndex === -1) return;
|
||||||
|
|
||||||
|
const node = nodes.value[nodeIndex];
|
||||||
|
const newNodes = [...nodes.value];
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'bringToFront':
|
||||||
|
// 移至最前
|
||||||
|
newNodes.splice(nodeIndex, 1);
|
||||||
|
newNodes.push(node);
|
||||||
|
break;
|
||||||
|
case 'sendToBack':
|
||||||
|
// 移至最后
|
||||||
|
newNodes.splice(nodeIndex, 1);
|
||||||
|
newNodes.unshift(node);
|
||||||
|
break;
|
||||||
|
case 'bringForward':
|
||||||
|
// 上移一层
|
||||||
|
if (nodeIndex < newNodes.length - 1) {
|
||||||
|
[newNodes[nodeIndex], newNodes[nodeIndex + 1]] = [newNodes[nodeIndex + 1], newNodes[nodeIndex]];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'sendBackward':
|
||||||
|
// 下移一层
|
||||||
|
if (nodeIndex > 0) {
|
||||||
|
[newNodes[nodeIndex], newNodes[nodeIndex - 1]] = [newNodes[nodeIndex - 1], newNodes[nodeIndex]];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新节点顺序
|
||||||
|
nodes.value = newNodes;
|
||||||
|
contextMenu.value.show = false;
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('FlowEditor 组件已挂载');
|
console.log('FlowEditor 组件已挂载');
|
||||||
|
// 添加全局点击事件监听
|
||||||
|
document.addEventListener('click', handleClickOutside);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 移除事件监听
|
||||||
|
document.removeEventListener('click', handleClickOutside);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -191,18 +275,20 @@ onMounted(() => {
|
|||||||
<div class="components-sidebar">
|
<div class="components-sidebar">
|
||||||
<ComponentsPanel @add-node="handleAddNode" />
|
<ComponentsPanel @add-node="handleAddNode" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 中间流程图区域 -->
|
<!-- 中间流程图区域 -->
|
||||||
<div class="flow-container">
|
<div class="flow-container">
|
||||||
<VueFlow
|
<VueFlow
|
||||||
:nodes="nodes"
|
:nodes="nodes"
|
||||||
:edges="edges"
|
:edges="edges"
|
||||||
@nodes-change="onNodesChange"
|
@nodes-change="onNodesChange"
|
||||||
@edges-change="onEdgesChange"
|
@edges-change="onEdgesChange"
|
||||||
@connect="onConnect"
|
@connect="onConnect"
|
||||||
fit-view-on-init
|
fit-view-on-init
|
||||||
@drop="handleDrop"
|
@drop="handleDrop"
|
||||||
@dragover="handleDragOver"
|
@dragover="handleDragOver"
|
||||||
|
@node-context-menu="handleNodeContextMenu"
|
||||||
|
@pane-context-menu="handlePaneContextMenu"
|
||||||
>
|
>
|
||||||
<Background pattern-color="#aaa" gap="8" />
|
<Background pattern-color="#aaa" gap="8" />
|
||||||
<Controls />
|
<Controls />
|
||||||
@@ -210,14 +296,27 @@ onMounted(() => {
|
|||||||
<div>流程图编辑器 (模仿 draw.io)</div>
|
<div>流程图编辑器 (模仿 draw.io)</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
</VueFlow>
|
</VueFlow>
|
||||||
|
|
||||||
|
<!-- 右键菜单 -->
|
||||||
|
<Teleport to="body">
|
||||||
|
<div v-if="contextMenu.show"
|
||||||
|
class="context-menu"
|
||||||
|
:style="{ left: contextMenu.x + 'px', top: contextMenu.y + 'px' }"
|
||||||
|
@click.stop>
|
||||||
|
<div class="menu-item" @click="handleLayerOrder('bringToFront')">移至最前</div>
|
||||||
|
<div class="menu-item" @click="handleLayerOrder('sendToBack')">移至最后</div>
|
||||||
|
<div class="menu-item" @click="handleLayerOrder('bringForward')">上移一层</div>
|
||||||
|
<div class="menu-item" @click="handleLayerOrder('sendBackward')">下移一层</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧属性面板 -->
|
<!-- 右侧属性面板 -->
|
||||||
<PropertyPanel
|
<PropertyPanel
|
||||||
:height="height"
|
:height="height"
|
||||||
@open-shikigami-select="handleOpenShikigamiSelect"
|
@open-shikigami-select="handleOpenShikigamiSelect"
|
||||||
@open-yuhun-select="handleOpenYuhunSelect"
|
@open-yuhun-select="handleOpenYuhunSelect"
|
||||||
@open-property-select="handleOpenPropertySelect"
|
@open-property-select="handleOpenPropertySelect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -268,4 +367,29 @@ onMounted(() => {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.context-menu {
|
||||||
|
position: fixed;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 5px 0;
|
||||||
|
z-index: 9999;
|
||||||
|
min-width: 120px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@@ -60,156 +60,71 @@ onUnmounted(() => {
|
|||||||
defineExpose({
|
defineExpose({
|
||||||
updateNodeShikigami
|
updateNodeShikigami
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add Position enum usage to fix type error
|
||||||
|
const position = Position;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NodeResizer
|
<NodeResizer
|
||||||
v-if="selected"
|
v-if="selected"
|
||||||
:min-width="150"
|
|
||||||
:min-height="150"
|
|
||||||
:max-width="300"
|
|
||||||
:max-height="300"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 输入连接点 -->
|
<Handle type="target" :position="position.Left" :id="`${id}-target`" />
|
||||||
<Handle type="target" position="left" :id="`${id}-target`" />
|
|
||||||
|
|
||||||
<div class="node-content">
|
<div class="node-content">
|
||||||
<div class="node-header">
|
<img
|
||||||
<div class="node-title">式神选择</div>
|
v-if="currentShikigami.avatar"
|
||||||
</div>
|
:src="currentShikigami.avatar"
|
||||||
|
:alt="currentShikigami.name"
|
||||||
<div class="node-body">
|
class="shikigami-image"
|
||||||
<div v-if="currentShikigami.avatar" class="shikigami-avatar">
|
/>
|
||||||
<img :src="currentShikigami.avatar" alt="式神头像" />
|
<div v-else class="placeholder-text">点击选择式神</div>
|
||||||
</div>
|
<div class="name-text">{{ currentShikigami.name }}</div>
|
||||||
<div v-else class="shikigami-placeholder">
|
|
||||||
点击选择式神
|
|
||||||
</div>
|
|
||||||
<div class="shikigami-name">{{ currentShikigami.name }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 输出连接点 -->
|
<Handle type="source" :position="position.Right" :id="`${id}-source`" />
|
||||||
<Handle type="source" position="right" :id="`${id}-source`" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.shikigami-node {
|
.node-content {
|
||||||
position: relative;
|
display: flex;
|
||||||
width: 100%;
|
flex-direction: column;
|
||||||
height: 100%;
|
align-items: center;
|
||||||
min-width: 180px;
|
justify-content: center;
|
||||||
min-height: 180px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-content {
|
.shikigami-image {
|
||||||
position: relative;
|
width: 85%;
|
||||||
background-color: white;
|
height: 85%;
|
||||||
border: 1px solid #dcdfe6;
|
object-fit: cover;
|
||||||
border-radius: 4px;
|
}
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
min-width: 180px;
|
.placeholder-text {
|
||||||
min-height: 180px;
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-text {
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer) {
|
:deep(.vue-flow__node-resizer) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer-handle) {
|
:deep(.vue-flow__node-resizer-handle) {
|
||||||
position: absolute;
|
width: 8px;
|
||||||
width: 8px;
|
height: 8px;
|
||||||
height: 8px;
|
background-color: #409EFF;
|
||||||
background-color: #409EFF;
|
border-radius: 50%;
|
||||||
border: 1px solid white;
|
pointer-events: all;
|
||||||
border-radius: 50%;
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
:deep(.vue-flow__node-resizer-handle.top-left) {
|
|
||||||
top: -4px;
|
|
||||||
left: -4px;
|
|
||||||
cursor: nwse-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer-handle.top-right) {
|
|
||||||
top: -4px;
|
|
||||||
right: -4px;
|
|
||||||
cursor: nesw-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer-handle.bottom-left) {
|
|
||||||
bottom: -4px;
|
|
||||||
left: -4px;
|
|
||||||
cursor: nesw-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer-handle.bottom-right) {
|
|
||||||
bottom: -4px;
|
|
||||||
right: -4px;
|
|
||||||
cursor: nwse-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-header {
|
|
||||||
padding: 8px 10px;
|
|
||||||
background-color: #ecf5ff;
|
|
||||||
border-bottom: 1px solid #dcdfe6;
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-title {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-body {
|
|
||||||
padding: 10px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
height: calc(100% - 37px); /* 减去header的高度 */
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shikigami-avatar {
|
|
||||||
width: 80%;
|
|
||||||
height: 80%;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shikigami-avatar img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shikigami-placeholder {
|
|
||||||
width: 80%;
|
|
||||||
height: 80%;
|
|
||||||
border: 1px dashed #c0c4cc;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #909399;
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shikigami-name {
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@@ -84,162 +84,80 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NodeResizer
|
<NodeResizer
|
||||||
v-if="selected"
|
v-if="selected"
|
||||||
:min-width="150"
|
:min-width="150"
|
||||||
:min-height="150"
|
:min-height="150"
|
||||||
:max-width="300"
|
:max-width="300"
|
||||||
:max-height="300"
|
:max-height="300"
|
||||||
/>
|
/>
|
||||||
<div class="yuhun-node" >
|
|
||||||
<!-- 输入连接点 -->
|
<Handle type="target" :position="Position.Left" :id="`${id}-target`" />
|
||||||
<Handle type="target" position="left" :id="`${id}-target`"/>
|
|
||||||
|
|
||||||
<div class="node-content">
|
<div class="node-content">
|
||||||
<div class="node-header">
|
<img
|
||||||
<div class="node-title">御魂选择</div>
|
v-if="currentYuhun.avatar"
|
||||||
</div>
|
:src="currentYuhun.avatar"
|
||||||
|
:alt="currentYuhun.name"
|
||||||
<div class="node-body">
|
class="yuhun-image"
|
||||||
<div v-if="currentYuhun.avatar" class="yuhun-avatar">
|
/>
|
||||||
<img :src="currentYuhun.avatar" alt="御魂图片"/>
|
<div v-else class="placeholder-text">点击选择御魂</div>
|
||||||
</div>
|
<div class="name-text">{{ currentYuhun.name }}</div>
|
||||||
<div v-else class="yuhun-placeholder">
|
<div v-if="currentYuhun.type" class="type-text">{{ currentYuhun.type }}</div>
|
||||||
点击选择御魂
|
|
||||||
</div>
|
|
||||||
<div class="yuhun-name">{{ currentYuhun.name }}</div>
|
|
||||||
<div v-if="currentYuhun.type" class="yuhun-type">{{ currentYuhun.type }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 输出连接点 -->
|
<Handle type="source" :position="Position.Right" :id="`${id}-source`" />
|
||||||
<Handle type="source" position="right" :id="`${id}-source`"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.yuhun-node {
|
.node-content {
|
||||||
position: relative;
|
width: 100%;
|
||||||
width: 100%;
|
height: 100%;
|
||||||
height: 100%;
|
min-width: 180px;
|
||||||
min-width: 180px;
|
min-height: 180px;
|
||||||
min-height: 180px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-content {
|
.yuhun-image {
|
||||||
position: relative;
|
width: 85%;
|
||||||
background-color: white;
|
height: 85%;
|
||||||
border: 1px solid #dcdfe6;
|
object-fit: cover;
|
||||||
border-radius: 4px;
|
}
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
.placeholder-text {
|
||||||
width: 100%;
|
color: #909399;
|
||||||
height: 100%;
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
min-width: 180px;
|
.name-text {
|
||||||
min-height: 180px;
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer) {
|
:deep(.vue-flow__node-resizer) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer-handle) {
|
:deep(.vue-flow__node-resizer-handle) {
|
||||||
position: absolute;
|
width: 8px;
|
||||||
width: 8px;
|
height: 8px;
|
||||||
height: 8px;
|
background-color: #409EFF;
|
||||||
background-color: #409EFF;
|
border-radius: 50%;
|
||||||
border: 1px solid white;
|
pointer-events: all;
|
||||||
border-radius: 50%;
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer-handle.top-left) {
|
|
||||||
top: -4px;
|
|
||||||
left: -4px;
|
|
||||||
cursor: nwse-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer-handle.top-right) {
|
|
||||||
top: -4px;
|
|
||||||
right: -4px;
|
|
||||||
cursor: nesw-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer-handle.bottom-left) {
|
|
||||||
bottom: -4px;
|
|
||||||
left: -4px;
|
|
||||||
cursor: nesw-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.vue-flow__node-resizer-handle.bottom-right) {
|
|
||||||
bottom: -4px;
|
|
||||||
right: -4px;
|
|
||||||
cursor: nwse-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-header {
|
|
||||||
padding: 8px 10px;
|
|
||||||
background-color: #f0f7ff;
|
|
||||||
border-bottom: 1px solid #dcdfe6;
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-title {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-body {
|
|
||||||
padding: 10px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yuhun-avatar {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
transition: width 0.2s, height 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yuhun-avatar img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yuhun-placeholder {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border: 1px dashed #c0c4cc;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #909399;
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
transition: width 0.2s, height 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yuhun-name {
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yuhun-type {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #909399;
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Reference in New Issue
Block a user