feat(vue): [mind-map] mindmap (#1207)

* feat(mind-map): drag and zoom

* feat(vue): mindmap component

* fix(renderless): restore tsconfig.json

* fix: remove fabric add mind-elixir

* docs: composition api

* feat(vue): extract inline-style

* refactor(mind-map): delete exportData function

* refactor(mind-map): extract initEvent function

* feat: add lang=ts

* test(mind-map): e2e test

* feat: rename import event

* docs: add en-us doc & change event name

* fix: depends install error

* refactor(mind-map): extract fn to index.ts
This commit is contained in:
GaoNeng 2024-01-04 14:44:32 +08:00 committed by GitHub
parent 2133162169
commit 1e0a83e6f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1104 additions and 3 deletions

View File

@ -0,0 +1,14 @@
<template>
<tiny-mind-map class="mindmap" />
</template>
<script lang="ts" setup>
import { MindMap as TinyMindMap } from '@opentiny/vue'
</script>
<style scoped>
.mindmap {
width: 100%;
height: 300px;
}
</style>

View File

@ -0,0 +1,36 @@
import { test, expect } from '@playwright/test'
test('基本使用', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('mind-map#basic-usage')
const root = page.locator('me-tpc').filter({ hasText: 'root' })
expect(root).not.toBeNull()
await expect(root).toHaveText('root')
})
test('追加节点', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('mind-map#basic-usage')
await page.locator('me-tpc').click()
await page.locator('.map-canvas').press('Tab')
await page.locator('me-tpc').filter({ hasText: 'root' }).click()
await page.locator('.map-canvas').press('Tab')
await expect(page.locator('me-main me-wrapper')).toHaveCount(2)
})
test('修改节点', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('mind-map#basic-usage')
await page.locator('me-tpc').dblclick()
await page.locator('#input-box').fill('root-new')
await page.locator('#input-box').press('Enter')
})
test('删除节点', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('mind-map#basic-usage')
await page.locator('me-tpc').click()
await page.locator('.map-canvas').press('Tab')
await page.locator('me-tpc').filter({ hasText: 'root' }).click()
await page.locator('.map-canvas').press('Tab')
await page.locator('me-tpc').nth(2).press('Delete')
})

View File

@ -0,0 +1,20 @@
<template>
<tiny-mind-map class="mindmap" />
</template>
<script lang="ts">
import { MindMap } from '@opentiny/vue'
export default {
components: {
TinyMindMap: MindMap
}
}
</script>
<style scoped>
.mindmap {
width: 100%;
height: 300px;
}
</style>

View File

@ -0,0 +1,125 @@
<template>
<tiny-mind-map
ref="mindmap"
class="mindmap"
@create="onCreate"
@operation="onOperation"
@select-node="onSelectNode"
@select-new-node="onSelectNewNode"
@select-nodes="onSelectNodes"
@unselect-node="onUnselectNode"
@unselect-nodes="onUnselectNodes"
@expand-node="onExpandNode"
v-model="exampleData"
/>
</template>
<script setup>
import { Notify, MindMap as TinyMindMap } from '@opentiny/vue'
import { ref } from 'vue'
const exmpleData = ref({
'nodeData': {
'id': 'c9ee6647385c42de',
'topic': '前端修仙指南',
'root': true,
'children': [
{
'topic': 'Handfirst html and css',
'id': 'c9ee977189f3b1f1'
},
{
'topic': '高程',
'id': 'c9ee9a4e8f3f83c5'
},
{
'topic': 'Javascript权威指南',
'id': 'c9ee9b8e87958282'
},
{
'topic': '算法 第四版',
'id': 'c9eea19c874d331f'
},
{
'topic': '大话数据结构',
'id': 'c9eea8d788441a71'
},
{
'topic': '算法导论',
'id': 'c9eeac4c84aaba37'
},
{
'topic': '编译原理',
'id': 'c9eeadee881cf229'
},
{
'topic': '宫水三叶的刷题日记',
'id': 'c9eec88a85d8ff76'
}
]
}
})
const onCreate = () => {
Notify({
type: 'info',
message: '触发事件create',
duration: 1000
})
}
const onOperation = () => {
Notify({
type: 'info',
message: '触发事件operation',
duration: 1000
})
}
const onSelectNode = () => {
Notify({
type: 'info',
message: '触发事件selectNode',
duration: 1000
})
}
const onSelectNewNode = () => {
Notify({
type: 'info',
message: '触发事件selectNewNode',
duration: 1000
})
}
const onSelectNodes = () => {
Notify({
type: 'info',
message: '触发事件selectNodes',
duration: 1000
})
}
const onUnselectNode = () => {
Notify({
type: 'info',
message: '触发事件unselectNode',
duration: 1000
})
}
const onUnselectNodes = () => {
Notify({
type: 'info',
message: '触发事件unselectNodes',
duration: 1000
})
}
const onExpandNode = () => {
Notify({
type: 'info',
message: '触发事件expandNode',
duration: 1000
})
}
</script>
<style scoped>
.mindmap {
width: 100%;
height: 300px;
}
</style>

