@ -1,150 +0,0 @@ |
|||
<template> |
|||
<div class="control-container"> |
|||
<!-- 功能按钮 --> |
|||
<ul> |
|||
<li |
|||
v-for="(item, key) in titleLists" |
|||
:key="key" |
|||
:title="item.text" |
|||
@mouseenter.prevent="onEnter(key)" |
|||
@mouseleave.prevent="focusIndex = -1" |
|||
> |
|||
<a-button |
|||
:disabled="item.disabled" |
|||
:style="{ cursor: item.disabled === false ? 'pointer' : 'not-allowed' }" |
|||
@click="onControl(item, key)" |
|||
> |
|||
<span :class="'iconfont ' + item.icon"></span> |
|||
<p>{{ item.text }}</p> |
|||
</a-button> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { defineComponent, ref, unref, onMounted } from 'vue'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'Control', |
|||
props: { |
|||
lf: Object || String, |
|||
catTurboData: Boolean, |
|||
}, |
|||
emits: ['catData'], |
|||
setup(props, { emit }) { |
|||
let focusIndex = ref(-1); |
|||
let titleLists = ref([ |
|||
{ |
|||
icon: 'icon-zoom-out-hs', |
|||
text: '缩小', |
|||
disabled: false, |
|||
}, |
|||
{ |
|||
icon: 'icon-enlarge-hs', |
|||
text: '放大', |
|||
disabled: false, |
|||
}, |
|||
{ |
|||
icon: 'icon-full-screen-hs', |
|||
text: '适应', |
|||
disabled: false, |
|||
}, |
|||
{ |
|||
icon: 'icon-previous-hs', |
|||
text: '上一步', |
|||
disabled: true, |
|||
}, |
|||
{ |
|||
icon: 'icon-next-step-hs', |
|||
text: '下一步', |
|||
disabled: true, |
|||
}, |
|||
{ |
|||
icon: 'icon-download-hs', |
|||
text: '下载图片', |
|||
disabled: false, |
|||
}, |
|||
{ |
|||
icon: 'icon-watch-hs', |
|||
text: '查看数据', |
|||
disabled: false, |
|||
}, |
|||
]); |
|||
|
|||
const onControl = (item, key) => { |
|||
['zoom', 'zoom', 'resetZoom', 'undo', 'redo', 'getSnapshot'].forEach((v, i) => { |
|||
let domControl = props.lf; |
|||
if (key === 1) { |
|||
domControl.zoom(true); |
|||
} |
|||
if (key === 6) { |
|||
emit('catData'); |
|||
} |
|||
if (key === i) { |
|||
domControl[v](); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const onEnter = (key) => { |
|||
focusIndex.value = key; |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
props.lf.on('history:change', ({ data: { undoAble, redoAble } }) => { |
|||
unref(titleLists)[3].disabled = !undoAble; |
|||
unref(titleLists)[4].disabled = !redoAble; |
|||
}); |
|||
}); |
|||
|
|||
return { |
|||
focusIndex, |
|||
titleLists, |
|||
onControl, |
|||
onEnter, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
@import './assets/iconfont/iconfont.css'; |
|||
|
|||
.control-container { |
|||
position: absolute; |
|||
right: 20px; |
|||
background: hsla(0, 0%, 100%, 0.8); |
|||
box-shadow: 0 1px 4px rgb(0 0 0 / 30%); |
|||
} |
|||
|
|||
.iconfont { |
|||
font-size: 25px; |
|||
} |
|||
|
|||
.control-container p { |
|||
margin: 0; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.control-container ul { |
|||
display: flex; |
|||
justify-content: space-around; |
|||
align-items: center; |
|||
margin: 2px; |
|||
} |
|||
|
|||
.control-container ul li { |
|||
width: 60px; |
|||
text-align: center; |
|||
} |
|||
|
|||
.control-container ul li button { |
|||
width: 100%; |
|||
height: 60px; |
|||
padding: 0; |
|||
background-color: transparent; |
|||
border: none; |
|||
outline: none; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,156 @@ |
|||
<template> |
|||
<div :class="`${prefixCls}-toolbar`" class="flex items-center px-2 py-1"> |
|||
<template v-for="(item, index) in toolbarItemList" :key="item.type || index"> |
|||
<Tooltip placement="bottom" v-bind="item.disabled ? { visible: false } : {}"> |
|||
<template #title>{{ item.tooltip }}</template> |
|||
<span :class="`${prefixCls}-toolbar__icon`" v-if="item.icon" @click="onControl(item)"> |
|||
<Icon |
|||
:icon="item.icon" |
|||
:class="item.disabled ? 'cursor-not-allowed disabeld' : 'cursor-pointer'" |
|||
/> |
|||
</span> |
|||
</Tooltip> |
|||
<Divider v-if="item.separate" type="vertical" /> |
|||
</template> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { ToolbarConfig } from './types'; |
|||
|
|||
import { defineComponent, ref, onUnmounted, unref, nextTick, watchEffect } from 'vue'; |
|||
import { Divider, Tooltip } from 'ant-design-vue'; |
|||
import { Icon } from '/@/components/Icon'; |
|||
|
|||
import { useFlowChartContext } from './useFlowContext'; |
|||
import { ToolbarTypeEnum } from './enum'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'FlowChartToolbar', |
|||
components: { Icon, Divider, Tooltip }, |
|||
props: { |
|||
prefixCls: String, |
|||
}, |
|||
setup(_, { emit }) { |
|||
const toolbarItemList = ref<ToolbarConfig[]>([ |
|||
{ |
|||
type: ToolbarTypeEnum.ZOOM_IN, |
|||
icon: 'codicon:zoom-out', |
|||
tooltip: '缩小', |
|||
}, |
|||
{ |
|||
type: ToolbarTypeEnum.ZOOM_OUT, |
|||
icon: 'codicon:zoom-in', |
|||
tooltip: '放大', |
|||
}, |
|||
{ |
|||
type: ToolbarTypeEnum.RESET_ZOOM, |
|||
icon: 'codicon:screen-normal', |
|||
tooltip: '重置比例', |
|||
}, |
|||
{ separate: true }, |
|||
{ |
|||
type: ToolbarTypeEnum.UNDO, |
|||
icon: 'ion:arrow-undo-outline', |
|||
tooltip: '后退', |
|||
disabled: true, |
|||
}, |
|||
{ |
|||
type: ToolbarTypeEnum.REDO, |
|||
icon: 'ion:arrow-redo-outline', |
|||
tooltip: '前进', |
|||
disabled: true, |
|||
}, |
|||
{ separate: true }, |
|||
{ |
|||
type: ToolbarTypeEnum.SNAPSHOT, |
|||
icon: 'ion:download-outline', |
|||
tooltip: '下载', |
|||
}, |
|||
{ |
|||
type: ToolbarTypeEnum.VIEW_DATA, |
|||
icon: 'carbon:document-view', |
|||
tooltip: '查看数据', |
|||
}, |
|||
]); |
|||
|
|||
const { logicFlow } = useFlowChartContext(); |
|||
|
|||
function onHistoryChange({ data: { undoAble, redoAble } }) { |
|||
const itemsList = unref(toolbarItemList); |
|||
const undoIndex = itemsList.findIndex((item) => item.type === ToolbarTypeEnum.UNDO); |
|||
const redoIndex = itemsList.findIndex((item) => item.type === ToolbarTypeEnum.REDO); |
|||
if (undoIndex !== -1) { |
|||
unref(toolbarItemList)[undoIndex].disabled = !undoAble; |
|||
} |
|||
if (redoIndex !== -1) { |
|||
unref(toolbarItemList)[redoIndex].disabled = !redoAble; |
|||
} |
|||
} |
|||
|
|||
const onControl = (item) => { |
|||
const lf = unref(logicFlow); |
|||
if (!lf) { |
|||
return; |
|||
} |
|||
switch (item.type) { |
|||
case ToolbarTypeEnum.ZOOM_IN: |
|||
lf.zoom(); |
|||
break; |
|||
case ToolbarTypeEnum.ZOOM_OUT: |
|||
lf.zoom(true); |
|||
break; |
|||
case ToolbarTypeEnum.RESET_ZOOM: |
|||
lf.resetZoom(); |
|||
break; |
|||
case ToolbarTypeEnum.UNDO: |
|||
lf.undo(); |
|||
break; |
|||
case ToolbarTypeEnum.REDO: |
|||
lf.redo(); |
|||
break; |
|||
case ToolbarTypeEnum.SNAPSHOT: |
|||
lf.getSnapshot(); |
|||
break; |
|||
case ToolbarTypeEnum.VIEW_DATA: |
|||
emit('catData'); |
|||
break; |
|||
} |
|||
}; |
|||
|
|||
watchEffect(async () => { |
|||
if (unref(logicFlow)) { |
|||
await nextTick(); |
|||
unref(logicFlow)?.on('history:change', onHistoryChange); |
|||
} |
|||
}); |
|||
|
|||
onUnmounted(() => { |
|||
unref(logicFlow)?.off('history:change', onHistoryChange); |
|||
}); |
|||
return { toolbarItemList, onControl }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less" scoped> |
|||
@prefix-cls: ~'@{namespace}-flow-chart-toolbar'; |
|||
|
|||
.@{prefix-cls} { |
|||
height: 36px; |
|||
background: @content-background; |
|||
border-bottom: 1px solid @border-color-base; |
|||
|
|||
.disabeld { |
|||
color: @disabled-color; |
|||
} |
|||
|
|||
&__icon { |
|||
display: inline-block; |
|||
padding: 2px 4px; |
|||
margin-right: 10px; |
|||
|
|||
&:hover { |
|||
color: @primary-color; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,145 +0,0 @@ |
|||
<template> |
|||
<!-- 左侧bpmn元素选择器 --> |
|||
<div class="node-panel"> |
|||
<div |
|||
class="node-item" |
|||
v-for="item in nodeList" |
|||
:key="item.text" |
|||
@mousedown="nodeDragNode(item)" |
|||
> |
|||
<div class="node-item-icon" :class="item.class"> |
|||
<div v-if="item.type === 'user' || item.type === 'time'" class="shape"></div> |
|||
</div> |
|||
<span class="node-label">{{ item.text }}</span> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { defineComponent, ref, unref } from 'vue'; |
|||
export default defineComponent({ |
|||
name: 'NodePanel', |
|||
props: { |
|||
lf: Object, |
|||
nodeList: Array, |
|||
}, |
|||
setup(props) { |
|||
let node = ref({ |
|||
type: 'rect', |
|||
property: { |
|||
a: 'efrwe', |
|||
b: 'wewe', |
|||
}, |
|||
}); |
|||
let properties = ref({ |
|||
a: 'efrwe', |
|||
b: 'wewe', |
|||
}); |
|||
|
|||
const nodeDragNode = (item) => { |
|||
props.lf.dnd.startDrag({ |
|||
type: item.type, |
|||
properties: unref(properties), |
|||
}); |
|||
}; |
|||
|
|||
return { |
|||
node, |
|||
properties, |
|||
nodeDragNode, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.node-panel { |
|||
position: absolute; |
|||
top: 100px; |
|||
left: 50px; |
|||
z-index: 101; |
|||
width: 70px; |
|||
padding: 20px 10px; |
|||
text-align: center; |
|||
background-color: white; |
|||
border-radius: 6px; |
|||
box-shadow: 0 0 10px 1px rgb(228, 224, 219); |
|||
} |
|||
|
|||
.node-item { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.node-item-icon { |
|||
display: flex; |
|||
height: 30px; |
|||
background-size: cover; |
|||
flex-wrap: wrap; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.node-label { |
|||
margin-top: 5px; |
|||
font-size: 12px; |
|||
user-select: none; |
|||
} |
|||
|
|||
.node-start { |
|||
background: url('./background/start.png') no-repeat; |
|||
background-size: cover; |
|||
} |
|||
|
|||
.node-rect { |
|||
border: 1px solid black; |
|||
} |
|||
|
|||
.node-user { |
|||
background: url('./background/user.png') no-repeat; |
|||
background-size: cover; |
|||
} |
|||
|
|||
.node-time { |
|||
background: url('./background/time.png') no-repeat; |
|||
background-size: cover; |
|||
} |
|||
|
|||
.node-push { |
|||
background: url('./background/push.png') no-repeat; |
|||
background-size: cover; |
|||
} |
|||
|
|||
.node-download { |
|||
background: url('./background/download.png') no-repeat; |
|||
background-size: cover; |
|||
} |
|||
|
|||
.node-click { |
|||
background: url('./background/click.png') no-repeat; |
|||
background-size: cover; |
|||
} |
|||
|
|||
.node-end { |
|||
background: url('./background/end.png') no-repeat; |
|||
background-size: cover; |
|||
} |
|||
|
|||
.bpmn-start { |
|||
cursor: grab; |
|||
background: url('./assets/background/bpmn-start.png') center center no-repeat; |
|||
} |
|||
|
|||
.bpmn-end { |
|||
cursor: grab; |
|||
background: url('./assets/background/bpmn-end.png') center center no-repeat; |
|||
} |
|||
|
|||
.bpmn-user { |
|||
cursor: grab; |
|||
background: url('./assets/background/bpmn-user.png') center center no-repeat; |
|||
} |
|||
|
|||
.bpmn-exclusiveGateway { |
|||
cursor: grab; |
|||
background: url('./assets/background/bpmn-exclusiveGateway.png') center center no-repeat; |
|||
} |
|||
</style> |
|||
|
Before Width: | Height: | Size: 921 B |
|
Before Width: | Height: | Size: 830 B |
|
Before Width: | Height: | Size: 697 B |
|
Before Width: | Height: | Size: 754 B |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 40 KiB |
@ -1,48 +0,0 @@ |
|||
@font-face { |
|||
font-family: 'iconfont'; |
|||
src: url('iconfont.eot?t=1618544337340'); /* IE9 */ |
|||
src: url('iconfont.eot?t=1618544337340#iefix') format('embedded-opentype'), |
|||
/* IE6-IE8 */ |
|||
url('data:application/x-font-woff2;charset=utf-8;base64,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') |
|||
format('woff2'), |
|||
url('iconfont.woff?t=1618544337340') format('woff'), |
|||
url('iconfont.ttf?t=1618544337340') format('truetype'), |
|||
/* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ |
|||
url('iconfont.svg?t=1618544337340#iconfont') format('svg'); /* iOS 4.1- */ |
|||
} |
|||
|
|||
.iconfont { |
|||
font-family: 'iconfont' !important; |
|||
font-size: 16px; |
|||
font-style: normal; |
|||
-webkit-font-smoothing: antialiased; |
|||
-moz-osx-font-smoothing: grayscale; |
|||
} |
|||
|
|||
.icon-full-screen-hs:before { |
|||
content: '\e656'; |
|||
} |
|||
|
|||
.icon-watch-hs:before { |
|||
content: '\e766'; |
|||
} |
|||
|
|||
.icon-download-hs:before { |
|||
content: '\e6af'; |
|||
} |
|||
|
|||
.icon-enlarge-hs:before { |
|||
content: '\e765'; |
|||
} |
|||
|
|||
.icon-previous-hs:before { |
|||
content: '\e84c'; |
|||
} |
|||
|
|||
.icon-zoom-out-hs:before { |
|||
content: '\e744'; |
|||
} |
|||
|
|||
.icon-next-step-hs:before { |
|||
content: '\e84b'; |
|||
} |
|||
@ -1,58 +0,0 @@ |
|||
{ |
|||
"id": "2491438", |
|||
"name": "liu'c'tu", |
|||
"font_family": "iconfont", |
|||
"css_prefix_text": "icon-", |
|||
"description": "", |
|||
"glyphs": [ |
|||
{ |
|||
"icon_id": "755619", |
|||
"name": "自适应图标", |
|||
"font_class": "full-screen-hs", |
|||
"unicode": "e656", |
|||
"unicode_decimal": 58966 |
|||
}, |
|||
{ |
|||
"icon_id": "14445801", |
|||
"name": "查看", |
|||
"font_class": "watch-hs", |
|||
"unicode": "e766", |
|||
"unicode_decimal": 59238 |
|||
}, |
|||
{ |
|||
"icon_id": "9712640", |
|||
"name": "下载", |
|||
"font_class": "download-hs", |
|||
"unicode": "e6af", |
|||
"unicode_decimal": 59055 |
|||
}, |
|||
{ |
|||
"icon_id": "1029099", |
|||
"name": "放大", |
|||
"font_class": "enlarge-hs", |
|||
"unicode": "e765", |
|||
"unicode_decimal": 59237 |
|||
}, |
|||
{ |
|||
"icon_id": "20017362", |
|||
"name": "上一步", |
|||
"font_class": "previous-hs", |
|||
"unicode": "e84c", |
|||
"unicode_decimal": 59468 |
|||
}, |
|||
{ |
|||
"icon_id": "1010015", |
|||
"name": "缩小", |
|||
"font_class": "zoom-out-hs", |
|||
"unicode": "e744", |
|||
"unicode_decimal": 59204 |
|||
}, |
|||
{ |
|||
"icon_id": "20017363", |
|||
"name": "下一步", |
|||
"font_class": "next-step-hs", |
|||
"unicode": "e84b", |
|||
"unicode_decimal": 59467 |
|||
} |
|||
] |
|||
} |
|||
|
Before Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,11 @@ |
|||
export enum ToolbarTypeEnum { |
|||
ZOOM_IN = 'zoomIn', |
|||
ZOOM_OUT = 'zoomOut', |
|||
RESET_ZOOM = 'resetZoom', |
|||
|
|||
UNDO = 'undo', |
|||
REDO = 'redo', |
|||
|
|||
SNAPSHOT = 'snapshot', |
|||
VIEW_DATA = 'viewData', |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
<template> |
|||
<div class="h-full" :class="prefixCls"> |
|||
<FlowChartToolbar :prefixCls="prefixCls" v-if="toolbar" /> |
|||
<div ref="lfElRef" class="h-full"></div> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { Definition } from '@logicflow/core'; |
|||
|
|||
import { defineComponent, ref, onMounted, unref, nextTick, computed, watch } from 'vue'; |
|||
|
|||
import FlowChartToolbar from './FlowChartToolbar.vue'; |
|||
import LogicFlow from '@logicflow/core'; |
|||
import { Snapshot, BpmnElement, Menu, DndPanel } from '@logicflow/extension'; |
|||
|
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { createFlowChartContext } from './useFlowContext'; |
|||
|
|||
import { toLogicFlowData } from './adpterForTurbo'; |
|||
|
|||
import '@logicflow/core/dist/style/index.css'; |
|||
import '@logicflow/extension/lib/style/index.css'; |
|||
export default defineComponent({ |
|||
name: 'FlowChart', |
|||
components: { FlowChartToolbar }, |
|||
props: { |
|||
flowOptions: { |
|||
type: Object as PropType<Definition>, |
|||
default: () => {}, |
|||
}, |
|||
|
|||
data: { |
|||
type: Object as PropType<any>, |
|||
default: () => {}, |
|||
}, |
|||
|
|||
toolbar: { |
|||
type: Boolean, |
|||
default: true, |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
const lfElRef = ref<ElRef>(null); |
|||
|
|||
const lfInstance = ref<Nullable<LogicFlow>>(null); |
|||
|
|||
const { prefixCls } = useDesign('flow-chart'); |
|||
createFlowChartContext({ |
|||
logicFlow: (lfInstance as unknown) as LogicFlow, |
|||
}); |
|||
|
|||
const getFlowOptions = computed(() => { |
|||
const { flowOptions } = props; |
|||
|
|||
const defaultOptions: Partial<Definition> = { |
|||
grid: true, |
|||
background: { |
|||
color: '#f7f9ff', |
|||
}, |
|||
keyboard: { |
|||
enabled: true, |
|||
}, |
|||
...flowOptions, |
|||
}; |
|||
return defaultOptions as Definition; |
|||
}); |
|||
|
|||
watch( |
|||
() => props.data, |
|||
() => { |
|||
onRender(); |
|||
} |
|||
); |
|||
|
|||
watch( |
|||
() => props.flowOptions, |
|||
(options) => { |
|||
unref(lfInstance)?.updateEditConfig(options); |
|||
} |
|||
); |
|||
|
|||
// init logicFlow |
|||
async function init() { |
|||
await nextTick(); |
|||
|
|||
const lfEl = unref(lfElRef); |
|||
if (!lfEl) { |
|||
return; |
|||
} |
|||
|
|||
// Canvas configuration |
|||
LogicFlow.use(Snapshot); |
|||
// Use the bpmn plug-in to introduce bpmn elements, which can be used after conversion in turbo |
|||
LogicFlow.use(BpmnElement); |
|||
// Start the right-click menu |
|||
LogicFlow.use(Menu); |
|||
LogicFlow.use(DndPanel); |
|||
lfInstance.value = new LogicFlow({ |
|||
...unref(getFlowOptions), |
|||
container: lfEl, |
|||
}); |
|||
unref(lfInstance)?.setDefaultEdgeType('line'); |
|||
onRender(); |
|||
} |
|||
|
|||
async function onRender() { |
|||
await nextTick(); |
|||
const lf = unref(lfInstance); |
|||
if (!lf) { |
|||
return; |
|||
} |
|||
const lFData = toLogicFlowData(props.data); |
|||
lf.render(lFData); |
|||
} |
|||
|
|||
onMounted(init); |
|||
|
|||
return { |
|||
prefixCls, |
|||
lfElRef, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,14 @@ |
|||
import { NodeConfig } from '@logicflow/core'; |
|||
import { ToolbarTypeEnum } from './enum'; |
|||
|
|||
export interface NodeItem extends NodeConfig { |
|||
icon: string; |
|||
} |
|||
|
|||
export interface ToolbarConfig { |
|||
type?: string | ToolbarTypeEnum; |
|||
tooltip?: string | boolean; |
|||
icon?: string; |
|||
disabled?: boolean; |
|||
separate?: boolean; |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
import type LogicFlow from '@logicflow/core'; |
|||
|
|||
import { provide, inject } from 'vue'; |
|||
|
|||
const key = Symbol('flow-chart'); |
|||
|
|||
type Instance = { |
|||
logicFlow: LogicFlow; |
|||
}; |
|||
|
|||
export function createFlowChartContext(instance: Instance) { |
|||
provide(key, instance); |
|||
} |
|||
|
|||
export function useFlowChartContext(): Instance { |
|||
return inject(key) as Instance; |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
export default { |
|||
name: 'Graphics editor', |
|||
flowChart: 'FlowChart', |
|||
}; |
|||
@ -0,0 +1,4 @@ |
|||
export default { |
|||
name: '图形编辑器', |
|||
flowChart: '流程图', |
|||
}; |
|||
@ -0,0 +1,18 @@ |
|||
import type { MenuModule } from '/@/router/types'; |
|||
import { t } from '/@/hooks/web/useI18n'; |
|||
|
|||
const menu: MenuModule = { |
|||
orderNo: 5000, |
|||
menu: { |
|||
name: t('routes.demo.flow.name'), |
|||
path: '/flow', |
|||
|
|||
children: [ |
|||
{ |
|||
path: 'flowChart', |
|||
name: t('routes.demo.flow.flowChart'), |
|||
}, |
|||
], |
|||
}, |
|||
}; |
|||
export default menu; |
|||
@ -0,0 +1,27 @@ |
|||
import type { AppRouteModule } from '/@/router/types'; |
|||
|
|||
import { LAYOUT } from '/@/router/constant'; |
|||
import { t } from '/@/hooks/web/useI18n'; |
|||
|
|||
const charts: AppRouteModule = { |
|||
path: '/flow', |
|||
name: 'FlowDemo', |
|||
component: LAYOUT, |
|||
redirect: '/flow/flowChart', |
|||
meta: { |
|||
icon: 'tabler:chart-dots', |
|||
title: t('routes.demo.flow.name'), |
|||
}, |
|||
children: [ |
|||
{ |
|||
path: 'flowChart', |
|||
name: 'flowChartDemo', |
|||
component: () => import('/@/views/demo/comp/flow-chart/index.vue'), |
|||
meta: { |
|||
title: t('routes.demo.flow.flowChart'), |
|||
}, |
|||
}, |
|||
], |
|||
}; |
|||
|
|||
export default charts; |
|||