|
@@ -1,107 +1,283 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <el-tabs v-model="activeMenu" stretch class="w-full h-full">
|
|
|
|
|
- <el-tab-pane label="屏幕" name="screen" class="flex-1">
|
|
|
|
|
- <!-- 屏幕层 -->
|
|
|
|
|
- <el-tree
|
|
|
|
|
- ref="screenTreeRef"
|
|
|
|
|
- style="max-width: 600px"
|
|
|
|
|
- default-expand-all
|
|
|
|
|
- node-key="id"
|
|
|
|
|
- :data="projectStore.project?.screens"
|
|
|
|
|
- :props="{ label: 'name', children: 'pages' }"
|
|
|
|
|
- @node-click="handlePageNodeClick"
|
|
|
|
|
- >
|
|
|
|
|
- <template #default="{ node, data }">
|
|
|
|
|
- <ScreenTreeItem :node="node" :data="data" />
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-tree>
|
|
|
|
|
- </el-tab-pane>
|
|
|
|
|
- <el-tab-pane label="页面" name="page" class="w-full h-full">
|
|
|
|
|
- <!-- 页面层 -->
|
|
|
|
|
- <div class="h-full overflow-hidden" ref="pageBoxRef">
|
|
|
|
|
|
|
+ <div class="w-full h-full flex flex-col gap-8px">
|
|
|
|
|
+ <div class="p-8px">
|
|
|
|
|
+ <el-input v-model="search" clearable :prefix-icon="Search" placeholder="搜索" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="flex-1 overflow-hidden" ref="treeBoxRef">
|
|
|
|
|
+ <el-scrollbar class="h-full">
|
|
|
<el-tree
|
|
<el-tree
|
|
|
- ref="pageTreeRef"
|
|
|
|
|
|
|
+ ref="hierarchyTreeRef"
|
|
|
style="max-width: 600px"
|
|
style="max-width: 600px"
|
|
|
default-expand-all
|
|
default-expand-all
|
|
|
node-key="id"
|
|
node-key="id"
|
|
|
draggable
|
|
draggable
|
|
|
:allow-drag="allowDrag"
|
|
:allow-drag="allowDrag"
|
|
|
:allow-drop="allowDrop"
|
|
:allow-drop="allowDrop"
|
|
|
- :height="height || 100"
|
|
|
|
|
|
|
+ :height="treeHeight"
|
|
|
:highlight-current="false"
|
|
:highlight-current="false"
|
|
|
- :default-expanded-keys="projectStore.activePageId ? [projectStore.activePageId] : []"
|
|
|
|
|
- :data="projectStore.activePage ? [projectStore.activePage] : []"
|
|
|
|
|
|
|
+ :data="hierarchyTreeData"
|
|
|
:props="{ label: 'name', children: 'children' }"
|
|
:props="{ label: 'name', children: 'children' }"
|
|
|
- @node-click="handleWidgetNodeClick"
|
|
|
|
|
|
|
+ :filter-node-method="filterNode"
|
|
|
|
|
+ @node-click="handleNodeClick"
|
|
|
|
|
+ @node-drop="handleNodeDrop"
|
|
|
>
|
|
>
|
|
|
<template #default="{ node, data }">
|
|
<template #default="{ node, data }">
|
|
|
- <PageTreeItem :node="node" :data="data" />
|
|
|
|
|
|
|
+ <ScreenTreeItem
|
|
|
|
|
+ v-if="isScreenOrPageNode(data)"
|
|
|
|
|
+ :node="node"
|
|
|
|
|
+ :data="getScreenOrPageRawNode(data)"
|
|
|
|
|
+ />
|
|
|
|
|
+ <PageTreeItem v-else :node="node" :data="getWidgetRawNode(data)" />
|
|
|
</template>
|
|
</template>
|
|
|
</el-tree>
|
|
</el-tree>
|
|
|
- </div>
|
|
|
|
|
- </el-tab-pane>
|
|
|
|
|
- </el-tabs>
|
|
|
|
|
|
|
+ </el-scrollbar>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref } from 'vue'
|
|
|
|
|
|
|
+import { computed, ref, watch } from 'vue'
|
|
|
|
|
+import { Search } from '@element-plus/icons-vue'
|
|
|
|
|
+import { useElementSize } from '@vueuse/core'
|
|
|
|
|
|
|
|
import ScreenTreeItem from './components/ScreenTreeItem.vue'
|
|
import ScreenTreeItem from './components/ScreenTreeItem.vue'
|
|
|
import PageTreeItem from './components/PageTreeItem.vue'
|
|
import PageTreeItem from './components/PageTreeItem.vue'
|
|
|
import { useProjectStore } from '@/store/modules/project'
|
|
import { useProjectStore } from '@/store/modules/project'
|
|
|
-import { useElementSize } from '@vueuse/core'
|
|
|
|
|
|
|
|
|
|
-import type { AllowDragFunction, AllowDropFunction } from 'element-plus'
|
|
|
|
|
|
|
+import type { BaseWidget } from '@/types/baseWidget'
|
|
|
|
|
+import type { Page } from '@/types/page'
|
|
|
|
|
+import type { Screen } from '@/types/screen'
|
|
|
|
|
+import type {
|
|
|
|
|
+ AllowDragFunction,
|
|
|
|
|
+ AllowDropFunction,
|
|
|
|
|
+ FilterNodeMethodFunction,
|
|
|
|
|
+ NodeDropType,
|
|
|
|
|
+ TreeInstance,
|
|
|
|
|
+ TreeNodeData
|
|
|
|
|
+} from 'element-plus'
|
|
|
|
|
+
|
|
|
|
|
+type HierarchyNodeData = {
|
|
|
|
|
+ id: string
|
|
|
|
|
+ name: string
|
|
|
|
|
+ nodeType: 'screen' | 'page' | 'widget'
|
|
|
|
|
+ raw: Screen | Page | BaseWidget
|
|
|
|
|
+ screenId: string
|
|
|
|
|
+ pageId?: string
|
|
|
|
|
+ children: HierarchyNodeData[]
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
const projectStore = useProjectStore()
|
|
const projectStore = useProjectStore()
|
|
|
-const activeMenu = ref<string>('screen')
|
|
|
|
|
-const pageBoxRef = ref<HTMLDivElement>()
|
|
|
|
|
|
|
+const search = ref('')
|
|
|
|
|
+const hierarchyTreeRef = ref<TreeInstance | null>(null)
|
|
|
|
|
+const treeBoxRef = ref<HTMLDivElement | null>(null)
|
|
|
|
|
|
|
|
-const { height } = useElementSize(pageBoxRef, {
|
|
|
|
|
|
|
+const { height } = useElementSize(treeBoxRef, {
|
|
|
height: 100,
|
|
height: 100,
|
|
|
width: 100
|
|
width: 100
|
|
|
})
|
|
})
|
|
|
|
|
+const treeHeight = computed(() => Math.max(height.value || 0, 100))
|
|
|
|
|
+
|
|
|
|
|
+const getHierarchyNode = (data: TreeNodeData): HierarchyNodeData => data as HierarchyNodeData
|
|
|
|
|
+const getScreenOrPageRawNode = (data: TreeNodeData): Screen | Page =>
|
|
|
|
|
+ getHierarchyNode(data).raw as Screen | Page
|
|
|
|
|
+const getWidgetRawNode = (data: TreeNodeData): BaseWidget =>
|
|
|
|
|
+ getHierarchyNode(data).raw as BaseWidget
|
|
|
|
|
+const isScreenOrPageNode = (data: TreeNodeData) => {
|
|
|
|
|
+ const node = getHierarchyNode(data)
|
|
|
|
|
+ return node.nodeType === 'screen' || node.nodeType === 'page'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const buildWidgetNodes = (
|
|
|
|
|
+ widgets: BaseWidget[] = [],
|
|
|
|
|
+ screenId: string,
|
|
|
|
|
+ pageId: string
|
|
|
|
|
+): HierarchyNodeData[] => {
|
|
|
|
|
+ return widgets.map((widget) => ({
|
|
|
|
|
+ id: widget.id,
|
|
|
|
|
+ name: widget.name,
|
|
|
|
|
+ nodeType: 'widget',
|
|
|
|
|
+ raw: widget,
|
|
|
|
|
+ screenId,
|
|
|
|
|
+ pageId,
|
|
|
|
|
+ children: buildWidgetNodes(widget.children || [], screenId, pageId)
|
|
|
|
|
+ }))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const hierarchyTreeData = computed<HierarchyNodeData[]>(() => {
|
|
|
|
|
+ return (
|
|
|
|
|
+ projectStore.project?.screens.map((screen) => ({
|
|
|
|
|
+ id: screen.id,
|
|
|
|
|
+ name: screen.name,
|
|
|
|
|
+ nodeType: 'screen',
|
|
|
|
|
+ raw: screen,
|
|
|
|
|
+ screenId: screen.id,
|
|
|
|
|
+ children: screen.pages.map((page) => ({
|
|
|
|
|
+ id: page.id,
|
|
|
|
|
+ name: page.name,
|
|
|
|
|
+ nodeType: 'page',
|
|
|
|
|
+ raw: page,
|
|
|
|
|
+ screenId: screen.id,
|
|
|
|
|
+ pageId: page.id,
|
|
|
|
|
+ children: buildWidgetNodes(page.children || [], screen.id, page.id)
|
|
|
|
|
+ }))
|
|
|
|
|
+ })) || []
|
|
|
|
|
+ )
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const setOpenedPage = (screenId?: string, pageId?: string) => {
|
|
|
|
|
+ if (!screenId || !pageId) return
|
|
|
|
|
|
|
|
-const handlePageNodeClick = (node: any) => {
|
|
|
|
|
- if (node.type === 'page') {
|
|
|
|
|
- projectStore.activePageId = node.id
|
|
|
|
|
|
|
+ const screenIndex =
|
|
|
|
|
+ projectStore.project?.screens.findIndex((screen) => screen.id === screenId) ?? -1
|
|
|
|
|
+ if (screenIndex !== -1) {
|
|
|
|
|
+ projectStore.openPageIds[screenIndex] = pageId
|
|
|
}
|
|
}
|
|
|
- // 当前屏幕打开的页面
|
|
|
|
|
- projectStore.project?.screens.forEach((screen, index) => {
|
|
|
|
|
- screen.pages.forEach((page) => {
|
|
|
|
|
- if (page.id === node.id) {
|
|
|
|
|
- projectStore.openPageIds[index] = page.id
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- })
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const handleWidgetNodeClick = (nodeData, node, e) => {
|
|
|
|
|
- if (nodeData.type !== 'page' && nodeData?.id) {
|
|
|
|
|
- if (e.ctrlKey) {
|
|
|
|
|
- projectStore.activeWidgets.push(nodeData)
|
|
|
|
|
- } else {
|
|
|
|
|
- projectStore.setSelectWidgets([nodeData])
|
|
|
|
|
- }
|
|
|
|
|
|
|
+const handleNodeClick = (nodeData: HierarchyNodeData, _node: unknown, e?: MouseEvent) => {
|
|
|
|
|
+ console.log(1111, nodeData)
|
|
|
|
|
+ if (nodeData.nodeType === 'page') {
|
|
|
|
|
+ projectStore.activePageId = nodeData.id
|
|
|
|
|
+ setOpenedPage(nodeData.screenId, nodeData.id)
|
|
|
|
|
+ projectStore.setSelectWidgets([])
|
|
|
|
|
+ return
|
|
|
}
|
|
}
|
|
|
- const parent = node.parent?.data
|
|
|
|
|
- if (!nodeData?.id && parent) {
|
|
|
|
|
- if (e.ctrlKey) {
|
|
|
|
|
- projectStore.activeWidgets.push(parent)
|
|
|
|
|
- } else {
|
|
|
|
|
- projectStore.setSelectWidgets([parent])
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (nodeData.nodeType !== 'widget' || !e || !nodeData?.id) return
|
|
|
|
|
+
|
|
|
|
|
+ const pageChanged = !!nodeData.pageId && nodeData.pageId !== projectStore.activePageId
|
|
|
|
|
+ if (nodeData.pageId) {
|
|
|
|
|
+ projectStore.activePageId = nodeData.pageId
|
|
|
|
|
+ setOpenedPage(nodeData.screenId, nodeData.pageId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (e.ctrlKey && !pageChanged) {
|
|
|
|
|
+ projectStore.activeWidgets.push(nodeData.raw as BaseWidget)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ projectStore.setSelectWidgets([nodeData.raw as BaseWidget])
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const matchesKeyword = (keyword: string, node: HierarchyNodeData) => {
|
|
|
|
|
+ const currentName = String(node.raw.name || '').toLowerCase()
|
|
|
|
|
+ if (currentName.includes(keyword)) return true
|
|
|
|
|
+
|
|
|
|
|
+ return node.children.some((child) => matchesKeyword(keyword, child))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const filterNode: FilterNodeMethodFunction = (value, data) => {
|
|
|
|
|
+ const keyword = String(value || '')
|
|
|
|
|
+ .trim()
|
|
|
|
|
+ .toLowerCase()
|
|
|
|
|
+ if (!keyword) return true
|
|
|
|
|
+
|
|
|
|
|
+ return matchesKeyword(keyword, getHierarchyNode(data))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+watch(search, (value) => {
|
|
|
|
|
+ hierarchyTreeRef.value?.filter(value.trim())
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const isDescendantNode = (node: HierarchyNodeData, childId: string) => {
|
|
|
|
|
+ return node.children.some((item) => item.id === childId || isDescendantNode(item, childId))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const allowDrag: AllowDragFunction = (node) => {
|
|
const allowDrag: AllowDragFunction = (node) => {
|
|
|
- // 禁用拖拽的节点
|
|
|
|
|
- const notallow = ['page']
|
|
|
|
|
|
|
+ const currentNode = getHierarchyNode(node.data)
|
|
|
|
|
+ return (
|
|
|
|
|
+ currentNode.nodeType === 'widget' &&
|
|
|
|
|
+ !!currentNode.pageId &&
|
|
|
|
|
+ currentNode.pageId === projectStore.activePageId
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const allowDrop: AllowDropFunction = (dragNode, dropNode, type) => {
|
|
|
|
|
+ const dragData = getHierarchyNode(dragNode.data)
|
|
|
|
|
+ const dropData = getHierarchyNode(dropNode.data)
|
|
|
|
|
|
|
|
- return !notallow.includes(node.data?.type)
|
|
|
|
|
|
|
+ if (dragData.nodeType !== 'widget' || dropData.nodeType === 'screen') return false
|
|
|
|
|
+ if (!dragData.pageId || !dropData.pageId || dragData.pageId !== dropData.pageId) return false
|
|
|
|
|
+ if (dragData.id === dropData.id || isDescendantNode(dragData, dropData.id)) return false
|
|
|
|
|
+
|
|
|
|
|
+ if (dropData.nodeType === 'page') {
|
|
|
|
|
+ return type === 'inner'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (type === 'inner') {
|
|
|
|
|
+ return !!(dropData.raw as BaseWidget).children
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const findPageById = (pageId?: string) => {
|
|
|
|
|
+ if (!pageId) return
|
|
|
|
|
+
|
|
|
|
|
+ return projectStore.project?.screens
|
|
|
|
|
+ .flatMap((screen) => screen.pages)
|
|
|
|
|
+ .find((page) => page.id === pageId)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const allowDrop: AllowDropFunction = (_dragNode, dropNode) => {
|
|
|
|
|
- return dropNode.data?.children
|
|
|
|
|
|
|
+const findWidgetListById = (widgets: BaseWidget[], widgetId: string): BaseWidget[] | undefined => {
|
|
|
|
|
+ for (const widget of widgets) {
|
|
|
|
|
+ if (widget.id === widgetId) {
|
|
|
|
|
+ return widgets
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (widget.children?.length) {
|
|
|
|
|
+ const found = findWidgetListById(widget.children, widgetId)
|
|
|
|
|
+ if (found) {
|
|
|
|
|
+ return found
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const takeWidgetFromPage = (page: Page, widgetId: string) => {
|
|
|
|
|
+ const list = findWidgetListById(page.children || [], widgetId)
|
|
|
|
|
+ if (!list) return
|
|
|
|
|
+
|
|
|
|
|
+ const index = list.findIndex((widget) => widget.id === widgetId)
|
|
|
|
|
+ if (index === -1) return
|
|
|
|
|
+
|
|
|
|
|
+ return list.splice(index, 1)[0]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type TreeDragNode = {
|
|
|
|
|
+ data: TreeNodeData
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleNodeDrop = (dragNode: TreeDragNode, dropNode: TreeDragNode, dropType: NodeDropType) => {
|
|
|
|
|
+ const dragData = getHierarchyNode(dragNode.data)
|
|
|
|
|
+ const targetData = getHierarchyNode(dropNode.data)
|
|
|
|
|
+
|
|
|
|
|
+ if (dropType === 'none') return
|
|
|
|
|
+
|
|
|
|
|
+ if (dragData.nodeType !== 'widget' || !dragData.pageId || dragData.pageId !== targetData.pageId) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const page = findPageById(dragData.pageId)
|
|
|
|
|
+ if (!page) return
|
|
|
|
|
+
|
|
|
|
|
+ const widget = takeWidgetFromPage(page, dragData.id)
|
|
|
|
|
+ if (!widget) return
|
|
|
|
|
+
|
|
|
|
|
+ if (dropType === 'inner') {
|
|
|
|
|
+ const target = targetData.raw as Page | BaseWidget
|
|
|
|
|
+ target.children = target.children || []
|
|
|
|
|
+ target.children.push(widget)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const targetList = findWidgetListById(page.children || [], targetData.id)
|
|
|
|
|
+ if (!targetList) return
|
|
|
|
|
+
|
|
|
|
|
+ const index = targetList.findIndex((item) => item.id === targetData.id)
|
|
|
|
|
+ if (index === -1) return
|
|
|
|
|
+
|
|
|
|
|
+ targetList.splice(dropType === 'before' ? index : index + 1, 0, widget)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ projectStore.setSelectWidgets([widget])
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|