View File

@ -0,0 +1,52 @@
import { test, expect } from '@playwright/test'
test('测试创建事件', async ({ page }) => {
await page.goto('mind-map#event')
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.getByText('触发事件create').isVisible()
})
test('测试operation事件', async ({ page }) => {
await page.goto('mind-map#event')
await page.locator('me-tpc').click()
await page.locator('.map-canvas').press('Tab')
await page.getByText('触发事件operation').first().isVisible()
// 新创建node时会触发selectNewNode事件
// 这个触发是符合逻辑的, 因为创建后的node的确是new-node
// 创建后会自动选择, 自然触发selectNewNode是符合逻辑的
await page.getByText('触发事件selectNewNode').first().isVisible()
})
test('测试select-node, select-nodes, select-new-node事件', async ({ page }) => {
await page.goto('mind-map#event')
await page.locator('me-tpc').filter({ hasText: 'root' }).click()
await page.locator('.map-canvas').press('Tab')
await page.locator('me-tpc').filter({ hasText: 'root' }).click()
await page.locator('.map-canvas').press('Tab')
await page.locator('me-tpc').filter({ hasText: 'root' }).click()
await page
.locator('me-tpc')
.nth(1)
.filter({ hasText: 'new node' })
.click({
modifiers: ['Control']
})
await page.getByText('触发事件selectNode').first().isVisible()
await page.getByAltText('触发事件selectNodes').isVisible()
})
test('测试 unselect-node,unselect-nodes', async ({ page }) => {
await page.goto('mind-map#event')
await page.locator('me-tpc').filter({ hasText: 'root' }).click()
await page.locator('.map-canvas').press('Tab')
await page.locator('me-tpc').filter({ hasText: 'root' }).click()
await page.locator('.map-canvas').press('Tab')
await page.locator('me-tpc').nth(2).click()
await page
.locator('me-tpc')
.nth(1)
.click({
modifiers: ['Control']
})
await page.locator('.map-canvas').click()
})

View File

