forked from opentiny/tiny-vue
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:
parent
2133162169
commit
1e0a83e6f0
|
@ -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>
|
|
@ -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')
|
||||
})
|
|
@ -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>
|
|
@ -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>
|
|
@ -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()
|
||||
})
|
|
@ -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>
|
|
@ -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>
|
|
@ -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()
|
||||
})
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: MindMap 脑图
|
||||
---
|
||||
|
||||
# MindMap 脑图
|
||||
|
||||
思维导图组件,默认启用了拖拽与缩放
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: MindMap
|
||||
---
|
||||
|
||||
# MindMap 脑图
|
||||
|
||||
MindMap component. Dragging and scaling are enabled by default
|
|
@ -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
|
||||
}
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
|
@ -288,6 +288,11 @@ export const cmpMenus = [
|
|||
'mark': {
|
||||
'text': 'New'
|
||||
}
|
||||
},
|
||||
{
|
||||
'nameCn': '脑图',
|
||||
'name': 'mind-map',
|
||||
'key': 'mind-map'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -23,7 +23,5 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
, "src/button/react.js" ]
|
||||
"include": ["src/**/*.ts","src/button/react.js"]
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import type { MindMapProps } from '@/mind-map/src'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
|
||||
export type IMindMapProps = ExtractPropTypes<typeof MindMapProps>
|
|
@ -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:~",
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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 })
|
||||
}
|
||||
})
|
|
@ -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>
|
Loading…
Reference in New Issue