mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-13 10:00:53 +08:00
【新增】私有证书
This commit is contained in:
25
frontend/apps/vue-flow/components/nodes/ApplyNode.tsx
Normal file
25
frontend/apps/vue-flow/components/nodes/ApplyNode.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { Handle, Position } from '@vue-flow/core'
|
||||
import styles from './Node.module.css'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ApplyNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<div class={`${styles.node} ${styles.applyNode}`}>
|
||||
<div class={styles.nodeContent}>
|
||||
<div class={styles.nodeIcon}>📝</div>
|
||||
<div class={styles.nodeLabel}>{props.data.label}</div>
|
||||
</div>
|
||||
<Handle id="target" type="target" position={Position.Top} class={styles.handle} />
|
||||
<Handle id="source" type="source" position={Position.Bottom} class={styles.handle} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,192 @@
|
||||
import { defineComponent, ref, computed } from 'vue'
|
||||
import { Handle, Position, useVueFlow, NodeProps } from '@vue-flow/core'
|
||||
import styles from './Node.module.css'
|
||||
import { NodeData, ICON_MAP, NodeType } from '../../types'
|
||||
|
||||
// 节点类型定义
|
||||
export const NODE_TYPES = {
|
||||
APPLY: 'apply',
|
||||
BRANCH: 'branch',
|
||||
DEPLOY: 'deploy',
|
||||
UPLOAD: 'upload',
|
||||
NOTIFY: 'notify',
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BaseNodeWithAddButton',
|
||||
props: {
|
||||
id: { type: String, required: true },
|
||||
data: { type: Object, required: true },
|
||||
selected: { type: Boolean, default: false },
|
||||
connectable: { type: Boolean, default: true },
|
||||
nodeClassName: { type: String, default: '' },
|
||||
icon: { type: String, default: '' },
|
||||
isClickable: { type: Boolean, default: false },
|
||||
onClick: { type: Function, default: () => {} },
|
||||
},
|
||||
emits: ['add-node'],
|
||||
setup(props, { slots, emit }) {
|
||||
const showMenu = ref(false)
|
||||
const vueFlowInstance = useVueFlow()
|
||||
|
||||
// 获取节点图标
|
||||
const nodeIcon = computed(() => {
|
||||
const nodeType = props.data.type as NodeType
|
||||
return props.icon || props.data.icon || ICON_MAP[nodeType] || '📄'
|
||||
})
|
||||
|
||||
// 处理添加按钮点击
|
||||
const handleAddClick = (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
showMenu.value = !showMenu.value
|
||||
}
|
||||
|
||||
// 处理节点点击
|
||||
const handleNodeClick = (e: MouseEvent) => {
|
||||
if (props.isClickable) {
|
||||
e.stopPropagation()
|
||||
props.onClick(props.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加新节点
|
||||
const addNewNode = (type: string) => {
|
||||
// 获取当前节点信息
|
||||
const currentNode = vueFlowInstance.findNode(props.id)
|
||||
if (!currentNode) return
|
||||
|
||||
// 创建新节点ID
|
||||
const newNodeId = `${type}-${Date.now()}`
|
||||
|
||||
// 根据不同类型节点设置不同图标和标签
|
||||
let nodeLabel = '新节点'
|
||||
|
||||
switch (type) {
|
||||
case NODE_TYPES.APPLY:
|
||||
nodeLabel = '申请证书'
|
||||
break
|
||||
case NODE_TYPES.BRANCH:
|
||||
nodeLabel = '分支节点'
|
||||
break
|
||||
case NODE_TYPES.DEPLOY:
|
||||
nodeLabel = '部署证书'
|
||||
break
|
||||
case NODE_TYPES.UPLOAD:
|
||||
nodeLabel = '上传证书'
|
||||
break
|
||||
case NODE_TYPES.NOTIFY:
|
||||
nodeLabel = '通知'
|
||||
break
|
||||
}
|
||||
|
||||
// 计算新节点位置
|
||||
const nodeHeight = 120 // 节点固定高度
|
||||
const verticalGap = 150 // 节点之间的垂直间距
|
||||
const newY = currentNode.position.y + nodeHeight + verticalGap
|
||||
|
||||
// 创建新节点
|
||||
const newNode = {
|
||||
id: newNodeId,
|
||||
type: type,
|
||||
position: {
|
||||
x: currentNode.position.x,
|
||||
y: newY,
|
||||
},
|
||||
data: {
|
||||
id: newNodeId,
|
||||
type: type as NodeType,
|
||||
label: nodeLabel,
|
||||
icon: ICON_MAP[type as NodeType],
|
||||
canMove: false,
|
||||
canDelete: true,
|
||||
canChangeType: true,
|
||||
},
|
||||
}
|
||||
|
||||
// 创建连接边
|
||||
const newEdge = {
|
||||
id: `${props.id}-${newNodeId}`,
|
||||
source: props.id,
|
||||
target: newNodeId,
|
||||
type: 'smoothstep',
|
||||
animated: true,
|
||||
style: { strokeWidth: 2 },
|
||||
sourceHandle: 'bottom',
|
||||
targetHandle: 'top',
|
||||
}
|
||||
|
||||
// 添加节点和边
|
||||
vueFlowInstance.addNodes([newNode])
|
||||
vueFlowInstance.addEdges([newEdge])
|
||||
|
||||
// 关闭菜单
|
||||
showMenu.value = false
|
||||
|
||||
// 触发添加节点事件
|
||||
emit('add-node', { nodeId: newNodeId, nodeType: type })
|
||||
}
|
||||
|
||||
// 处理点击外部关闭菜单
|
||||
const handleOutsideClick = () => {
|
||||
showMenu.value = false
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div
|
||||
class={`
|
||||
${styles.node}
|
||||
${props.nodeClassName}
|
||||
`}
|
||||
>
|
||||
<div
|
||||
class={`
|
||||
${styles.nodeBody}
|
||||
${props.selected ? styles.nodeSelected : ''}
|
||||
${props.isClickable ? styles.nodeClickable : ''}
|
||||
`}
|
||||
onClick={handleNodeClick}
|
||||
>
|
||||
<div class={styles.nodeHeader}>
|
||||
<span class={styles.nodeIcon}>{nodeIcon.value}</span>
|
||||
<span class={styles.nodeLabel}>{props.data.label}</span>
|
||||
</div>
|
||||
|
||||
<div class={styles.nodeBody}>{slots.default && slots.default()}</div>
|
||||
|
||||
{/* 添加节点按钮 */}
|
||||
<div class={styles.addNodeBtn} onClick={handleAddClick}>
|
||||
+
|
||||
</div>
|
||||
|
||||
{/* 菜单选项 - 使用 v-show 控制显示 */}
|
||||
<div class={styles.nodeMenu} style={{ display: showMenu.value ? 'flex' : 'none', zIndex: 99999 }}>
|
||||
<div class={styles.menuItem} onClick={() => addNewNode(NODE_TYPES.APPLY)}>
|
||||
<span class={styles.menuItemIcon}>{ICON_MAP['apply' as NodeType]}</span>
|
||||
<span class={styles.menuItemLabel}>申请证书节点</span>
|
||||
</div>
|
||||
<div class={styles.menuItem} onClick={() => addNewNode(NODE_TYPES.DEPLOY)}>
|
||||
<span class={styles.menuItemIcon}>{ICON_MAP['deploy' as NodeType]}</span>
|
||||
<span class={styles.menuItemLabel}>部署证书节点</span>
|
||||
</div>
|
||||
<div class={styles.menuItem} onClick={() => addNewNode(NODE_TYPES.UPLOAD)}>
|
||||
<span class={styles.menuItemIcon}>{ICON_MAP['upload' as NodeType]}</span>
|
||||
<span class={styles.menuItemLabel}>上传证书节点</span>
|
||||
</div>
|
||||
<div class={styles.menuItem} onClick={() => addNewNode(NODE_TYPES.NOTIFY)}>
|
||||
<span class={styles.menuItemIcon}>{ICON_MAP['notify' as NodeType]}</span>
|
||||
<span class={styles.menuItemLabel}>通知节点</span>
|
||||
</div>
|
||||
<div class={styles.menuItem} onClick={() => addNewNode(NODE_TYPES.BRANCH)}>
|
||||
<span class={styles.menuItemIcon}>{ICON_MAP['branch' as NodeType]}</span>
|
||||
<span class={styles.menuItemLabel}>分支节点</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 连接点 */}
|
||||
<Handle id="target" type="target" position={Position.Top} class={styles.handle} />
|
||||
<Handle id="bottom" type="source" position={Position.Bottom} class={styles.handle} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
25
frontend/apps/vue-flow/components/nodes/DeployNode.tsx
Normal file
25
frontend/apps/vue-flow/components/nodes/DeployNode.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { Handle, Position } from '@vue-flow/core'
|
||||
import styles from './Node.module.css'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DeployNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<div class={`${styles.node} ${styles.deployNode}`}>
|
||||
<div class={styles.nodeContent}>
|
||||
<div class={styles.nodeIcon}>🚀</div>
|
||||
<div class={styles.nodeLabel}>{props.data.label}</div>
|
||||
</div>
|
||||
<Handle id="target" type="target" position={Position.Top} class={styles.handle} />
|
||||
<Handle id="source" type="source" position={Position.Bottom} class={styles.handle} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
25
frontend/apps/vue-flow/components/nodes/EndNode.tsx
Normal file
25
frontend/apps/vue-flow/components/nodes/EndNode.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { Handle, Position } from '@vue-flow/core'
|
||||
import styles from './Node.module.css'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EndNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<div class={`${styles.node} ${styles.endNode}`}>
|
||||
<div class={styles.nodeContent}>
|
||||
<div class={styles.nodeIcon}>🏁</div>
|
||||
<div class={styles.nodeLabel}>{props.data.label}</div>
|
||||
</div>
|
||||
{/* 只有入口,没有出口 */}
|
||||
<Handle id="target" type="target" position={Position.Top} class={styles.handle} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
55
frontend/apps/vue-flow/components/nodes/NewApplyNode.tsx
Normal file
55
frontend/apps/vue-flow/components/nodes/NewApplyNode.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import BaseNodeWithAddButton from './BaseNodeWithAddButton'
|
||||
import styles from './Node.module.css'
|
||||
import { NodeProps } from '@vue-flow/core'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NewApplyNode',
|
||||
props: {
|
||||
id: { type: String, required: true },
|
||||
data: { type: Object, required: true },
|
||||
selected: { type: Boolean, default: false },
|
||||
connectable: { type: Boolean, default: true },
|
||||
},
|
||||
setup(props) {
|
||||
const handleNodeClick = (nodeData: any) => {
|
||||
// 在这里可以打开节点配置面板
|
||||
console.log('配置申请证书节点', nodeData)
|
||||
// 触发事件通知父组件打开配置面板
|
||||
const event = new CustomEvent('open-node-config', {
|
||||
detail: {
|
||||
nodeId: nodeData.id,
|
||||
nodeType: 'apply',
|
||||
nodeData: nodeData,
|
||||
},
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
|
||||
const handleAddNode = (data: any) => {
|
||||
console.log('添加节点成功', data)
|
||||
}
|
||||
|
||||
return () => (
|
||||
<BaseNodeWithAddButton
|
||||
id={props.id}
|
||||
data={props.data}
|
||||
selected={props.selected}
|
||||
connectable={props.connectable}
|
||||
nodeClassName={styles.applyNode}
|
||||
icon="📝"
|
||||
isClickable={true}
|
||||
onClick={handleNodeClick}
|
||||
onAdd-node={handleAddNode}
|
||||
>
|
||||
{/* 节点内容部分可以在这里添加 */}
|
||||
{props.data.applicationContent && (
|
||||
<div class={styles.nodeMessage}>
|
||||
{props.data.applicationContent.substring(0, 20)}
|
||||
{props.data.applicationContent.length > 20 ? '...' : ''}
|
||||
</div>
|
||||
)}
|
||||
</BaseNodeWithAddButton>
|
||||
)
|
||||
},
|
||||
})
|
||||
50
frontend/apps/vue-flow/components/nodes/NewBranchNode.tsx
Normal file
50
frontend/apps/vue-flow/components/nodes/NewBranchNode.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import BaseNodeWithAddButton from './BaseNodeWithAddButton'
|
||||
import styles from './Node.module.css'
|
||||
import { NodeProps } from '@vue-flow/core'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NewBranchNode',
|
||||
props: {
|
||||
id: { type: String, required: true },
|
||||
data: { type: Object, required: true },
|
||||
selected: { type: Boolean, default: false },
|
||||
connectable: { type: Boolean, default: true },
|
||||
},
|
||||
setup(props) {
|
||||
const handleNodeClick = (nodeData: any) => {
|
||||
// 在这里可以打开节点配置面板
|
||||
console.log('配置分支节点', nodeData)
|
||||
// 触发事件通知父组件打开配置面板
|
||||
const event = new CustomEvent('open-node-config', {
|
||||
detail: {
|
||||
nodeId: nodeData.id,
|
||||
nodeType: 'branch',
|
||||
nodeData: nodeData,
|
||||
},
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
|
||||
const handleAddNode = (data: any) => {
|
||||
console.log('添加节点成功', data)
|
||||
}
|
||||
|
||||
return () => (
|
||||
<BaseNodeWithAddButton
|
||||
id={props.id}
|
||||
data={props.data}
|
||||
selected={props.selected}
|
||||
connectable={props.connectable}
|
||||
nodeClassName={styles.branchNode}
|
||||
icon="🔀"
|
||||
isClickable={true}
|
||||
onClick={handleNodeClick}
|
||||
onAdd-node={handleAddNode}
|
||||
>
|
||||
{/* 分支节点可以显示分支数量 */}
|
||||
{props.data.branchCount && <div class={styles.nodeMessage}>分支数: {props.data.branchCount}</div>}
|
||||
</BaseNodeWithAddButton>
|
||||
)
|
||||
},
|
||||
})
|
||||
50
frontend/apps/vue-flow/components/nodes/NewDeployNode.tsx
Normal file
50
frontend/apps/vue-flow/components/nodes/NewDeployNode.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import BaseNodeWithAddButton from './BaseNodeWithAddButton'
|
||||
import styles from './Node.module.css'
|
||||
import { NodeProps } from '@vue-flow/core'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NewDeployNode',
|
||||
props: {
|
||||
id: { type: String, required: true },
|
||||
data: { type: Object, required: true },
|
||||
selected: { type: Boolean, default: false },
|
||||
connectable: { type: Boolean, default: true },
|
||||
},
|
||||
setup(props) {
|
||||
const handleNodeClick = (nodeData: any) => {
|
||||
// 在这里可以打开节点配置面板
|
||||
console.log('配置部署证书节点', nodeData)
|
||||
// 触发事件通知父组件打开配置面板
|
||||
const event = new CustomEvent('open-node-config', {
|
||||
detail: {
|
||||
nodeId: nodeData.id,
|
||||
nodeType: 'deploy',
|
||||
nodeData: nodeData,
|
||||
},
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
|
||||
const handleAddNode = (data: any) => {
|
||||
console.log('添加节点成功', data)
|
||||
}
|
||||
|
||||
return () => (
|
||||
<BaseNodeWithAddButton
|
||||
id={props.id}
|
||||
data={props.data}
|
||||
selected={props.selected}
|
||||
connectable={props.connectable}
|
||||
nodeClassName={styles.deployNode}
|
||||
icon="🚀"
|
||||
isClickable={true}
|
||||
onClick={handleNodeClick}
|
||||
onAdd-node={handleAddNode}
|
||||
>
|
||||
{/* 部署节点可以显示部署目标 */}
|
||||
{props.data.deployTarget && <div class={styles.nodeMessage}>部署到: {props.data.deployTarget}</div>}
|
||||
</BaseNodeWithAddButton>
|
||||
)
|
||||
},
|
||||
})
|
||||
70
frontend/apps/vue-flow/components/nodes/NewNotifyNode.tsx
Normal file
70
frontend/apps/vue-flow/components/nodes/NewNotifyNode.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import BaseNodeWithAddButton from './BaseNodeWithAddButton'
|
||||
import styles from './Node.module.css'
|
||||
import { NodeProps } from '@vue-flow/core'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NewNotifyNode',
|
||||
props: {
|
||||
id: { type: String, required: true },
|
||||
data: { type: Object, required: true },
|
||||
selected: { type: Boolean, default: false },
|
||||
connectable: { type: Boolean, default: true },
|
||||
},
|
||||
setup(props) {
|
||||
const handleNodeClick = (nodeData: any) => {
|
||||
// 在这里可以打开节点配置面板
|
||||
console.log('配置通知节点', nodeData)
|
||||
// 触发事件通知父组件打开配置面板
|
||||
const event = new CustomEvent('open-node-config', {
|
||||
detail: {
|
||||
nodeId: nodeData.id,
|
||||
nodeType: 'notify',
|
||||
nodeData: nodeData,
|
||||
},
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
|
||||
const handleAddNode = (data: any) => {
|
||||
console.log('添加节点成功', data)
|
||||
}
|
||||
|
||||
// 获取通知类型显示文本
|
||||
const getNotifyTypeText = () => {
|
||||
switch (props.data.notifyType) {
|
||||
case 'email':
|
||||
return '邮件通知'
|
||||
case 'sms':
|
||||
return '短信通知'
|
||||
case 'webhook':
|
||||
return 'Webhook通知'
|
||||
default:
|
||||
return props.data.notifyType || '默认通知'
|
||||
}
|
||||
}
|
||||
|
||||
return () => (
|
||||
<BaseNodeWithAddButton
|
||||
id={props.id}
|
||||
data={props.data}
|
||||
selected={props.selected}
|
||||
connectable={props.connectable}
|
||||
nodeClassName={styles.notifyNode}
|
||||
icon="📣"
|
||||
isClickable={true}
|
||||
onClick={handleNodeClick}
|
||||
onAdd-node={handleAddNode}
|
||||
>
|
||||
{/* 通知节点可以显示通知类型和内容 */}
|
||||
{props.data.notifyType && <div class={styles.nodeMessage}>类型: {getNotifyTypeText()}</div>}
|
||||
{props.data.message && (
|
||||
<div class={styles.nodeMessage}>
|
||||
{props.data.message.substring(0, 20)}
|
||||
{props.data.message.length > 20 ? '...' : ''}
|
||||
</div>
|
||||
)}
|
||||
</BaseNodeWithAddButton>
|
||||
)
|
||||
},
|
||||
})
|
||||
34
frontend/apps/vue-flow/components/nodes/NewStartNode.tsx
Normal file
34
frontend/apps/vue-flow/components/nodes/NewStartNode.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { NodeProps } from '@vue-flow/core'
|
||||
import styles from './Node.module.css'
|
||||
import BaseNodeWithAddButton from './BaseNodeWithAddButton'
|
||||
import { NodeType } from '../../types'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NewStartNode',
|
||||
props: {
|
||||
id: { type: String, required: true },
|
||||
data: { type: Object, required: true },
|
||||
selected: { type: Boolean, default: false },
|
||||
connectable: { type: Boolean, default: true },
|
||||
},
|
||||
emits: ['add-node'],
|
||||
setup(props, { emit }) {
|
||||
// 处理添加节点事件
|
||||
const handleAddNode = (nodeData: any) => {
|
||||
console.log('添加节点成功:', nodeData)
|
||||
emit('add-node', nodeData)
|
||||
}
|
||||
|
||||
return () => (
|
||||
<BaseNodeWithAddButton
|
||||
id={props.id}
|
||||
data={props.data}
|
||||
selected={props.selected}
|
||||
connectable={props.connectable}
|
||||
nodeClassName={styles.startNode}
|
||||
onAdd-node={handleAddNode}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
55
frontend/apps/vue-flow/components/nodes/NewUploadNode.tsx
Normal file
55
frontend/apps/vue-flow/components/nodes/NewUploadNode.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import BaseNodeWithAddButton from './BaseNodeWithAddButton'
|
||||
import styles from './Node.module.css'
|
||||
import { NodeProps } from '@vue-flow/core'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NewUploadNode',
|
||||
props: {
|
||||
id: { type: String, required: true },
|
||||
data: { type: Object, required: true },
|
||||
selected: { type: Boolean, default: false },
|
||||
connectable: { type: Boolean, default: true },
|
||||
},
|
||||
setup(props) {
|
||||
const handleNodeClick = (nodeData: any) => {
|
||||
// 在这里可以打开节点配置面板
|
||||
console.log('配置上传证书节点', nodeData)
|
||||
// 触发事件通知父组件打开配置面板
|
||||
const event = new CustomEvent('open-node-config', {
|
||||
detail: {
|
||||
nodeId: nodeData.id,
|
||||
nodeType: 'upload',
|
||||
nodeData: nodeData,
|
||||
},
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
|
||||
const handleAddNode = (data: any) => {
|
||||
console.log('添加节点成功', data)
|
||||
}
|
||||
|
||||
return () => (
|
||||
<BaseNodeWithAddButton
|
||||
id={props.id}
|
||||
data={props.data}
|
||||
selected={props.selected}
|
||||
connectable={props.connectable}
|
||||
nodeClassName={styles.uploadNode}
|
||||
icon="📤"
|
||||
isClickable={true}
|
||||
onClick={handleNodeClick}
|
||||
onAdd-node={handleAddNode}
|
||||
>
|
||||
{/* 上传节点可以显示文件状态 */}
|
||||
{props.data.certificateFile && (
|
||||
<div class={styles.nodeMessage}>已上传: {props.data.certificateFile.name || '证书文件'}</div>
|
||||
)}
|
||||
{!props.data.certificateFile && props.data.certificateContent && (
|
||||
<div class={styles.nodeMessage}>已填写证书内容</div>
|
||||
)}
|
||||
</BaseNodeWithAddButton>
|
||||
)
|
||||
},
|
||||
})
|
||||
230
frontend/apps/vue-flow/components/nodes/Node.module.css
Normal file
230
frontend/apps/vue-flow/components/nodes/Node.module.css
Normal file
@@ -0,0 +1,230 @@
|
||||
.node {
|
||||
border-radius: 5px;
|
||||
width: 240px;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 120px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.nodeHeader {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 10px;
|
||||
color: white;
|
||||
box-sizing: border-box;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.nodeIcon {
|
||||
font-size: 20px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.nodeLabel {
|
||||
font-weight: 500;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nodeBody {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
color: #5a5e66;
|
||||
box-sizing: border-box;
|
||||
min-height: 60px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nodeMessage {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-top: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.handle {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: #64748b;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.handle-top {
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.handle-bottom {
|
||||
bottom: -5px;
|
||||
}
|
||||
|
||||
/* 添加节点按钮 */
|
||||
.addNodeBtn {
|
||||
position: absolute;
|
||||
bottom: -15px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: #fff;
|
||||
border: 2px solid #1e83e9;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
color: #1e83e9;
|
||||
font-size: 18px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.addNodeBtn:hover {
|
||||
background-color: #1e83e9;
|
||||
color: white;
|
||||
transform: translateX(-50%) scale(1.1);
|
||||
}
|
||||
|
||||
/* 添加节点选项菜单 */
|
||||
.nodeMenu {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 180px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menuItem {
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.menuItem:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.menuItemIcon {
|
||||
font-size: 16px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menuItemLabel {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 节点交互样式 */
|
||||
.nodeClickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nodeClickable:hover::after {
|
||||
content: '点击配置';
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 节点类型样式 */
|
||||
.startNode .nodeHeader {
|
||||
background-color: #1e83e9;
|
||||
}
|
||||
|
||||
.endNode .nodeHeader {
|
||||
background-color: #7855ce;
|
||||
}
|
||||
|
||||
.uploadNode .nodeHeader {
|
||||
background-color: #10b981;
|
||||
}
|
||||
|
||||
.deployNode .nodeHeader {
|
||||
background-color: #f97316;
|
||||
}
|
||||
|
||||
.notifyNode .nodeHeader {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
|
||||
.applyNode .nodeHeader {
|
||||
background-color: #6366f1;
|
||||
}
|
||||
|
||||
.normalNode .nodeHeader {
|
||||
background-color: #64748b;
|
||||
}
|
||||
|
||||
.branchNode .nodeHeader {
|
||||
background-color: #597ef7;
|
||||
}
|
||||
|
||||
.nodeStatus {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.statusItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.statusLabel {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.statusDot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.handleSuccess {
|
||||
background-color: #10b981;
|
||||
left: 20%;
|
||||
}
|
||||
|
||||
.handleFailure {
|
||||
background-color: #ef4444;
|
||||
left: 80%;
|
||||
}
|
||||
38
frontend/apps/vue-flow/components/nodes/NormalNode.tsx
Normal file
38
frontend/apps/vue-flow/components/nodes/NormalNode.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { Handle, Position } from '@vue-flow/core'
|
||||
import styles from './Node.module.css'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NormalNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
// 根据状态获取图标
|
||||
const getStatusIcon = () => {
|
||||
switch (props.data.status) {
|
||||
case 'success':
|
||||
return '✅'
|
||||
case 'error':
|
||||
return '❌'
|
||||
default:
|
||||
return 'ℹ️'
|
||||
}
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div class={`${styles.node} ${styles.normalNode}`}>
|
||||
<div class={styles.nodeContent}>
|
||||
<div class={styles.nodeIcon}>{getStatusIcon()}</div>
|
||||
<div class={styles.nodeLabel}>{props.data.label}</div>
|
||||
{props.data.message && <div class={styles.nodeMessage}>{props.data.message}</div>}
|
||||
</div>
|
||||
<Handle id="target" type="target" position={Position.Top} class={styles.handle} />
|
||||
<Handle id="source" type="source" position={Position.Bottom} class={styles.handle} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
47
frontend/apps/vue-flow/components/nodes/NotifyNode.tsx
Normal file
47
frontend/apps/vue-flow/components/nodes/NotifyNode.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { Handle, Position } from '@vue-flow/core'
|
||||
import styles from './Node.module.css'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NotifyNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<div class={`${styles.node} ${styles.notifyNode}`}>
|
||||
<div class={styles.nodeContent}>
|
||||
<div class={styles.nodeIcon}>📢</div>
|
||||
<div class={styles.nodeLabel}>{props.data.label}</div>
|
||||
<div class={styles.nodeStatus}>
|
||||
<div class={styles.statusItem}>
|
||||
<div class={styles.statusLabel}>成功</div>
|
||||
<div class={styles.statusDot} style={{ backgroundColor: '#10b981' }}></div>
|
||||
</div>
|
||||
<div class={styles.statusItem}>
|
||||
<div class={styles.statusLabel}>失败</div>
|
||||
<div class={styles.statusDot} style={{ backgroundColor: '#ef4444' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Handle id="target" type="target" position={Position.Top} class={styles.handle} />
|
||||
<Handle
|
||||
id="source-success"
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
class={`${styles.handle} ${styles.handleSuccess}`}
|
||||
/>
|
||||
<Handle
|
||||
id="source-failure"
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
class={`${styles.handle} ${styles.handleFailure}`}
|
||||
style={{ left: '80%' }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
25
frontend/apps/vue-flow/components/nodes/StartNode.tsx
Normal file
25
frontend/apps/vue-flow/components/nodes/StartNode.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { Handle, Position } from '@vue-flow/core'
|
||||
import styles from './Node.module.css'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'StartNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<div class={`${styles.node} ${styles.startNode}`}>
|
||||
<div class={styles.nodeContent}>
|
||||
<div class={styles.nodeIcon}>🏁</div>
|
||||
<div class={styles.nodeLabel}>{props.data.label}</div>
|
||||
</div>
|
||||
{/* 只有出口,没有入口 */}
|
||||
<Handle id="source" type="source" position={Position.Bottom} class={styles.handle} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
24
frontend/apps/vue-flow/components/nodes/UploadNode.tsx
Normal file
24
frontend/apps/vue-flow/components/nodes/UploadNode.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Handle, Position } from '@vue-flow/core'
|
||||
import styles from './Node.module.css'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UploadNode',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<div class={`${styles.node} ${styles.uploadNode}`}>
|
||||
<div class={styles.nodeContent}>
|
||||
<div class={styles.nodeIcon}>📄</div>
|
||||
<div class={styles.nodeLabel}>{props.data.label}</div>
|
||||
</div>
|
||||
<Handle id="target" type="target" position={Position.Top} class={styles.handle} />
|
||||
<Handle id="source" type="source" position={Position.Bottom} class={styles.handle} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user