@ -0,0 +1,136 @@
<template>
<tiny-mind-map
ref="mindmap"
class="mindmap"
@create="onCreate"
@operation="onOperation"
@select-node="onSelectNode"
@select-new-node="onSelectNewNode"
@select-nodes="onSelectNodes"
@unselect-node="onUnselectNode"
@unselect-nodes="onUnselectNodes"
@expand-node="onExpandNode"
v-model="exampleData"
/>
</template>
<script lang="jsx">
import { MindMap, Notify } from '@opentiny/vue'
export default {
components: {
TinyMindMap: MindMap
},
data() {
return {
exampleData: {
'nodeData': {
'id': 'c9ee6647385c42de',
'topic': '前端修仙指南',
'root': true,
'children': [
{
'topic': 'Handfirst html and css',
'id': 'c9ee977189f3b1f1'
},
{
'topic': '高程',
'id': 'c9ee9a4e8f3f83c5'
},
{
'topic': 'Javascript权威指南',
'id': 'c9ee9b8e87958282'
},
{
'topic': '算法 第四版',
'id': 'c9eea19c874d331f'
},
{
'topic': '大话数据结构',
'id': 'c9eea8d788441a71'
},
{
'topic': '算法导论',
'id': 'c9eeac4c84aaba37'
},
{
'topic': '编译原理',
'id': 'c9eeadee881cf229'
},
{
'topic': '宫水三叶的刷题日记',
'id': 'c9eec88a85d8ff76'
}
]
}
},
loading: false
}
},
methods: {
onCreate() {
Notify({
type: 'info',
message: '触发事件create',
duration: 1000
})
},
onOperation() {
Notify({
type: 'info',
message: '触发事件operation',
duration: 1000
})
},
onSelectNode() {
Notify({
type: 'info',
message: '触发事件selectNode',
duration: 1000
})
},
onSelectNewNode() {
Notify({
type: 'info',
message: '触发事件selectNewNode',
duration: 1000
})
},
onSelectNodes() {
Notify({
type: 'info',
message: '触发事件selectNodes',
duration: 1000
})
},
onUnselectNode() {
Notify({
type: 'info',
message: '触发事件unselectNode',
duration: 1000
})
},
onUnselectNodes() {
Notify({
type: 'info',
message: '触发事件unselectNodes',
duration: 1000
})
},
onExpandNode() {
Notify({
type: 'info',
message: '触发事件expandNode',
duration: 1000
})
}
}
}
</script>
<style scoped>
.mindmap {
width: 100%;
height: 300px;
}
</style>

View File

@ -0,0 +1,108 @@
<template>
<tiny-mind-map class="mindmap" ref="mindmap" @create="onCreate" v-model="exampleData" />
<tiny-button @click="exportData">导出数据</tiny-button>
<tiny-button @click="importData" :loading="loading">导入示例数据</tiny-button>
<tiny-button @click="clearData" :loading="loading">清空数据</tiny-button>
</template>
<script setup>
import { MindMap as TinyMindMap, Button as TinyButton, Notify } from '@opentiny/vue'
import { ref } from 'vue'
const render = ref(null)
const exampleData = ref({
'nodeData': {
'id': 'c9ee6647385c42de',
'topic': '前端修仙指南',
'root': true,
'children': [
{
'topic': 'Handfirst html and css',
'id': 'c9ee977189f3b1f1'
},
{
'topic': '高程',
'id': 'c9ee9a4e8f3f83c5'
},
{
'topic': 'Javascript权威指南',
'id': 'c9ee9b8e87958282'
},
{
'topic': '算法 第四版',
'id': 'c9eea19c874d331f'
},
{
'topic': '大话数据结构',
'id': 'c9eea8d788441a71'
},
{
'topic': '算法导论',
'id': 'c9eeac4c84aaba37'
},
{
'topic': '编译原理',
'id': 'c9eeadee881cf229'
},
{
'topic': '宫水三叶的刷题日记',
'id': 'c9eec88a85d8ff76'
}
]
}
})
const loading = ref(false)
const onCreate = (instance) => {
render.value = instance
}
const exportData = () => {
if (render.value) {
Notify({
type: 'info',
message: '数据已经输出于控制台, 请打开控制台查看'
})
// eslint-disable-next-line no-console
console.log(render.value.getData())
}
}
const importData = () => {
if (render.value) {
const fn = async () => {
return new Promise((resolve) => {
setTimeout(() => {
if (render.value) {
render.value.init(exampleData.value)
}
resolve(null)
}, 1000)
})
}
loading.value = true
fn().finally(() => (loading.value = false))
}
}
const clearData = () => {
loading.value = true
const clearNodeData = {
'nodeData': {
'id': 'c9ee6647385c42de',
'topic': '我的子节点被清空啦~',
'root': true,
'children': []
}
}
try {
render.value.init(clearNodeData)
render.value.refresh(clearNodeData)
} finally {
loading.value = false
}
}
</script>
<style scoped>
.mindmap {
width: 100%;
height: 300px;
}
</style>

View File

