Files
fauto-design/draggable-panels/src/fauto/materials/TreeViewer/index.vue
2025-12-20 23:16:23 +08:00

164 lines
3.2 KiB
Vue

<script setup lang="ts">
import { ref, watch } from 'vue'
import draggable from 'vuedraggable'
import { useDesignStore } from '../../stores/designStore'
import config from './index.json'
const designStore = useDesignStore()
// 创建可响应的本地副本用于拖拽
const localComponents = ref([...designStore.components])
// 监听 designStore 组件变化,同步到本地副本
watch(
() => designStore.components,
(newVal) => {
localComponents.value = [...newVal]
},
{ deep: true, immediate: true }
)
// 拖拽结束处理 - 同步顺序到 designStore
const onDragEnd = () => {
// 重新排序 designStore 中的组件
designStore.reorderComponents([...localComponents.value])
}
// 点击节点 - 选中组件
const handleNodeClick = (nodeId: string) => {
designStore.selectComponent(nodeId)
}
// 获取组件类型图标
const getComponentIcon = (componentId: string) => {
const icons: Record<string, string> = {
TextInput: '✏️',
RadioSelect: '◉',
GridTable: '☰'
}
return icons[componentId] || '⭕'
}
</script>
<template>
<div class="tree-viewer">
<div class="viewer-header">
<span class="title">{{ config.name }}</span>
<span class="hint">设计组件列表</span>
</div>
<div class="viewer-body">
<div class="tree-container">
<draggable
:list="localComponents"
item-key="id"
:animation="150"
ghost-class="node-ghost"
@end="onDragEnd"
>
<template #item="{ element: node }">
<div
class="tree-node"
:class="{ selected: designStore.selectedId === node.id }"
@click="handleNodeClick(node.id)"
>
<span class="node-icon">{{ getComponentIcon(node.componentId) }}</span>
<span class="node-label">{{ node.name }}</span>
</div>
</template>
</draggable>
<div v-if="localComponents.length === 0" class="empty-tip">
暂无设计组件
</div>
</div>
</div>
</div>
</template>
<style scoped>
.tree-viewer {
display: flex;
flex-direction: column;
height: 100%;
background: #1e1e1e;
}
.viewer-header {
padding: 8px 12px;
background: #2d2d2d;
border-bottom: 1px solid #3c3c3c;
display: flex;
align-items: center;
justify-content: space-between;
}
.title {
color: #cccccc;
font-size: 13px;
font-weight: 500;
}
.hint {
color: #666666;
font-size: 11px;
}
.viewer-body {
flex: 1;
padding: 8px;
overflow: auto;
}
.tree-container {
font-size: 13px;
}
.tree-node {
display: flex;
align-items: center;
padding: 8px 12px;
cursor: grab;
border-radius: 4px;
color: #cccccc;
user-select: none;
margin-bottom: 2px;
transition: all 0.15s;
}
.tree-node:active {
cursor: grabbing;
}
.tree-node:hover {
background: #2a2d2e;
}
.tree-node.selected {
background: #094771;
}
.node-icon {
width: 20px;
margin-right: 8px;
text-align: center;
}
.node-label {
flex: 1;
}
.empty-tip {
color: #666666;
text-align: center;
padding: 20px;
font-size: 12px;
}
/* 拖拽样式 */
.node-ghost {
opacity: 0.4;
background: #094771;
border-radius: 4px;
}
</style>