u
This commit is contained in:
1 parent
f572dce2f6
commit
e99a9fb274
356 files changed
+28877
-1055
No files matched your search
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
// 最基础:treeData 是嵌套结构,默认按 { key, title, children } 取值
|
||||
import { ref } from 'vue'
|
||||
import { G3Tree, type TreeNodeData } from '@g3soft/ui'
|
||||
|
||||
const treeData: TreeNodeData[] = [
|
||||
{
|
||||
key: 'design',
|
||||
title: '设计',
|
||||
children: [
|
||||
{ key: 'design/ui', title: 'UI 规范' },
|
||||
{ key: 'design/icon', title: '图标库' },
|
||||
],
|
||||
},
|
||||
{ key: 'frontend', title: '前端' },
|
||||
{ key: 'backend', title: '后端' },
|
||||
]
|
||||
|
||||
const selectedKeys = ref<string[]>(['design/ui'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="max-width: 320px">
|
||||
<G3Tree v-model:selected-keys="selectedKeys" :tree-data="treeData" default-expand-all />
|
||||
<p style="margin: 8px 0 0; font-size: 13px; color: #6b7280">选中:{{ selectedKeys.join(', ') || '无' }}</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
// checkable:父子联动;checkStrictly 时父子互不影响(右侧)
|
||||
import { ref } from 'vue'
|
||||
import { G3Space, G3Tree, type TreeNodeData } from '@g3soft/ui'
|
||||
|
||||
const treeData: TreeNodeData[] = [
|
||||
{
|
||||
key: 'role',
|
||||
title: '角色权限',
|
||||
children: [
|
||||
{ key: 'read', title: '查看' },
|
||||
{ key: 'write', title: '编辑' },
|
||||
{ key: 'delete', title: '删除' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const linked = ref<string[]>(['read'])
|
||||
const strict = ref<string[]>(['read'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<G3Space :size="32" wrap align="start">
|
||||
<div style="width: 240px">
|
||||
<p style="margin: 0 0 8px; font-size: 13px; color: #6b7280">父子联动(半选会回传)</p>
|
||||
<G3Tree v-model:checked-keys="linked" :tree-data="treeData" checkable default-expand-all block-node />
|
||||
<p style="margin: 8px 0 0; font-size: 12px">checked: {{ linked }}</p>
|
||||
</div>
|
||||
|
||||
<div style="width: 240px">
|
||||
<p style="margin: 0 0 8px; font-size: 13px; color: #6b7280">checkStrictly(互不影响)</p>
|
||||
<G3Tree v-model:checked-keys="strict" :tree-data="treeData" checkable check-strictly default-expand-all block-node />
|
||||
<p style="margin: 8px 0 0; font-size: 12px">checked: {{ strict }}</p>
|
||||
</div>
|
||||
</G3Space>
|
||||
</template>
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
// contextMenu:右键节点或空白处弹出菜单;菜单点击通过 contextMenuClick 回传
|
||||
import { ref } from 'vue'
|
||||
import { G3Message, G3Tree, type TreeContextMenuItem, type TreeNodeData } from '@g3soft/ui'
|
||||
import { Copy, Pencil, Trash2 } from '@lucide/vue'
|
||||
|
||||
const treeData: TreeNodeData[] = [
|
||||
{ key: 'a', title: '文档 A' },
|
||||
{ key: 'b', title: '文档 B' },
|
||||
]
|
||||
|
||||
const lastAction = ref('暂无')
|
||||
|
||||
// 用函数形式:可以按节点动态生成菜单(node 为 null 表示在空白处右键)
|
||||
function contextMenu(node: TreeNodeData | null): TreeContextMenuItem[] {
|
||||
if (!node) {
|
||||
return [{ key: 'create', label: '新建文档' }]
|
||||
}
|
||||
return [
|
||||
{ key: 'rename', label: '重命名', icon: Pencil },
|
||||
{ key: 'copy', label: '复制', icon: Copy },
|
||||
{ divider: true },
|
||||
{ key: 'delete', label: '删除', icon: Trash2, danger: true },
|
||||
]
|
||||
}
|
||||
|
||||
function onMenuClick(info: { item: TreeContextMenuItem; node: TreeNodeData | null }) {
|
||||
lastAction.value = `${info.node ? info.node.title : '空白处'} → ${info.item.label}`
|
||||
G3Message.info(lastAction.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="max-width: 320px">
|
||||
<G3Tree
|
||||
:tree-data="treeData"
|
||||
:context-menu="contextMenu"
|
||||
default-expand-all
|
||||
block-node
|
||||
@context-menu-click="onMenuClick"
|
||||
/>
|
||||
<p style="margin: 8px 0 0; font-size: 12px; color: #6b7280">{{ lastAction }}</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
// fieldNames 适配后端结构;#title 插槽自定义节点渲染;节点字段可控制图标/禁用
|
||||
import { ref } from 'vue'
|
||||
import { G3Button, G3Tag, G3Tree, type TreeNodeData } from '@g3soft/ui'
|
||||
import { Folder, FileText } from '@lucide/vue'
|
||||
|
||||
// 后端字段:id / name / nodes
|
||||
const treeData = ref<TreeNodeData[]>([
|
||||
{
|
||||
id: 'f1',
|
||||
name: '设计稿',
|
||||
icon: Folder,
|
||||
nodes: [
|
||||
{ id: 'f1-1', name: '首页.sketch', icon: FileText },
|
||||
{ id: 'f1-2', name: '详情页.sketch', icon: FileText, disabled: true },
|
||||
],
|
||||
},
|
||||
{ id: 'f2', name: '需求文档.md', icon: FileText },
|
||||
])
|
||||
|
||||
const selectedKeys = ref<string[]>([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="max-width: 360px">
|
||||
<G3Tree
|
||||
v-model:selected-keys="selectedKeys"
|
||||
:tree-data="treeData"
|
||||
:field-names="{ key: 'id', title: 'name', children: 'nodes' }"
|
||||
default-expand-all
|
||||
block-node
|
||||
>
|
||||
<!-- #title 插槽:node 是原始数据,可自由渲染 -->
|
||||
<template #title="{ node, selected }">
|
||||
<span style="display: inline-flex; align-items: center; gap: 8px; width: 100%">
|
||||
<span>{{ node.name }}</span>
|
||||
<G3Tag v-if="selected" variant="secondary">当前</G3Tag>
|
||||
<G3Button
|
||||
v-if="!node.disabled"
|
||||
size="small"
|
||||
type="link"
|
||||
style="margin-left: auto"
|
||||
@click.stop="selectedKeys = [String(node.id)]"
|
||||
>
|
||||
选中
|
||||
</G3Button>
|
||||
</span>
|
||||
</template>
|
||||
</G3Tree>
|
||||
|
||||
<p style="margin: 8px 0 0; font-size: 12px; color: #6b7280">选中:{{ selectedKeys.join(', ') || '无' }}</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
// draggable:拖到行的上/下 25% 是「前/后插入」,中间是「放入内部」
|
||||
// drop 事件里自己重排数据(组件不直接改 treeData)
|
||||
import { ref } from 'vue'
|
||||
import { G3Message, G3Tree, type TreeNodeData } from '@g3soft/ui'
|
||||
|
||||
const treeData = ref<TreeNodeData[]>([
|
||||
{ key: '1', title: '第一项' },
|
||||
{ key: '2', title: '第二项' },
|
||||
{
|
||||
key: '3',
|
||||
title: '第三项(可拖入)',
|
||||
children: [{ key: '3-1', title: '子项' }],
|
||||
},
|
||||
])
|
||||
|
||||
function findNode(nodes: TreeNodeData[], key: string): TreeNodeData | null {
|
||||
for (const node of nodes) {
|
||||
if (String(node.key) === key) return node
|
||||
const found = node.children ? findNode(node.children, key) : null
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function removeNode(nodes: TreeNodeData[], key: string): TreeNodeData | null {
|
||||
const index = nodes.findIndex((node) => String(node.key) === key)
|
||||
if (index >= 0) return nodes.splice(index, 1)[0]
|
||||
for (const node of nodes) {
|
||||
if (node.children) {
|
||||
const removed = removeNode(node.children, key)
|
||||
if (removed) return removed
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function onDrop(info: { dragKey: string | number; dropKey: string | number; dropPosition: string }) {
|
||||
const dragKey = String(info.dragKey)
|
||||
const dropKey = String(info.dropKey)
|
||||
const data = treeData.value
|
||||
const moved = removeNode(data, dragKey)
|
||||
if (!moved) return
|
||||
|
||||
if (info.dropPosition === 'inside') {
|
||||
const target = findNode(data, dropKey)
|
||||
if (target) {
|
||||
target.children = [...(target.children ?? []), moved]
|
||||
G3Message.success(`已移动到「${target.title}」内部`)
|
||||
}
|
||||
} else {
|
||||
const insert = (nodes: TreeNodeData[]): boolean => {
|
||||
const index = nodes.findIndex((node) => String(node.key) === dropKey)
|
||||
if (index >= 0) {
|
||||
nodes.splice(info.dropPosition === 'before' ? index : index + 1, 0, moved)
|
||||
return true
|
||||
}
|
||||
return nodes.some((node) => (node.children ? insert(node.children) : false))
|
||||
}
|
||||
insert(data)
|
||||
G3Message.success(`已${info.dropPosition === 'before' ? '前移' : '后移'}到「${dropKey}」`)
|
||||
}
|
||||
|
||||
treeData.value = [...data]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="max-width: 320px">
|
||||
<G3Tree :tree-data="treeData" draggable default-expand-all block-node @drop="onDrop" />
|
||||
<p style="margin: 8px 0 0; font-size: 12px; color: #6b7280">拖拽任意节点试试(子节点不能拖进自己的子树)</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
// 展开状态:受控 expandedKeys(传了就是受控)与非受控默认值
|
||||
import { ref } from 'vue'
|
||||
import { G3Button, G3Space, G3Tree, type TreeNodeData } from '@g3soft/ui'
|
||||
|
||||
const treeData: TreeNodeData[] = [
|
||||
{
|
||||
key: 'a',
|
||||
title: 'A 组',
|
||||
children: [
|
||||
{ key: 'a1', title: 'A-1', children: [{ key: 'a11', title: 'A-1-1' }] },
|
||||
{ key: 'a2', title: 'A-2' },
|
||||
],
|
||||
},
|
||||
{ key: 'b', title: 'B 组', children: [{ key: 'b1', title: 'B-1' }] },
|
||||
]
|
||||
|
||||
// 受控:所有展开/收起都要经过 update:expandedKeys
|
||||
const expandedKeys = ref<string[]>(['a', 'a1'])
|
||||
|
||||
function expandAll() {
|
||||
expandedKeys.value = ['a', 'a1', 'a2', 'b', 'b1']
|
||||
}
|
||||
|
||||
function collapseAll() {
|
||||
expandedKeys.value = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<G3Space direction="vertical" :size="12" style="display: flex; max-width: 320px">
|
||||
<G3Space :size="8">
|
||||
<G3Button size="small" type="outline" @click="expandAll">全部展开</G3Button>
|
||||
<G3Button size="small" type="outline" @click="collapseAll">全部收起</G3Button>
|
||||
</G3Space>
|
||||
|
||||
<!-- 传了 expanded-keys 即受控;双击节点也能展开(会走 update 事件) -->
|
||||
<G3Tree v-model:expanded-keys="expandedKeys" :tree-data="treeData" block-node />
|
||||
</G3Space>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
// filter:命中节点与它的祖先都会显示,命中的文字会被高亮,且自动展开命中路径
|
||||
import { ref } from 'vue'
|
||||
import { G3Input, G3Tree, type TreeNodeData } from '@g3soft/ui'
|
||||
|
||||
const treeData: TreeNodeData[] = [
|
||||
{
|
||||
key: 'module',
|
||||
title: '模块管理',
|
||||
children: [
|
||||
{ key: 'module/list', title: '模块列表' },
|
||||
{ key: 'module/field', title: '字段设计' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'system',
|
||||
title: '系统管理',
|
||||
children: [
|
||||
{ key: 'system/user', title: '用户管理' },
|
||||
{ key: 'system/role', title: '角色权限' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const keyword = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="max-width: 320px">
|
||||
<G3Input v-model="keyword" allow-clear placeholder="输入关键词,如「管理」" style="margin-bottom: 8px" />
|
||||
<G3Tree :tree-data="treeData" :filter="keyword" default-expand-all block-node>
|
||||
<template #empty>没有匹配的节点</template>
|
||||
</G3Tree>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
// lazy + load:首次展开没有子节点的节点时请求数据,父层把 children 写回并把 key 记入 loadedKeys
|
||||
import { ref } from 'vue'
|
||||
import { G3Tree, type TreeNodeData } from '@g3soft/ui'
|
||||
|
||||
const treeData = ref<TreeNodeData[]>([
|
||||
{ key: 'org-1', title: '总公司' },
|
||||
{ key: 'org-2', title: '分公司' },
|
||||
])
|
||||
|
||||
const loadedKeys = ref<string[]>([])
|
||||
|
||||
async function onLoad(node: TreeNodeData) {
|
||||
// 模拟请求:200ms 后返回子节点
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
node.children = [
|
||||
{ key: `${node.key}-a`, title: `${node.title} / 部门 A` },
|
||||
{ key: `${node.key}-b`, title: `${node.title} / 部门 B` },
|
||||
]
|
||||
treeData.value = [...treeData.value]
|
||||
loadedKeys.value = [...loadedKeys.value, String(node.key)]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="max-width: 320px">
|
||||
<G3Tree v-model:loaded-keys="loadedKeys" :tree-data="treeData" lazy block-node @load="onLoad" />
|
||||
<p style="margin: 8px 0 0; font-size: 12px; color: #6b7280">已加载:{{ loadedKeys.join(', ') || '无' }}</p>
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user