@ -0,0 +1,19 @@
import { test, expect } from '@playwright/test'
test('导出数据', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('mind-map#export-data')
await page.locator('button').filter({ hasText: '导出数据' }).click()
})
test('导入数据', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('mind-map#export-data')
await page.locator('button').filter({ hasText: '导入示例数据' }).click()
})
test('清空样例数据', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('mind-map#export-data')
await page.locator('button').filter({ hasText: '清空数据' }).click()
})

View File

@ -0,0 +1,119 @@
<template>
<tiny-mind-map class="mindmap" ref="mindmap" @create="onCreate" v-model="exampleData" />
<tiny-button @click="exportData">导出数据</tiny-button>
<tiny-button @click="importData" :loading="loading">导入示例数据</tiny-button>
<tiny-button @click="clearData" :loading="loading">清空数据</tiny-button>
</template>
<script lang="jsx">
import { MindMap, Button, Notify } from '@opentiny/vue'
export default {
components: {
TinyMindMap: MindMap,
TinyButton: Button
},
data() {
return {
render: null,
exampleData: {
'nodeData': {
'id': 'c9ee6647385c42de',
'topic': '前端修仙指南',
'root': true,
'children': [
{
'topic': 'Handfirst html and css',
'id': 'c9ee977189f3b1f1'
},
{
'topic': '高程',
'id': 'c9ee9a4e8f3f83c5'
},
{
'topic': 'Javascript权威指南',
'id': 'c9ee9b8e87958282'
},
{
'topic': '算法 第四版',
'id': 'c9eea19c874d331f'
},
{
'topic': '大话数据结构',
'id': 'c9eea8d788441a71'
},
{
'topic': '算法导论',
'id': 'c9eeac4c84aaba37'
},
{
'topic': '编译原理',
'id': 'c9eeadee881cf229'
},
{
'topic': '宫水三叶的刷题日记',
'id': 'c9eec88a85d8ff76'
}
]
}
},
loading: false
}
},
methods: {
onCreate(instance) {
this.render = instance
},
exportData() {
if (this.render) {
Notify({
type: 'info',
message: '数据已经输出于控制台, 请打开控制台查看'
})
// eslint-disable-next-line no-console
console.log(this.render.getData())
}
},
importData() {
if (this.render) {
const fn = async () => {
return new Promise((resolve) => {
setTimeout(() => {
if (this.render) {
this.render.init(this.exampleData)
}
resolve(null)
}, 1000)
})
}
this.loading = true
fn().finally(() => (this.loading = false))
}
},
clearData() {
this.loading = true
const clearNodeData = {
'nodeData': {
'id': 'c9ee6647385c42de',
'topic': '我的子节点被清空啦~',
'root': true,
'children': []
}
}
try {
this.render.init(clearNodeData)
this.render.refresh(clearNodeData)
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.mindmap {
width: 100%;
height: 300px;
}
</style>

View File

@ -0,0 +1,7 @@
---
title: MindMap 脑图
---
# MindMap 脑图
思维导图组件,默认启用了拖拽与缩放

View File

@ -0,0 +1,7 @@
---
title: MindMap
---
# MindMap 脑图
MindMap component. Dragging and scaling are enabled by default

View File

@ -0,0 +1,250 @@
export default {
column: '2',
owner: '',
demos: [
{
demoId: 'basic-usage',
name: { 'zh-CN': '基本用法', 'en-US': 'Basic Usage' },
codeFiles: ['basic-usage.vue'],
desc: {
'zh-CN': '本示例介绍了脑图的基本使用方法',
'en-US': 'This example introduces the basic usage of mind map'
}
},
{
demoId: 'export-data',
name: { 'zh-CN': '导出数据', 'en-US': 'Export data' },
codeFiles: ['export-data.vue'],
desc: {
'zh-CN': '本demo讲解了如何导入导出脑图',
'en-US': 'This demo introduces how to import/exprot mind map data'
}
},
{
demoId: 'event',
name: { 'zh-CN': '事件触发', 'en-US': 'Event' },
codeFiles: ['event.vue'],
desc: {
'zh-CN': '本demo讲解了该组件的所有可触发事件',
'en-US': 'This demo explains all the triggering events of the build'
}
}
],
apis: [
{
name: 'mind-map',
type: 'component',
props: [
{
name: 'modelValue',
type: 'NodeObj',
defaultValue: '{}',
desc: {
'zh-CN': '默认节点数据',
'en-US': 'Default node data'
},
demoId: 'basic-usage'
},
{
name: 'options',
type: 'Options',
defaultValue: '{contextMenu: false,toolBar: false,nodeMenu: false}',
desc: {
'zh-CN': '配置项',
'en-US': 'options'
}
}
],
events: [
{
name: 'operation',
type: 'onOperation',
'desc': {
'zh-CN': '节点重新计算时, 例如将节点A拖拽到节点B, 使得节点A是节点B的子节点',
'en-US':
'When recalculating nodes, for example, dragging node A to node B so that node A is a child node of node B'
}
},
{
name: 'create',
type: '(render:MindElixirInstance)=>void',
'desc': {
'zh-CN': 'mindmap创建时会触发该事件',
'en-US': 'This event will be triggered when creating mindmap'
}
},
{
name: 'selectNode',
type: 'onSelectNode',
'desc': {
'zh-CN': '选择任意一个节点时, 会触发该事件',
'en-US': 'When selecting any node, this event will be triggered'
}
},
{
name: 'selectNewNode',
type: 'onSelectNewNode',
'desc': {
'zh-CN': '创建新节点时',
'en-US': 'when create new node'
}
},
{
name: 'selectNodes',
type: 'onSelectNodes',
'desc': {
'zh-CN': '选择多个节点的时候会触发该事件',
'en-US': 'When selecting multiple nodes, this event will be triggered'
}
},
{
name: 'unselectNode',
type: 'onUnselectNode',
'desc': {
'zh-CN': '取消选择的时候会触发该事件',
'en-US': 'When deselecting, this event will be triggered'
}
},
{
name: 'unselectNodes',
type: 'onUnselectNode',
'desc': {
'zh-CN': '取消选择多个节点时会触发该事件',
'en-US': 'This event will be triggered when multiple nodes are unselected'
}
},
{
name: 'expandNode',
type: 'onExpandNode',
'desc': {
'zh-CN': '展开节点时会触发该事件',
'en-US': 'This event will be triggered when expanding a node'
}
},
{
name: 'beforeImport',
type: '({render, data}: {render:MindElixirInstance, data: })=>void',
'desc': {
'zh-CN': 'v-model更新前会触发',
'en-US': 'Triggered before updating the v-model'
}
},
{
name: 'afterImport',
type: '({render, data}: {render:MindElixirInstance, data: })=>void',
'desc': {
'zh-CN': 'v-model更新后会触发',
'en-US': 'After updating the v-model, it will trigger'
}
}
]
}
],
types: [
{
'name': 'Options',
type: 'interface',
code: `
interface Options {
direction?: number
locale?: string
draggable?: boolean
editable?: boolean
contextMenu?: boolean
contextMenuOption?: any
toolBar?: boolean
keypress?: boolean
mouseSelectionButton?: 0 | 2
before?: Before
newTopicName?: string
allowUndo?: boolean
overflowHidden?: boolean
mainLinkStyle?: number
subLinkStyle?: number
mobileMenu?: boolean
theme?: Theme
nodeMenu?: boolean
}
`
},
{
name: 'onOperation',
type: 'type',
code: `
type onOperation = ({render, info}: {render:MindElixirInstance, info: Operation}) => void
`
},
{
name: 'onSelectNode',
type: 'type',
code: `
type onSelectNode = ({render,nodeObj}: {render:MindElixirInstance,nodeObj:NodeObj}, e?: MouseEvent) => void
`
},
{
name: 'onSelectNewNode',
type: 'type',
code: `
type selectNewNode: ({render,nodeObj}: {render:MindElixirInstance,nodeObj:NodeObj}) => void
`
},
{
name: 'onSelectNodes',
type: 'type',
code: `
type selectNodes: ({render,nodeObj}: {render:MindElixirInstance,nodeObj:NodeObj[]}) => void
`
},
{
name: 'onUnselectNode',
type: 'type',
code: `
type unselectNodes: ({render}: {render: MindElixirInstance}) => void
`
},
{
name: 'onUnselectNodes',
type: 'type',
code: `
type unselectNodes: ({render}: {render: MindElixirInstance}) => void
`
},
{
name: 'onExpandNode',
type: 'type',
code: `
type expandNode: ({render,nodeObj}: {render:MindElixirInstance,nodeObj:NodeObj}) => void
`
},
{
name: 'NodeObj',
type: 'interface',
code: `
export interface NodeObj {
topic: string
id: Uid
style?: {
fontSize?: string
color?: string
background?: string
fontWeight?: string
}
children?: NodeObj[]
tags?: string[]
icons?: string[]
hyperLink?: string
expanded?: boolean
direction?: number
root?: boolean
image?: {
url: string
width: number
height: number
}
branchColor?: string
parent?: NodeObj
}
`
}
]
}

View File

@ -288,6 +288,11 @@ export const cmpMenus = [
'mark': {
'text': 'New'
}
},
{
'nameCn': '脑图',
'name': 'mind-map',
'key': 'mind-map'
}
]
}

View File

@ -1609,6 +1609,14 @@
"type": "template",
"exclude": false
},
"MindMap": {
"path": "vue/src/mind-map/src/pc.vue",
"type": "component",
"exclude": false,
"mode": [
"pc"
]
},
"MiniPicker": {
"path": "vue/src/mini-picker/index.ts",
"type": "component",

View File

@ -0,0 +1,42 @@
export const importData = (instance, data: Object) => {
instance.init(data)
}
export const initEvent = (render, emit) => {
const onOperation = (info) => {
emit('operation', { render, info })
}
const onSelectNode = (nodeObj, e?: MouseEvent) => {
emit('selectNode', { render, nodeObj, e })
}
const selectNewNode = (nodeObj) => {
emit('selectNewNode', { render, nodeObj })
}
const onSelectNodes = (nodeObj) => {
emit('selectNodes', { render, nodeObj })
}
const unselectNode = () => {
emit('unselectNode', { render })
}
const unselectNodes = () => {
emit('unselectNodes', { render })
}
const expandNode = (nodeObj) => {
emit('expandNode', { render, nodeObj })
}
render.bus.addListener('operation', onOperation)
render.bus.addListener('selectNode', onSelectNode)
render.bus.addListener('selectNewNode', selectNewNode)
render.bus.addListener('selectNodes', onSelectNodes)
render.bus.addListener('unselectNode', unselectNode)
render.bus.addListener('unselectNodes', unselectNodes)
render.bus.addListener('expandNode', expandNode)
return () => {
render.bus.removeListener('operation', onOperation)
render.bus.removeListener('selectNode', onSelectNode)
render.bus.removeListener('selectNewNode', selectNewNode)
render.bus.removeListener('selectNodes', onSelectNodes)
render.bus.removeListener('unselectNode', unselectNode)
render.bus.removeListener('unselectNodes', unselectNodes)
render.bus.removeListener('expandNode', expandNode)
}
}

View File

@ -0,0 +1,55 @@
import type { ISharedRenderlessParamHooks, IMindMapProps } from '@/types'
import { importData, initEvent } from '.'
export const api = []
export const renderless = (
props: IMindMapProps,
{ getCurrentInstance, onMounted, onUnmounted, watch }: ISharedRenderlessParamHooks,
{ emit },
{
MindElixir
}: {
MindElixir
}
) => {
const api: Record<string, any> = {}
let destoryListener: (() => void) | null = null
onMounted(() => {
const instance = getCurrentInstance()
if (!instance) {
throw new Error(
'Can not find instance. Please open Issue: https://github.com/opentiny/tiny-vue/issues/new/choose'
)
}
const mindmap: HTMLElement = instance.refs.mindmap as HTMLElement
const render = new MindElixir({
contextMenu: false,
toolBar: false,
nodeMenu: false,
...(props.options ?? {}),
el: mindmap
})
destoryListener = initEvent(render, emit)
emit('create', render)
watch(
() => props.modelValue,
() => {
if (props.modelValue) {
emit('beforeImport', { render, data: props.modelValue })
importData(render, props.modelValue)
emit('afterImport', { render, data: props.modelValue })
} else {
const root = MindElixir.new('root')
render.init(root)
}
},
{ deep: true, immediate: true }
)
})
onUnmounted(() => {
destoryListener?.()
})
return api
}

View File

@ -23,7 +23,5 @@
]
}
},
"include": [
"src/**/*.ts"
, "src/button/react.js" ]
"include": ["src/**/*.ts","src/button/react.js"]
}

