- 已选课程支持拖拽调整顺序 - 支持跨阶段拖拽移动课程 - 拖拽时显示视觉反馈(高亮线条) - 拖拽到空阶段时显示占位提示 - 自动更新课程排序编号 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -334,29 +334,44 @@
|
|||||||
:class="{ 'is-dragging-over': isDraggingOver }"
|
:class="{ 'is-dragging-over': isDraggingOver }"
|
||||||
@dragover.prevent="handleDragOver"
|
@dragover.prevent="handleDragOver"
|
||||||
@dragleave="handleDragLeave"
|
@dragleave="handleDragLeave"
|
||||||
@drop="handleDrop"
|
@drop="handleDrop"
|
||||||
>
|
>
|
||||||
<template v-if="editingPath.nodes && editingPath.nodes.length > 0">
|
<template v-if="editingPath.nodes && editingPath.nodes.length > 0">
|
||||||
<div
|
<div
|
||||||
v-for="(stage, stageIndex) in editingPath.stages"
|
v-for="(stage, stageIndex) in editingPath.stages"
|
||||||
:key="stageIndex"
|
:key="stageIndex"
|
||||||
class="stage-section"
|
class="stage-section"
|
||||||
|
:class="{ 'stage-drag-over': dragOverStage === stage.name }"
|
||||||
|
@dragover.prevent="handleStageDragOver($event, stage.name)"
|
||||||
|
@dragleave="handleStageDragLeave($event)"
|
||||||
|
@drop.stop="handleNodeDrop($event, stage.name)"
|
||||||
>
|
>
|
||||||
<div class="stage-header">
|
<div class="stage-header">
|
||||||
<span>{{ stage.name }}</span>
|
<span>{{ stage.name }}</span>
|
||||||
<el-tag size="small" type="info">
|
<el-tag size="small" type="info">
|
||||||
{{ getStageNodes(stage.name).length }} 门
|
{{ getStageNodes(stage.name).length }} 门
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="stage-nodes">
|
<div class="stage-nodes">
|
||||||
<div
|
<div
|
||||||
v-for="(node, nodeIndex) in getStageNodes(stage.name)"
|
v-for="(node, nodeIndex) in getStageNodes(stage.name)"
|
||||||
:key="node.course_id"
|
:key="node.course_id"
|
||||||
class="node-item"
|
class="node-item"
|
||||||
|
:class="{
|
||||||
|
'is-dragging': draggingNode?.course_id === node.course_id,
|
||||||
|
'drag-over-top': dragOverNode?.course_id === node.course_id && dragOverPosition === 'top',
|
||||||
|
'drag-over-bottom': dragOverNode?.course_id === node.course_id && dragOverPosition === 'bottom'
|
||||||
|
}"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="handleNodeDragStart($event, node, stage.name)"
|
||||||
|
@dragend="handleNodeDragEnd"
|
||||||
|
@dragover.prevent="handleNodeDragOver($event, node)"
|
||||||
|
@dragleave="handleNodeDragLeave"
|
||||||
|
@drop.stop="handleNodeDropOnNode($event, node, stage.name)"
|
||||||
>
|
>
|
||||||
<div class="node-drag-handle">
|
<div class="node-drag-handle" @mousedown.stop>
|
||||||
<el-icon><Rank /></el-icon>
|
<el-icon><Rank /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="node-content">
|
<div class="node-content">
|
||||||
<span class="node-title">{{ node.title }}</span>
|
<span class="node-title">{{ node.title }}</span>
|
||||||
<div class="node-meta">
|
<div class="node-meta">
|
||||||
@@ -367,16 +382,17 @@
|
|||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
>
|
>
|
||||||
{{ node.is_required ? '必修' : '选修' }}
|
{{ node.is_required ? '必修' : '选修' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<span>{{ node.estimated_days || 1 }}天</span>
|
<span>{{ node.estimated_days || 1 }}天</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="node-actions">
|
<div class="node-actions">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="node.stage_name"
|
v-model="node.stage_name"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 90px"
|
style="width: 90px"
|
||||||
placeholder="阶段"
|
placeholder="阶段"
|
||||||
|
@change="handleNodeStageChange(node)"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="s in editingPath.stages"
|
v-for="s in editingPath.stages"
|
||||||
@@ -393,16 +409,18 @@
|
|||||||
>
|
>
|
||||||
<el-icon><Delete /></el-icon>
|
<el-icon><Delete /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="getStageNodes(stage.name).length === 0"
|
v-if="getStageNodes(stage.name).length === 0"
|
||||||
class="stage-empty"
|
class="stage-empty"
|
||||||
|
:class="{ 'stage-empty-active': dragOverStage === stage.name }"
|
||||||
>
|
>
|
||||||
拖拽或点击课程添加到此阶段
|
<el-icon><Plus /></el-icon>
|
||||||
</div>
|
拖拽课程到此阶段
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-empty v-else description="请从左侧添加课程" :image-size="60">
|
<el-empty v-else description="请从左侧添加课程" :image-size="60">
|
||||||
<template #description>
|
<template #description>
|
||||||
@@ -494,6 +512,13 @@ const courseCategory = ref('')
|
|||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
const isDraggingOver = ref(false)
|
const isDraggingOver = ref(false)
|
||||||
|
|
||||||
|
// 节点拖拽排序状态
|
||||||
|
const draggingNode = ref<CreateGrowthPathNode | null>(null)
|
||||||
|
const draggingFromStage = ref<string>('')
|
||||||
|
const dragOverNode = ref<CreateGrowthPathNode | null>(null)
|
||||||
|
const dragOverPosition = ref<'top' | 'bottom'>('bottom')
|
||||||
|
const dragOverStage = ref<string>('')
|
||||||
|
|
||||||
// 基础数据
|
// 基础数据
|
||||||
const positions = ref<Position[]>([])
|
const positions = ref<Position[]>([])
|
||||||
const courses = ref<Course[]>([])
|
const courses = ref<Course[]>([])
|
||||||
@@ -942,6 +967,255 @@ const removeNode = (node: CreateGrowthPathNode) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点阶段变更时重新排序
|
||||||
|
*/
|
||||||
|
const handleNodeStageChange = (node: CreateGrowthPathNode) => {
|
||||||
|
// 阶段变更后自动调整到该阶段的末尾
|
||||||
|
reorderNodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新排序节点(按阶段分组)
|
||||||
|
*/
|
||||||
|
const reorderNodes = () => {
|
||||||
|
if (!editingPath.value) return
|
||||||
|
// 按阶段顺序重新排列
|
||||||
|
const stageOrder = editingPath.value.stages.map(s => s.name)
|
||||||
|
editingPath.value.nodes.sort((a, b) => {
|
||||||
|
const stageIndexA = stageOrder.indexOf(a.stage_name)
|
||||||
|
const stageIndexB = stageOrder.indexOf(b.stage_name)
|
||||||
|
if (stageIndexA !== stageIndexB) {
|
||||||
|
return stageIndexA - stageIndexB
|
||||||
|
}
|
||||||
|
return (a.order_num || 0) - (b.order_num || 0)
|
||||||
|
})
|
||||||
|
// 更新 order_num
|
||||||
|
editingPath.value.nodes.forEach((node, index) => {
|
||||||
|
node.order_num = index + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 节点拖拽排序 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点开始拖拽
|
||||||
|
*/
|
||||||
|
const handleNodeDragStart = (event: DragEvent, node: CreateGrowthPathNode, stageName: string) => {
|
||||||
|
draggingNode.value = node
|
||||||
|
draggingFromStage.value = stageName
|
||||||
|
event.dataTransfer!.effectAllowed = 'move'
|
||||||
|
event.dataTransfer!.setData('node', JSON.stringify(node))
|
||||||
|
// 添加拖拽样式
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
setTimeout(() => {
|
||||||
|
target.classList.add('is-dragging')
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点拖拽结束
|
||||||
|
*/
|
||||||
|
const handleNodeDragEnd = () => {
|
||||||
|
draggingNode.value = null
|
||||||
|
draggingFromStage.value = ''
|
||||||
|
dragOverNode.value = null
|
||||||
|
dragOverPosition.value = 'bottom'
|
||||||
|
dragOverStage.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点拖拽经过另一个节点
|
||||||
|
*/
|
||||||
|
const handleNodeDragOver = (event: DragEvent, targetNode: CreateGrowthPathNode) => {
|
||||||
|
if (!draggingNode.value || draggingNode.value.course_id === targetNode.course_id) return
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
dragOverNode.value = targetNode
|
||||||
|
|
||||||
|
// 判断是放在目标节点的上方还是下方
|
||||||
|
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
|
||||||
|
const midY = rect.top + rect.height / 2
|
||||||
|
dragOverPosition.value = event.clientY < midY ? 'top' : 'bottom'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点拖拽离开
|
||||||
|
*/
|
||||||
|
const handleNodeDragLeave = () => {
|
||||||
|
dragOverNode.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点放置到另一个节点上
|
||||||
|
*/
|
||||||
|
const handleNodeDropOnNode = (event: DragEvent, targetNode: CreateGrowthPathNode, targetStage: string) => {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (!draggingNode.value || !editingPath.value) return
|
||||||
|
if (draggingNode.value.course_id === targetNode.course_id) return
|
||||||
|
|
||||||
|
// 找到拖拽节点的索引
|
||||||
|
const fromIndex = editingPath.value.nodes.findIndex(n => n.course_id === draggingNode.value!.course_id)
|
||||||
|
if (fromIndex === -1) return
|
||||||
|
|
||||||
|
// 从原位置移除
|
||||||
|
const [movedNode] = editingPath.value.nodes.splice(fromIndex, 1)
|
||||||
|
|
||||||
|
// 更新阶段
|
||||||
|
movedNode.stage_name = targetStage
|
||||||
|
|
||||||
|
// 找到目标节点的新索引(因为可能已经移除了一个元素)
|
||||||
|
const toIndex = editingPath.value.nodes.findIndex(n => n.course_id === targetNode.course_id)
|
||||||
|
|
||||||
|
// 根据放置位置插入
|
||||||
|
if (dragOverPosition.value === 'top') {
|
||||||
|
editingPath.value.nodes.splice(toIndex, 0, movedNode)
|
||||||
|
} else {
|
||||||
|
editingPath.value.nodes.splice(toIndex + 1, 0, movedNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新计算 order_num
|
||||||
|
editingPath.value.nodes.forEach((node, index) => {
|
||||||
|
node.order_num = index + 1
|
||||||
|
})
|
||||||
|
|
||||||
|
// 清理状态
|
||||||
|
handleNodeDragEnd()
|
||||||
|
ElMessage.success('排序已更新')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阶段区域拖拽经过
|
||||||
|
*/
|
||||||
|
const handleStageDragOver = (event: DragEvent, stageName: string) => {
|
||||||
|
event.preventDefault()
|
||||||
|
dragOverStage.value = stageName
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阶段区域拖拽离开
|
||||||
|
*/
|
||||||
|
const handleStageDragLeave = (event: DragEvent) => {
|
||||||
|
// 检查是否真的离开了阶段区域
|
||||||
|
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
|
||||||
|
const x = event.clientX
|
||||||
|
const y = event.clientY
|
||||||
|
if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
|
||||||
|
dragOverStage.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点放置到阶段区域(空白处)
|
||||||
|
*/
|
||||||
|
const handleNodeDrop = (event: DragEvent, targetStage: string) => {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
// 优先处理从课程库拖入的课程
|
||||||
|
const courseData = event.dataTransfer!.getData('course')
|
||||||
|
if (courseData) {
|
||||||
|
const course = JSON.parse(courseData) as Course
|
||||||
|
handleAddCourseToStage(course, targetStage)
|
||||||
|
dragOverStage.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理节点排序
|
||||||
|
if (!draggingNode.value || !editingPath.value) {
|
||||||
|
dragOverStage.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动节点到目标阶段末尾
|
||||||
|
const fromIndex = editingPath.value.nodes.findIndex(n => n.course_id === draggingNode.value!.course_id)
|
||||||
|
if (fromIndex === -1) return
|
||||||
|
|
||||||
|
const [movedNode] = editingPath.value.nodes.splice(fromIndex, 1)
|
||||||
|
movedNode.stage_name = targetStage
|
||||||
|
|
||||||
|
// 找到该阶段最后一个节点的位置,插入其后
|
||||||
|
const stageNodes = editingPath.value.nodes.filter(n => n.stage_name === targetStage)
|
||||||
|
if (stageNodes.length > 0) {
|
||||||
|
const lastNode = stageNodes[stageNodes.length - 1]
|
||||||
|
const lastIndex = editingPath.value.nodes.findIndex(n => n.course_id === lastNode.course_id)
|
||||||
|
editingPath.value.nodes.splice(lastIndex + 1, 0, movedNode)
|
||||||
|
} else {
|
||||||
|
// 该阶段为空,找到下一个阶段的第一个节点位置
|
||||||
|
const stageOrder = editingPath.value.stages.map(s => s.name)
|
||||||
|
const targetStageIndex = stageOrder.indexOf(targetStage)
|
||||||
|
let insertIndex = editingPath.value.nodes.length
|
||||||
|
|
||||||
|
for (let i = targetStageIndex + 1; i < stageOrder.length; i++) {
|
||||||
|
const nextStageFirstNode = editingPath.value.nodes.find(n => n.stage_name === stageOrder[i])
|
||||||
|
if (nextStageFirstNode) {
|
||||||
|
insertIndex = editingPath.value.nodes.indexOf(nextStageFirstNode)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editingPath.value.nodes.splice(insertIndex, 0, movedNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新计算 order_num
|
||||||
|
editingPath.value.nodes.forEach((node, index) => {
|
||||||
|
node.order_num = index + 1
|
||||||
|
})
|
||||||
|
|
||||||
|
handleNodeDragEnd()
|
||||||
|
ElMessage.success('已移动到' + targetStage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加课程到指定阶段
|
||||||
|
*/
|
||||||
|
const handleAddCourseToStage = (course: Course, stageName: string) => {
|
||||||
|
if (!editingPath.value) return
|
||||||
|
if (isNodeAdded(course.id)) {
|
||||||
|
ElMessage.warning('该课程已添加')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到该阶段最后一个节点的位置
|
||||||
|
const stageNodes = editingPath.value.nodes.filter(n => n.stage_name === stageName)
|
||||||
|
let insertIndex = editingPath.value.nodes.length
|
||||||
|
|
||||||
|
if (stageNodes.length > 0) {
|
||||||
|
const lastNode = stageNodes[stageNodes.length - 1]
|
||||||
|
insertIndex = editingPath.value.nodes.indexOf(lastNode) + 1
|
||||||
|
} else {
|
||||||
|
// 该阶段为空,找到正确的插入位置
|
||||||
|
const stageOrder = editingPath.value.stages.map(s => s.name)
|
||||||
|
const targetStageIndex = stageOrder.indexOf(stageName)
|
||||||
|
|
||||||
|
for (let i = targetStageIndex + 1; i < stageOrder.length; i++) {
|
||||||
|
const nextStageFirstNode = editingPath.value.nodes.find(n => n.stage_name === stageOrder[i])
|
||||||
|
if (nextStageFirstNode) {
|
||||||
|
insertIndex = editingPath.value.nodes.indexOf(nextStageFirstNode)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newNode: CreateGrowthPathNode = {
|
||||||
|
course_id: course.id,
|
||||||
|
stage_name: stageName,
|
||||||
|
title: course.name || course.title || '',
|
||||||
|
description: course.description,
|
||||||
|
order_num: insertIndex + 1,
|
||||||
|
is_required: true,
|
||||||
|
estimated_days: Math.ceil((course.duration_hours || course.estimatedDuration || 2) / 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
editingPath.value.nodes.splice(insertIndex, 0, newNode)
|
||||||
|
|
||||||
|
// 重新计算 order_num
|
||||||
|
editingPath.value.nodes.forEach((node, index) => {
|
||||||
|
node.order_num = index + 1
|
||||||
|
})
|
||||||
|
|
||||||
|
ElMessage.success(`已添加到${stageName}`)
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 生命周期 ==========
|
// ========== 生命周期 ==========
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadPositions()
|
loadPositions()
|
||||||
@@ -1316,6 +1590,14 @@ onMounted(() => {
|
|||||||
|
|
||||||
.stage-section {
|
.stage-section {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&.stage-drag-over {
|
||||||
|
.stage-nodes {
|
||||||
|
background: #ecf5ff;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.stage-header {
|
.stage-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1335,64 +1617,135 @@ onMounted(() => {
|
|||||||
border-radius: 0 0 6px 6px;
|
border-radius: 0 0 6px 6px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
.node-item {
|
.node-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #ebeef5;
|
border: 1px solid #ebeef5;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
cursor: grab;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-style: dashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.drag-over-top {
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||||
|
border-radius: 2px;
|
||||||
|
animation: pulse-line 1s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.drag-over-bottom {
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -4px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||||
|
border-radius: 2px;
|
||||||
|
animation: pulse-line 1s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.node-drag-handle {
|
.node-drag-handle {
|
||||||
color: #c0c4cc;
|
color: #c0c4cc;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-content {
|
.node-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
.node-title {
|
.node-title {
|
||||||
display: block;
|
display: block;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-meta {
|
.node-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-actions {
|
.node-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stage-empty {
|
.stage-empty {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 16px;
|
padding: 20px 16px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
border: 2px dashed #e4e7ed;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 4px 0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&.stage-empty-active {
|
||||||
|
background: #ecf5ff;
|
||||||
|
border-color: #667eea;
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@keyframes pulse-line {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.5; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user