CMVR-IOT-UI/src/views/flow/components/Aside.vue

252 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
* 文件说明:节点组件库侧栏,负责分类、搜索和拖拽创建。
* 作用范围:仅服务于流程设计器模块。
-->
<template>
<div class="component-library">
<div class="library-header">
<div class="library-heading">
<div>
<div class="library-title">组件库</div>
<div class="library-subtitle">{{ totalCount }} 个可用组件</div>
</div>
</div>
<el-input v-model="keyword" clearable placeholder="搜索组件" :prefix-icon="Search" />
</div>
<div class="library-content">
<el-collapse v-if="filteredGroups.length" v-model="activeNames">
<el-collapse-item
v-for="item in filteredGroups"
:key="item.collapseTitle"
:name="item.collapseTitle"
class="collapse-item"
>
<template #title>
<div class="category-title">
<span>{{ item.collapseTitle }}</span>
<span class="category-count">{{ item.nodeList.length }}</span>
</div>
</template>
<div class="node-list">
<div
v-for="node in item.nodeList"
:key="`${node.type}-${node.action || node.name}`"
class="node-item"
draggable="true"
@dragstart="handleDragCustom($event, node)"
>
<div class="node-icon"><img :src="node.icon" alt="" /></div>
<div class="node-content">
<div class="node-name">{{ node.name }}</div>
<div class="node-desc">{{ node.desc }}</div>
</div>
<el-icon class="drag-handle"><Rank /></el-icon>
</div>
</div>
</el-collapse-item>
</el-collapse>
<el-empty v-else :image-size="72" description="没有匹配的组件" />
</div>
</div>
</template>
<script setup>
import { computed, ref, watch } from "vue";
import { Rank, Search } from "@element-plus/icons-vue";
import { collapseList } from "../config";
const keyword = ref("");
const activeNames = ref(collapseList[0] ? [collapseList[0].collapseTitle] : []);
const totalCount = computed(() => collapseList.reduce((total, group) => total + group.nodeList.length, 0));
const filteredGroups = computed(() => {
const query = keyword.value.trim().toLowerCase();
if (!query) return collapseList;
return collapseList
.map((group) => ({
...group,
nodeList: group.nodeList.filter((node) => (
[node.name, node.desc, node.action, node.type]
.filter(Boolean)
.some((value) => String(value).toLowerCase().includes(query))
)),
}))
.filter((group) => group.nodeList.length > 0);
});
watch(keyword, (value) => {
if (value.trim()) activeNames.value = filteredGroups.value.map((group) => group.collapseTitle);
});
/**
* 处理 handleDragCustom 对应的数据或交互;作用范围仅限当前组件或模块。
* @param {*} event 触发操作的浏览器事件
* @param {*} node 当前流程节点
*/
const handleDragCustom = (event, node) => {
event.dataTransfer.effectAllowed = "copy";
event.dataTransfer.setData("flowNode", JSON.stringify(node));
};
</script>
<style lang="scss" scoped>
.component-library {
display: flex;
height: 100%;
min-height: 0;
flex-direction: column;
color: #1f2937;
}
.library-header {
padding: 16px 14px 14px;
border-bottom: 1px solid #e4e8ef;
background: #fff;
}
.library-heading {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.library-title {
font-size: 15px;
font-weight: 650;
}
.library-subtitle {
margin-top: 2px;
color: #8a94a6;
font-size: 12px;
}
.library-content {
min-height: 0;
flex: 1;
overflow-y: auto;
scrollbar-width: thin;
}
:deep(.el-collapse) {
border: 0;
}
:deep(.collapse-item) {
.el-collapse-item__header {
height: 46px;
padding: 0 14px;
border-bottom-color: #e8ebf0;
background: #f8fafc;
color: #344054;
font-size: 13px;
font-weight: 600;
}
.el-collapse-item__wrap {
border-bottom-color: #e8ebf0;
background: #f8fafc;
}
.el-collapse-item__content {
padding: 8px 10px 10px;
}
}
.category-title {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
justify-content: space-between;
padding-right: 10px;
}
.category-count {
display: grid;
min-width: 22px;
height: 20px;
padding: 0 6px;
place-items: center;
border: 1px solid #dfe4ec;
border-radius: 4px;
background: #fff;
color: #7a8597;
font-size: 11px;
font-weight: 500;
line-height: 18px;
}
.node-list {
display: flex;
flex-direction: column;
gap: 7px;
}
.node-item {
display: grid;
min-height: 62px;
grid-template-columns: 34px minmax(0, 1fr) 18px;
gap: 9px;
align-items: center;
padding: 9px 8px 9px 10px;
border: 1px solid #e1e6ed;
border-radius: 7px;
background: #fff;
cursor: grab;
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
&:hover {
border-color: #f2a071;
box-shadow: 0 5px 14px rgba(31, 41, 55, 0.08);
transform: translateX(2px);
}
&:active { cursor: grabbing; }
}
.node-icon {
display: grid;
width: 34px;
height: 34px;
place-items: center;
border-radius: 6px;
background: #f1f5f9;
img {
width: 21px;
height: 21px;
object-fit: contain;
}
}
.node-content { min-width: 0; }
.node-name {
overflow: hidden;
color: #202938;
font-size: 13px;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.node-desc {
display: -webkit-box;
overflow: hidden;
margin-top: 4px;
color: #7a8597;
font-size: 11px;
line-height: 16px;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.drag-handle {
color: #a8b0bf;
font-size: 16px;
}
</style>