View File

@ -206,3 +206,4 @@ export * from './year-table.type'
export * from './color-picker.type'
export * from './color-select-panel.type'
export * from './label.type'
export * from './mind-map.type'

View File

@ -0,0 +1,4 @@
import type { MindMapProps } from '@/mind-map/src'
import type { ExtractPropTypes } from 'vue'
export type IMindMapProps = ExtractPropTypes<typeof MindMapProps>

View File

@ -160,6 +160,7 @@
"@opentiny/vue-menu": "workspace:~",
"@opentiny/vue-message": "workspace:~",
"@opentiny/vue-milestone": "workspace:~",
"@opentiny/vue-mind-map": "workspace:~",
"@opentiny/vue-mini-picker": "workspace:~",
"@opentiny/vue-modal": "workspace:~",
"@opentiny/vue-month-range": "workspace:~",

View File

@ -0,0 +1,24 @@
import MindMap from './src/index'
import '@opentiny/vue-theme/mind-map/index.less'
import { version } from './package.json'
MindMap.model = {
prop: 'modelValue',
event: 'update:modelValue'
}
/* istanbul ignore next */
MindMap.install = function (Vue) {
Vue.component(MindMap.name, MindMap)
}
MindMap.version = version
/* istanbul ignore next */
if (process.env.BUILD_TARGET === 'runtime') {
if (typeof window !== 'undefined' && window.Vue) {
MindMap.install(window.Vue)
}
}
export default MindMap

View File

@ -0,0 +1,19 @@
{
"name": "@opentiny/vue-mind-map",
"version": "5.12.0",
"description": "",
"main": "lib/index.js",
"module": "index.ts",
"sideEffects": false,
"type": "module",
"dependencies": {
"@opentiny/vue-common": "workspace:~",
"@opentiny/vue-locale": "workspace:~",
"@opentiny/vue-renderless": "workspace:~",
"@opentiny/vue-theme": "workspace:~"
},
"license": "MIT",
"devDependencies": {
"mind-elixir": "^3.3.2"
}
}

View File

@ -0,0 +1,22 @@
import { $props, $setup, $prefix, defineComponent } from '@opentiny/vue-common'
import template from 'virtual-template?pc'
const $constants = {}
export const MindMapProps = {
...$props,
_constants: {
type: Object,
default: () => $constants
},
modelValue: Object,
options: Object
}
export default defineComponent({
name: $prefix + 'MindMap',
props: MindMapProps,
setup(props, context) {
return $setup({ props, context, template })
}
})

View File

@ -0,0 +1,29 @@
<template>
<div ref="mindmap" class="tiny-mind-map"></div>
</template>
<script lang="ts">
import { renderless, api } from '@opentiny/vue-renderless/mind-map/vue'
import { props, setup, defineComponent } from '@opentiny/vue-common'
import MindElixir from 'mind-elixir'
export default defineComponent({
emits: [
'update:modelValue',
'operation',
'selectNode',
'selectNewNode',
'selectNodes',
'unselectNode',
'unselectNodes',
'expandNode,',
'beforeImport',
'afterImport,',
'create'
],
props: [...props, 'modelValue', 'options'],
setup(props, context) {
return setup({ props, context, renderless, api, mono: false, extendOptions: { MindElixir } })
}
})
</script>