forked from opentiny/tiny-vue
feat(process-designer): [process-designer] add BPMN process designer component
Wraps bpmn-js to provide a BPMN 2.0 diagram editor following the renderless / vue / theme three-layer architecture. Supports XML / SVG import & export, zoom, undo / redo, readonly mode (Viewer), and forwards bpmn-js events. Closes opentiny/tiny-vue#115507. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2698d3dd2d
commit
2f02d20dc5
|
|
@ -0,0 +1,258 @@
|
||||||
|
export default {
|
||||||
|
mode: ['pc'],
|
||||||
|
apis: [
|
||||||
|
{
|
||||||
|
name: 'process-designer',
|
||||||
|
type: 'component',
|
||||||
|
props: [
|
||||||
|
{
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: "''",
|
||||||
|
desc: {
|
||||||
|
'zh-CN': 'BPMN 2.0 XML 字符串,作为流程图的初始内容;变更时会自动重新导入',
|
||||||
|
'en-US': 'BPMN 2.0 XML string used as the initial diagram. Re-imported automatically when changed.'
|
||||||
|
},
|
||||||
|
mode: ['pc'],
|
||||||
|
pcDemo: 'basic-usage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'xml',
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: "''",
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '与 value 等价,便于按属性命名习惯使用',
|
||||||
|
'en-US': 'Alias of value for naming convenience.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'readonly',
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: 'false',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '只读模式,不渲染编辑工具栏,底层使用 bpmn-js Viewer',
|
||||||
|
'en-US': 'Readonly mode. Toolbar is hidden and the underlying bpmn-js Viewer is used.'
|
||||||
|
},
|
||||||
|
mode: ['pc'],
|
||||||
|
pcDemo: 'readonly'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'show-toolbar',
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: 'true',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '是否显示内置工具栏(撤销 / 重做 / 缩放 / 导出)',
|
||||||
|
'en-US': 'Whether to show the built-in toolbar (undo / redo / zoom / export).'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'height',
|
||||||
|
type: 'string | number',
|
||||||
|
defaultValue: '600',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '组件整体高度,数字将转为 px',
|
||||||
|
'en-US': 'Overall component height; numbers are converted to px.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'file-name',
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: "'diagram'",
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '调用 downloadXML / downloadSVG 时使用的文件名(不含扩展名)',
|
||||||
|
'en-US': 'File name (without extension) used when calling downloadXML / downloadSVG.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'options',
|
||||||
|
type: 'object',
|
||||||
|
defaultValue: '{}',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '透传给 bpmn-js Modeler / Viewer 构造函数的额外配置',
|
||||||
|
'en-US': 'Extra options forwarded to the bpmn-js Modeler / Viewer constructor.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'ready',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '内部 modeler 初始化完成时触发,回调参数为 modeler 实例',
|
||||||
|
'en-US': 'Fired when the underlying modeler is ready. Receives the modeler instance.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'import-done',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': 'XML 导入完成时触发',
|
||||||
|
'en-US': 'Fired when XML import completes.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'import-error',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': 'XML 导入失败时触发',
|
||||||
|
'en-US': 'Fired when XML import fails.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'save-xml',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '调用 saveXML 后触发,回调参数包含 xml 字段',
|
||||||
|
'en-US': 'Fired after saveXML is called. Payload contains the xml field.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'save-svg',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '调用 saveSVG 后触发,回调参数包含 svg 字段',
|
||||||
|
'en-US': 'Fired after saveSVG is called. Payload contains the svg field.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'element.click',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '画布节点被点击时触发(透传 bpmn-js 事件)',
|
||||||
|
'en-US': 'Forwarded from bpmn-js when a diagram element is clicked.'
|
||||||
|
},
|
||||||
|
mode: ['pc'],
|
||||||
|
pcDemo: 'events'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'selection.changed',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '选择变化时触发(透传 bpmn-js 事件)',
|
||||||
|
'en-US': 'Forwarded from bpmn-js when the selection changes.'
|
||||||
|
},
|
||||||
|
mode: ['pc'],
|
||||||
|
pcDemo: 'events'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
name: 'importXML',
|
||||||
|
args: 'xml: string',
|
||||||
|
returnType: 'Promise<{ warnings: any[] }>',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '导入指定的 BPMN XML 字符串',
|
||||||
|
'en-US': 'Import the given BPMN XML string.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'saveXML',
|
||||||
|
args: 'options?: { format?: boolean }',
|
||||||
|
returnType: 'Promise<{ xml: string }>',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '导出当前流程图的 XML',
|
||||||
|
'en-US': 'Export the current diagram as XML.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'saveSVG',
|
||||||
|
args: '',
|
||||||
|
returnType: 'Promise<{ svg: string }>',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '导出当前流程图的 SVG',
|
||||||
|
'en-US': 'Export the current diagram as SVG.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'zoom',
|
||||||
|
args: "level: number | 'fit-viewport'",
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '设置缩放级别,传入 fit-viewport 自适应画布',
|
||||||
|
'en-US': "Set zoom level. Pass 'fit-viewport' to fit the diagram to the canvas."
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'zoomIn',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '在当前缩放基础上放大 10%',
|
||||||
|
'en-US': 'Zoom in by 10%.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'zoomOut',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '在当前缩放基础上缩小 10%',
|
||||||
|
'en-US': 'Zoom out by 10%.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fitViewport',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '将流程图自适应到画布大小',
|
||||||
|
'en-US': 'Fit the diagram to the viewport.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'undo',
|
||||||
|
desc: { 'zh-CN': '撤销上一步操作', 'en-US': 'Undo the last action.' },
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redo',
|
||||||
|
desc: { 'zh-CN': '重做上一步撤销', 'en-US': 'Redo the previous undo.' },
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clear',
|
||||||
|
desc: { 'zh-CN': '清空当前流程图', 'en-US': 'Clear the current diagram.' },
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'downloadXML',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '将当前流程图作为 .bpmn 文件下载',
|
||||||
|
'en-US': 'Download the current diagram as a .bpmn file.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'downloadSVG',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '将当前流程图作为 .svg 文件下载',
|
||||||
|
'en-US': 'Download the current diagram as a .svg file.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'getModeler',
|
||||||
|
returnType: 'BpmnModeler | BpmnViewer | null',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '获取底层 bpmn-js 实例,便于扩展高级用法',
|
||||||
|
'en-US': 'Return the underlying bpmn-js instance for advanced usage.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
slots: [
|
||||||
|
{
|
||||||
|
name: 'toolbar',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '自定义工具栏内容',
|
||||||
|
'en-US': 'Customize the toolbar content.'
|
||||||
|
},
|
||||||
|
mode: ['pc']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<tiny-process-designer :value="xml" :height="520" file-name="basic-diagram" @save-xml="onSaveXml" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { TinyProcessDesigner } from '@opentiny/vue'
|
||||||
|
|
||||||
|
const xml = ref(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="basic" targetNamespace="http://bpmn.io/schema/bpmn">
|
||||||
|
<bpmn2:process id="Process_basic" isExecutable="false">
|
||||||
|
<bpmn2:startEvent id="StartEvent_1" name="开始"/>
|
||||||
|
<bpmn2:task id="Task_1" name="审批"/>
|
||||||
|
<bpmn2:endEvent id="EndEvent_1" name="结束"/>
|
||||||
|
<bpmn2:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="Task_1"/>
|
||||||
|
<bpmn2:sequenceFlow id="Flow_2" sourceRef="Task_1" targetRef="EndEvent_1"/>
|
||||||
|
</bpmn2:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_basic">
|
||||||
|
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="160" y="180" width="36" height="36"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
|
||||||
|
<dc:Bounds x="260" y="158" width="100" height="80"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
|
||||||
|
<dc:Bounds x="430" y="180" width="36" height="36"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1">
|
||||||
|
<di:waypoint x="196" y="198"/>
|
||||||
|
<di:waypoint x="260" y="198"/>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_2_di" bpmnElement="Flow_2">
|
||||||
|
<di:waypoint x="360" y="198"/>
|
||||||
|
<di:waypoint x="430" y="198"/>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn2:definitions>`)
|
||||||
|
|
||||||
|
const onSaveXml = ({ xml }) => {
|
||||||
|
console.log('saved xml length:', xml && xml.length)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test('process-designer 基本用法', async ({ page }) => {
|
||||||
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
|
await page.goto('process-designer#basic-usage')
|
||||||
|
|
||||||
|
const wrap = page.locator('#basic-usage')
|
||||||
|
const designer = wrap.locator('.tiny-process-designer')
|
||||||
|
await expect(designer).toBeVisible()
|
||||||
|
|
||||||
|
// bpmn-js 渲染完成后画布内会出现 SVG
|
||||||
|
const svg = designer.locator('.tiny-process-designer__canvas svg').first()
|
||||||
|
await expect(svg).toBeVisible()
|
||||||
|
|
||||||
|
// 工具栏按钮存在
|
||||||
|
const toolbar = designer.locator('.tiny-process-designer__toolbar')
|
||||||
|
await expect(toolbar.locator('button')).toHaveCount(7)
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<tiny-process-designer :value="xml" :height="520" file-name="basic-diagram" @save-xml="onSaveXml" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { TinyProcessDesigner } from '@opentiny/vue'
|
||||||
|
|
||||||
|
const initialXml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="basic" targetNamespace="http://bpmn.io/schema/bpmn">
|
||||||
|
<bpmn2:process id="Process_basic" isExecutable="false">
|
||||||
|
<bpmn2:startEvent id="StartEvent_1" name="开始"/>
|
||||||
|
<bpmn2:task id="Task_1" name="审批"/>
|
||||||
|
<bpmn2:endEvent id="EndEvent_1" name="结束"/>
|
||||||
|
<bpmn2:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="Task_1"/>
|
||||||
|
<bpmn2:sequenceFlow id="Flow_2" sourceRef="Task_1" targetRef="EndEvent_1"/>
|
||||||
|
</bpmn2:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_basic">
|
||||||
|
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="160" y="180" width="36" height="36"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
|
||||||
|
<dc:Bounds x="260" y="158" width="100" height="80"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
|
||||||
|
<dc:Bounds x="430" y="180" width="36" height="36"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1">
|
||||||
|
<di:waypoint x="196" y="198"/>
|
||||||
|
<di:waypoint x="260" y="198"/>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_2_di" bpmnElement="Flow_2">
|
||||||
|
<di:waypoint x="360" y="198"/>
|
||||||
|
<di:waypoint x="430" y="198"/>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn2:definitions>`
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
TinyProcessDesigner
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
xml: initialXml
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onSaveXml({ xml }) {
|
||||||
|
console.log('saved xml length:', xml && xml.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<tiny-process-designer :height="480" @element.click="onElementClick" @selection.changed="onSelectionChanged" />
|
||||||
|
<p>当前选中:{{ selectedId || '无' }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { TinyProcessDesigner } from '@opentiny/vue'
|
||||||
|
|
||||||
|
const selectedId = ref('')
|
||||||
|
|
||||||
|
const onElementClick = (event) => {
|
||||||
|
selectedId.value = event && event.element ? event.element.id : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelectionChanged = ({ newSelection }) => {
|
||||||
|
selectedId.value = newSelection && newSelection[0] ? newSelection[0].id : ''
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test('process-designer 事件监听', async ({ page }) => {
|
||||||
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
|
await page.goto('process-designer#events')
|
||||||
|
|
||||||
|
const wrap = page.locator('#events')
|
||||||
|
const designer = wrap.locator('.tiny-process-designer')
|
||||||
|
await expect(designer).toBeVisible()
|
||||||
|
await expect(designer.locator('.tiny-process-designer__canvas svg').first()).toBeVisible()
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<tiny-process-designer
|
||||||
|
ref="designer"
|
||||||
|
:height="480"
|
||||||
|
@element.click="onElementClick"
|
||||||
|
@selection.changed="onSelectionChanged"
|
||||||
|
/>
|
||||||
|
<p>当前选中:{{ selectedId || '无' }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { TinyProcessDesigner } from '@opentiny/vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { TinyProcessDesigner },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedId: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onElementClick(event) {
|
||||||
|
this.selectedId = event && event.element ? event.element.id : ''
|
||||||
|
},
|
||||||
|
onSelectionChanged({ newSelection }) {
|
||||||
|
this.selectedId = newSelection && newSelection[0] ? newSelection[0].id : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<template>
|
||||||
|
<tiny-process-designer :value="xml" :height="420" readonly />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { TinyProcessDesigner } from '@opentiny/vue'
|
||||||
|
|
||||||
|
const xml = ref(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="readonly" targetNamespace="http://bpmn.io/schema/bpmn">
|
||||||
|
<bpmn2:process id="Process_ro" isExecutable="false">
|
||||||
|
<bpmn2:startEvent id="StartEvent_1"/>
|
||||||
|
<bpmn2:endEvent id="EndEvent_1"/>
|
||||||
|
<bpmn2:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="EndEvent_1"/>
|
||||||
|
</bpmn2:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_ro">
|
||||||
|
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="160" y="180" width="36" height="36"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
|
||||||
|
<dc:Bounds x="280" y="180" width="36" height="36"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1">
|
||||||
|
<di:waypoint x="196" y="198"/>
|
||||||
|
<di:waypoint x="280" y="198"/>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn2:definitions>`)
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test('process-designer 只读模式', async ({ page }) => {
|
||||||
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
|
await page.goto('process-designer#readonly')
|
||||||
|
|
||||||
|
const wrap = page.locator('#readonly')
|
||||||
|
const designer = wrap.locator('.tiny-process-designer')
|
||||||
|
await expect(designer).toBeVisible()
|
||||||
|
|
||||||
|
// 只读模式下不渲染工具栏
|
||||||
|
await expect(designer.locator('.tiny-process-designer__toolbar')).toHaveCount(0)
|
||||||
|
await expect(designer.locator('.tiny-process-designer__canvas svg').first()).toBeVisible()
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<tiny-process-designer :value="xml" :height="420" readonly />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { TinyProcessDesigner } from '@opentiny/vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { TinyProcessDesigner },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="readonly" targetNamespace="http://bpmn.io/schema/bpmn">
|
||||||
|
<bpmn2:process id="Process_ro" isExecutable="false">
|
||||||
|
<bpmn2:startEvent id="StartEvent_1"/>
|
||||||
|
<bpmn2:endEvent id="EndEvent_1"/>
|
||||||
|
<bpmn2:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="EndEvent_1"/>
|
||||||
|
</bpmn2:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_ro">
|
||||||
|
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="160" y="180" width="36" height="36"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
|
||||||
|
<dc:Bounds x="280" y="180" width="36" height="36"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1">
|
||||||
|
<di:waypoint x="196" y="198"/>
|
||||||
|
<di:waypoint x="280" y="198"/>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn2:definitions>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
title: ProcessDesigner 流程设计器
|
||||||
|
---
|
||||||
|
|
||||||
|
# ProcessDesigner 流程设计器
|
||||||
|
|
||||||
|
基于 [bpmn-js](https://github.com/bpmn-io/bpmn-js) 封装的 BPMN 2.0 流程图设计器组件,支持在画布上图形化绘制业务流程,提供撤销 / 重做 / 缩放 / 导出 XML / 导出 SVG 等基础能力,可用于工作流配置、审批流编排等场景。
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
title: ProcessDesigner
|
||||||
|
---
|
||||||
|
|
||||||
|
# ProcessDesigner
|
||||||
|
|
||||||
|
A BPMN 2.0 process designer component built on top of [bpmn-js](https://github.com/bpmn-io/bpmn-js). It provides graphical authoring of business processes on a canvas, with built-in undo / redo / zoom and XML / SVG export — suitable for workflow configuration, approval-flow design, and similar scenarios.
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
export default {
|
||||||
|
column: '2',
|
||||||
|
owner: '',
|
||||||
|
show: true,
|
||||||
|
cloud: false,
|
||||||
|
demos: [
|
||||||
|
{
|
||||||
|
demoId: 'basic-usage',
|
||||||
|
name: {
|
||||||
|
'zh-CN': '基本用法',
|
||||||
|
'en-US': 'Basic Usage'
|
||||||
|
},
|
||||||
|
desc: {
|
||||||
|
'zh-CN':
|
||||||
|
'<p>通过 <code>value</code> 传入 BPMN 2.0 XML 即可渲染流程图,可在画布上拖拽编辑,使用工具栏完成撤销 / 重做 / 缩放 / 导出。</p>\n',
|
||||||
|
'en-US':
|
||||||
|
'<p>Pass BPMN 2.0 XML via <code>value</code> to render the diagram. The diagram can be edited on the canvas, with the toolbar offering undo / redo / zoom / export.</p>\n'
|
||||||
|
},
|
||||||
|
codeFiles: ['basic-usage.vue']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
demoId: 'readonly',
|
||||||
|
name: {
|
||||||
|
'zh-CN': '只读模式',
|
||||||
|
'en-US': 'Readonly'
|
||||||
|
},
|
||||||
|
desc: {
|
||||||
|
'zh-CN':
|
||||||
|
'<p>设置 <code>readonly</code> 属性后只渲染流程图、不可编辑,适用于流程预览场景。底层使用 <code>bpmn-js</code> 的 <code>Viewer</code>。</p>\n',
|
||||||
|
'en-US':
|
||||||
|
'<p>With <code>readonly</code> set, the diagram is rendered without editing capabilities — suitable for read-only preview. Backed by <code>bpmn-js</code> <code>Viewer</code>.</p>\n'
|
||||||
|
},
|
||||||
|
codeFiles: ['readonly.vue']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
demoId: 'events',
|
||||||
|
name: {
|
||||||
|
'zh-CN': '事件监听',
|
||||||
|
'en-US': 'Events'
|
||||||
|
},
|
||||||
|
desc: {
|
||||||
|
'zh-CN':
|
||||||
|
'<p>组件透传 <code>bpmn-js</code> 的常用事件,可监听 <code>element.click</code>、<code>selection.changed</code>、<code>commandStack.changed</code> 等。</p>\n',
|
||||||
|
'en-US':
|
||||||
|
'<p>Common <code>bpmn-js</code> events are forwarded, such as <code>element.click</code>, <code>selection.changed</code>, and <code>commandStack.changed</code>.</p>\n'
|
||||||
|
},
|
||||||
|
codeFiles: ['events.vue']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -262,6 +262,7 @@ export const cmpMenus = [
|
||||||
{ 'nameCn': '图片', 'name': 'Image', 'key': 'image' },
|
{ 'nameCn': '图片', 'name': 'Image', 'key': 'image' },
|
||||||
{ 'nameCn': '无限滚动', 'name': 'InfiniteScroll', 'key': 'infinite-scroll' },
|
{ 'nameCn': '无限滚动', 'name': 'InfiniteScroll', 'key': 'infinite-scroll' },
|
||||||
{ 'nameCn': '里程碑', 'name': 'Milestone', 'key': 'milestone' },
|
{ 'nameCn': '里程碑', 'name': 'Milestone', 'key': 'milestone' },
|
||||||
|
{ 'nameCn': '流程设计器', 'name': 'ProcessDesigner', 'key': 'process-designer' },
|
||||||
{
|
{
|
||||||
'nameCn': '思维导图',
|
'nameCn': '思维导图',
|
||||||
'name': 'MindMap',
|
'name': 'MindMap',
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,14 @@
|
||||||
"@opentiny/vue-hooks": "workspace:~",
|
"@opentiny/vue-hooks": "workspace:~",
|
||||||
"color": "4.2.3"
|
"color": "4.2.3"
|
||||||
},
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bpmn-js": "^17.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bpmn-js": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esno": "^4.7.0",
|
"esno": "^4.7.0",
|
||||||
"tsup": "7.2.0"
|
"tsup": "7.2.0"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const EMPTY_BPMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn">
|
||||||
|
<bpmn2:process id="Process_1" isExecutable="false">
|
||||||
|
<bpmn2:startEvent id="StartEvent_1"/>
|
||||||
|
</bpmn2:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
|
||||||
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds height="36.0" width="36.0" x="412.0" y="240.0"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn2:definitions>`
|
||||||
|
|
||||||
|
const FORWARDED_EVENTS = [
|
||||||
|
'element.click',
|
||||||
|
'element.dblclick',
|
||||||
|
'element.hover',
|
||||||
|
'element.out',
|
||||||
|
'element.changed',
|
||||||
|
'shape.added',
|
||||||
|
'shape.removed',
|
||||||
|
'connection.added',
|
||||||
|
'connection.removed',
|
||||||
|
'commandStack.changed',
|
||||||
|
'selection.changed',
|
||||||
|
'import.done',
|
||||||
|
'saveXML.done'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const createModeler =
|
||||||
|
({ state, props, emit, api }) =>
|
||||||
|
async () => {
|
||||||
|
const BpmnLib = props.readonly ? await import('bpmn-js/lib/Viewer') : await import('bpmn-js/lib/Modeler')
|
||||||
|
const Ctor = BpmnLib.default
|
||||||
|
|
||||||
|
const modeler = new Ctor({
|
||||||
|
container: state.canvasRef,
|
||||||
|
keyboard: { bindTo: document },
|
||||||
|
...(props.options || {})
|
||||||
|
})
|
||||||
|
|
||||||
|
state.modeler = modeler
|
||||||
|
|
||||||
|
FORWARDED_EVENTS.forEach((event) => {
|
||||||
|
modeler.on(event, (payload) => {
|
||||||
|
emit(event, payload, modeler)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
modeler.on('commandStack.changed', () => {
|
||||||
|
const stack = modeler.get('commandStack')
|
||||||
|
state.canUndo = stack.canUndo()
|
||||||
|
state.canRedo = stack.canRedo()
|
||||||
|
})
|
||||||
|
|
||||||
|
const xml = props.value || props.xml || EMPTY_BPMN_XML
|
||||||
|
await api.importXML(xml)
|
||||||
|
|
||||||
|
emit('ready', modeler)
|
||||||
|
|
||||||
|
return modeler
|
||||||
|
}
|
||||||
|
|
||||||
|
export const importXML =
|
||||||
|
({ state, emit }) =>
|
||||||
|
async (xml: string) => {
|
||||||
|
if (!state.modeler) return { warnings: [] }
|
||||||
|
try {
|
||||||
|
const result = await state.modeler.importXML(xml || EMPTY_BPMN_XML)
|
||||||
|
state.lastError = null
|
||||||
|
emit('import-done', result)
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
state.lastError = err
|
||||||
|
emit('import-error', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveXML =
|
||||||
|
({ state, emit }) =>
|
||||||
|
async (options?: { format?: boolean }) => {
|
||||||
|
if (!state.modeler) return { xml: '' }
|
||||||
|
const result = await state.modeler.saveXML({ format: true, ...(options || {}) })
|
||||||
|
emit('save-xml', result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveSVG =
|
||||||
|
({ state, emit }) =>
|
||||||
|
async () => {
|
||||||
|
if (!state.modeler) return { svg: '' }
|
||||||
|
const result = await state.modeler.saveSVG()
|
||||||
|
emit('save-svg', result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export const zoom =
|
||||||
|
({ state }) =>
|
||||||
|
(level: number | 'fit-viewport') => {
|
||||||
|
if (!state.modeler) return
|
||||||
|
state.modeler.get('canvas').zoom(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const zoomIn =
|
||||||
|
({ state, api }) =>
|
||||||
|
() => {
|
||||||
|
if (!state.modeler) return
|
||||||
|
const canvas = state.modeler.get('canvas')
|
||||||
|
const current = canvas.zoom()
|
||||||
|
api.zoom(Math.min(current + 0.1, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const zoomOut =
|
||||||
|
({ state, api }) =>
|
||||||
|
() => {
|
||||||
|
if (!state.modeler) return
|
||||||
|
const canvas = state.modeler.get('canvas')
|
||||||
|
const current = canvas.zoom()
|
||||||
|
api.zoom(Math.max(current - 0.1, 0.2))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fitViewport =
|
||||||
|
({ api }) =>
|
||||||
|
() => {
|
||||||
|
api.zoom('fit-viewport')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const undo =
|
||||||
|
({ state }) =>
|
||||||
|
() => {
|
||||||
|
if (!state.modeler) return
|
||||||
|
state.modeler.get('commandStack').undo()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const redo =
|
||||||
|
({ state }) =>
|
||||||
|
() => {
|
||||||
|
if (!state.modeler) return
|
||||||
|
state.modeler.get('commandStack').redo()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clear =
|
||||||
|
({ api }) =>
|
||||||
|
() =>
|
||||||
|
api.importXML(EMPTY_BPMN_XML)
|
||||||
|
|
||||||
|
export const downloadFile = (filename: string, content: string, mime: string) => {
|
||||||
|
if (typeof document === 'undefined') return
|
||||||
|
const blob = new Blob([content], { type: mime })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = filename
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const downloadXML =
|
||||||
|
({ api, props }) =>
|
||||||
|
async () => {
|
||||||
|
const { xml } = await api.saveXML({ format: true })
|
||||||
|
if (xml) downloadFile(`${props.fileName || 'diagram'}.bpmn`, xml, 'application/xml')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const downloadSVG =
|
||||||
|
({ api, props }) =>
|
||||||
|
async () => {
|
||||||
|
const { svg } = await api.saveSVG()
|
||||||
|
if (svg) downloadFile(`${props.fileName || 'diagram'}.svg`, svg, 'image/svg+xml')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getModeler =
|
||||||
|
({ state }) =>
|
||||||
|
() =>
|
||||||
|
state.modeler
|
||||||
|
|
||||||
|
export const destroy =
|
||||||
|
({ state }) =>
|
||||||
|
() => {
|
||||||
|
if (state.modeler) {
|
||||||
|
state.modeler.destroy()
|
||||||
|
state.modeler = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
createModeler,
|
||||||
|
importXML,
|
||||||
|
saveXML,
|
||||||
|
saveSVG,
|
||||||
|
zoom,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
fitViewport,
|
||||||
|
undo,
|
||||||
|
redo,
|
||||||
|
clear,
|
||||||
|
downloadXML,
|
||||||
|
downloadSVG,
|
||||||
|
getModeler,
|
||||||
|
destroy
|
||||||
|
} from './index'
|
||||||
|
|
||||||
|
export const api = [
|
||||||
|
'state',
|
||||||
|
'importXML',
|
||||||
|
'saveXML',
|
||||||
|
'saveSVG',
|
||||||
|
'zoom',
|
||||||
|
'zoomIn',
|
||||||
|
'zoomOut',
|
||||||
|
'fitViewport',
|
||||||
|
'undo',
|
||||||
|
'redo',
|
||||||
|
'clear',
|
||||||
|
'downloadXML',
|
||||||
|
'downloadSVG',
|
||||||
|
'getModeler'
|
||||||
|
]
|
||||||
|
|
||||||
|
const initState = ({ reactive, props }) => {
|
||||||
|
const state = reactive({
|
||||||
|
modeler: null,
|
||||||
|
canvasRef: null,
|
||||||
|
canUndo: false,
|
||||||
|
canRedo: false,
|
||||||
|
lastError: null,
|
||||||
|
readonly: props.readonly
|
||||||
|
})
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
const initApi = ({ api, state, props, emit }) => {
|
||||||
|
Object.assign(api, {
|
||||||
|
state,
|
||||||
|
createModeler: createModeler({ state, props, emit, api }),
|
||||||
|
importXML: importXML({ state, emit }),
|
||||||
|
saveXML: saveXML({ state, emit }),
|
||||||
|
saveSVG: saveSVG({ state, emit }),
|
||||||
|
zoom: zoom({ state }),
|
||||||
|
zoomIn: zoomIn({ state, api }),
|
||||||
|
zoomOut: zoomOut({ state, api }),
|
||||||
|
fitViewport: fitViewport({ api }),
|
||||||
|
undo: undo({ state }),
|
||||||
|
redo: redo({ state }),
|
||||||
|
clear: clear({ api }),
|
||||||
|
downloadXML: downloadXML({ api, props }),
|
||||||
|
downloadSVG: downloadSVG({ api, props }),
|
||||||
|
getModeler: getModeler({ state }),
|
||||||
|
destroy: destroy({ state })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const initWatch = ({ watch, state, api, props }) => {
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(val) => {
|
||||||
|
if (state.modeler && typeof val === 'string') {
|
||||||
|
api.importXML(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.xml,
|
||||||
|
(val) => {
|
||||||
|
if (state.modeler && typeof val === 'string') {
|
||||||
|
api.importXML(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const renderless = (props, { onMounted, onBeforeUnmount, reactive, watch }, { emit, vm }) => {
|
||||||
|
const api = {}
|
||||||
|
const state = initState({ reactive, props })
|
||||||
|
|
||||||
|
initApi({ api, state, props, emit })
|
||||||
|
initWatch({ watch, state, api, props })
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
state.canvasRef = vm.$refs.canvas
|
||||||
|
api.createModeler()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
api.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
|
@ -101,6 +101,7 @@
|
||||||
@import './popeditor/index.less';
|
@import './popeditor/index.less';
|
||||||
@import './popover/index.less';
|
@import './popover/index.less';
|
||||||
@import './popup/index.less';
|
@import './popup/index.less';
|
||||||
|
@import './process-designer/index.less';
|
||||||
@import './progress/index.less';
|
@import './progress/index.less';
|
||||||
@import './qr-code/index.less';
|
@import './qr-code/index.less';
|
||||||
@import './quarter-panel/index.less';
|
@import './quarter-panel/index.less';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import '../mixins/common.less';
|
||||||
|
@import '../custom.less';
|
||||||
|
@import './vars.less';
|
||||||
|
|
||||||
|
@process-designer-prefix-cls: ~'@{css-prefix}process-designer';
|
||||||
|
|
||||||
|
.@{process-designer-prefix-cls} {
|
||||||
|
.inject-ProcessDesigner-vars();
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--tv-ProcessDesigner-border-color);
|
||||||
|
border-radius: var(--tv-ProcessDesigner-border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&__toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: var(--tv-ProcessDesigner-toolbar-height);
|
||||||
|
padding: var(--tv-ProcessDesigner-toolbar-padding);
|
||||||
|
background-color: var(--tv-ProcessDesigner-toolbar-bg-color);
|
||||||
|
border-bottom: 1px solid var(--tv-ProcessDesigner-toolbar-border-color);
|
||||||
|
gap: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: var(--tv-ProcessDesigner-btn-size);
|
||||||
|
min-width: var(--tv-ProcessDesigner-btn-size);
|
||||||
|
padding: 0 6px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--tv-ProcessDesigner-border-radius);
|
||||||
|
color: var(--tv-ProcessDesigner-btn-text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1;
|
||||||
|
.user-select(none);
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: var(--tv-ProcessDesigner-btn-hover-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: var(--tv-ProcessDesigner-btn-disabled-text-color);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiny-svg-size {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btn-label {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__divider {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0 4px;
|
||||||
|
background-color: var(--tv-ProcessDesigner-divider-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__canvas {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
background-color: var(--tv-ProcessDesigner-canvas-bg-color);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
.inject-ProcessDesigner-vars() {
|
||||||
|
// 整体边框颜色
|
||||||
|
--tv-ProcessDesigner-border-color: var(--tv-color-border, #dcdfe6);
|
||||||
|
// 整体圆角
|
||||||
|
--tv-ProcessDesigner-border-radius: var(--tv-border-radius-m, 4px);
|
||||||
|
// 画布背景色
|
||||||
|
--tv-ProcessDesigner-canvas-bg-color: var(--tv-color-bg-2, #f5f5f5);
|
||||||
|
// 工具栏背景色
|
||||||
|
--tv-ProcessDesigner-toolbar-bg-color: var(--tv-color-bg-1, #ffffff);
|
||||||
|
// 工具栏底部边框颜色
|
||||||
|
--tv-ProcessDesigner-toolbar-border-color: var(--tv-color-border-separator, #ebeef5);
|
||||||
|
// 工具栏高度
|
||||||
|
--tv-ProcessDesigner-toolbar-height: var(--tv-size-height-lg, 40px);
|
||||||
|
// 工具栏内边距
|
||||||
|
--tv-ProcessDesigner-toolbar-padding: 4px 8px;
|
||||||
|
// 按钮尺寸
|
||||||
|
--tv-ProcessDesigner-btn-size: 28px;
|
||||||
|
// 按钮文字颜色
|
||||||
|
--tv-ProcessDesigner-btn-text-color: var(--tv-color-text-primary, #252b3a);
|
||||||
|
// 按钮悬浮背景色
|
||||||
|
--tv-ProcessDesigner-btn-hover-bg-color: var(--tv-color-bg-hover, #f2f5fc);
|
||||||
|
// 按钮禁用文字颜色
|
||||||
|
--tv-ProcessDesigner-btn-disabled-text-color: var(--tv-color-text-disabled, #adb0b8);
|
||||||
|
// 分割线颜色
|
||||||
|
--tv-ProcessDesigner-divider-color: var(--tv-color-border-separator, #ebeef5);
|
||||||
|
}
|
||||||
|
|
@ -479,6 +479,15 @@ export default {
|
||||||
hide: 'Hide Sidebar',
|
hide: 'Hide Sidebar',
|
||||||
show: 'Show Sidebar'
|
show: 'Show Sidebar'
|
||||||
},
|
},
|
||||||
|
processDesigner: {
|
||||||
|
undo: 'Undo',
|
||||||
|
redo: 'Redo',
|
||||||
|
zoomIn: 'Zoom In',
|
||||||
|
zoomOut: 'Zoom Out',
|
||||||
|
fitViewport: 'Fit Viewport',
|
||||||
|
downloadXml: 'Download XML',
|
||||||
|
downloadSvg: 'Download SVG'
|
||||||
|
},
|
||||||
navMenu: {
|
navMenu: {
|
||||||
moreText: 'More',
|
moreText: 'More',
|
||||||
navigation: 'Navigation',
|
navigation: 'Navigation',
|
||||||
|
|
|
||||||
|
|
@ -474,6 +474,15 @@ export default {
|
||||||
hide: '隐藏侧边栏',
|
hide: '隐藏侧边栏',
|
||||||
show: '显示侧边栏'
|
show: '显示侧边栏'
|
||||||
},
|
},
|
||||||
|
processDesigner: {
|
||||||
|
undo: '撤销',
|
||||||
|
redo: '重做',
|
||||||
|
zoomIn: '放大',
|
||||||
|
zoomOut: '缩小',
|
||||||
|
fitViewport: '适配画布',
|
||||||
|
downloadXml: '下载 XML',
|
||||||
|
downloadSvg: '下载 SVG'
|
||||||
|
},
|
||||||
navMenu: {
|
navMenu: {
|
||||||
moreText: '更多',
|
moreText: '更多',
|
||||||
navigation: '导航',
|
navigation: '导航',
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ import Popconfirm from '@opentiny/vue-popconfirm'
|
||||||
import Popeditor from '@opentiny/vue-popeditor'
|
import Popeditor from '@opentiny/vue-popeditor'
|
||||||
import Popover from '@opentiny/vue-popover'
|
import Popover from '@opentiny/vue-popover'
|
||||||
import Popup from '@opentiny/vue-popup'
|
import Popup from '@opentiny/vue-popup'
|
||||||
|
import ProcessDesigner from '@opentiny/vue-process-designer'
|
||||||
import Progress from '@opentiny/vue-progress'
|
import Progress from '@opentiny/vue-progress'
|
||||||
import PullRefresh from '@opentiny/vue-pull-refresh'
|
import PullRefresh from '@opentiny/vue-pull-refresh'
|
||||||
import QrCode from '@opentiny/vue-qr-code'
|
import QrCode from '@opentiny/vue-qr-code'
|
||||||
|
|
@ -322,6 +323,7 @@ const components = [
|
||||||
Popeditor,
|
Popeditor,
|
||||||
Popover,
|
Popover,
|
||||||
Popup,
|
Popup,
|
||||||
|
ProcessDesigner,
|
||||||
Progress,
|
Progress,
|
||||||
PullRefresh,
|
PullRefresh,
|
||||||
QrCode,
|
QrCode,
|
||||||
|
|
@ -665,6 +667,8 @@ export {
|
||||||
Popover as TinyPopover,
|
Popover as TinyPopover,
|
||||||
Popup,
|
Popup,
|
||||||
Popup as TinyPopup,
|
Popup as TinyPopup,
|
||||||
|
ProcessDesigner,
|
||||||
|
ProcessDesigner as TinyProcessDesigner,
|
||||||
Progress,
|
Progress,
|
||||||
Progress as TinyProgress,
|
Progress as TinyProgress,
|
||||||
PullRefresh,
|
PullRefresh,
|
||||||
|
|
@ -1066,6 +1070,8 @@ export default {
|
||||||
TinyPopover: Popover,
|
TinyPopover: Popover,
|
||||||
Popup,
|
Popup,
|
||||||
TinyPopup: Popup,
|
TinyPopup: Popup,
|
||||||
|
ProcessDesigner,
|
||||||
|
TinyProcessDesigner: ProcessDesigner,
|
||||||
Progress,
|
Progress,
|
||||||
TinyProgress: Progress,
|
TinyProgress: Progress,
|
||||||
PullRefresh,
|
PullRefresh,
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,7 @@
|
||||||
"@opentiny/vue-popeditor": "workspace:~",
|
"@opentiny/vue-popeditor": "workspace:~",
|
||||||
"@opentiny/vue-popover": "workspace:~",
|
"@opentiny/vue-popover": "workspace:~",
|
||||||
"@opentiny/vue-popup": "workspace:~",
|
"@opentiny/vue-popup": "workspace:~",
|
||||||
|
"@opentiny/vue-process-designer": "workspace:~",
|
||||||
"@opentiny/vue-progress": "workspace:~",
|
"@opentiny/vue-progress": "workspace:~",
|
||||||
"@opentiny/vue-pull-refresh": "workspace:~",
|
"@opentiny/vue-pull-refresh": "workspace:~",
|
||||||
"@opentiny/vue-qr-code": "workspace:~",
|
"@opentiny/vue-qr-code": "workspace:~",
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ import Popconfirm from '@opentiny/vue-popconfirm'
|
||||||
import Popeditor from '@opentiny/vue-popeditor'
|
import Popeditor from '@opentiny/vue-popeditor'
|
||||||
import Popover from '@opentiny/vue-popover'
|
import Popover from '@opentiny/vue-popover'
|
||||||
import Popup from '@opentiny/vue-popup'
|
import Popup from '@opentiny/vue-popup'
|
||||||
|
import ProcessDesigner from '@opentiny/vue-process-designer'
|
||||||
import Progress from '@opentiny/vue-progress'
|
import Progress from '@opentiny/vue-progress'
|
||||||
import QrCode from '@opentiny/vue-qr-code'
|
import QrCode from '@opentiny/vue-qr-code'
|
||||||
import QuarterPanel from '@opentiny/vue-quarter-panel'
|
import QuarterPanel from '@opentiny/vue-quarter-panel'
|
||||||
|
|
@ -282,6 +283,7 @@ const components = [
|
||||||
Popeditor,
|
Popeditor,
|
||||||
Popover,
|
Popover,
|
||||||
Popup,
|
Popup,
|
||||||
|
ProcessDesigner,
|
||||||
Progress,
|
Progress,
|
||||||
QrCode,
|
QrCode,
|
||||||
QuarterPanel,
|
QuarterPanel,
|
||||||
|
|
@ -588,6 +590,8 @@ export {
|
||||||
Popover as TinyPopover,
|
Popover as TinyPopover,
|
||||||
Popup,
|
Popup,
|
||||||
Popup as TinyPopup,
|
Popup as TinyPopup,
|
||||||
|
ProcessDesigner,
|
||||||
|
ProcessDesigner as TinyProcessDesigner,
|
||||||
Progress,
|
Progress,
|
||||||
Progress as TinyProgress,
|
Progress as TinyProgress,
|
||||||
QrCode,
|
QrCode,
|
||||||
|
|
@ -937,6 +941,8 @@ export default {
|
||||||
TinyPopover: Popover,
|
TinyPopover: Popover,
|
||||||
Popup,
|
Popup,
|
||||||
TinyPopup: Popup,
|
TinyPopup: Popup,
|
||||||
|
ProcessDesigner,
|
||||||
|
TinyProcessDesigner: ProcessDesigner,
|
||||||
Progress,
|
Progress,
|
||||||
TinyProgress: Progress,
|
TinyProgress: Progress,
|
||||||
QrCode,
|
QrCode,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import ProcessDesigner from './src/index'
|
||||||
|
import { version } from './package.json'
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
ProcessDesigner.install = function (Vue) {
|
||||||
|
Vue.component(ProcessDesigner.name, ProcessDesigner)
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessDesigner.version = version
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (process.env.BUILD_TARGET === 'runtime') {
|
||||||
|
if (typeof window !== 'undefined' && window.Vue) {
|
||||||
|
ProcessDesigner.install(window.Vue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProcessDesigner
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "@opentiny/vue-process-designer",
|
||||||
|
"type": "module",
|
||||||
|
"version": "3.30.0",
|
||||||
|
"description": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"sideEffects": false,
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"module": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "pnpm -w build:ui $npm_package_name"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@opentiny/utils": "workspace:~",
|
||||||
|
"@opentiny/vue-common": "workspace:~",
|
||||||
|
"@opentiny/vue-icon": "workspace:~",
|
||||||
|
"@opentiny/vue-renderless": "workspace:~",
|
||||||
|
"@opentiny/vue-theme": "workspace:~",
|
||||||
|
"bpmn-js": "^17.11.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@opentiny-internal/vue-test-utils": "workspace:*",
|
||||||
|
"vitest": "catalog:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
|
||||||
|
import template from 'virtual-template?pc'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: $prefix + 'ProcessDesigner',
|
||||||
|
props: {
|
||||||
|
...$props,
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
xml: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
readonly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showToolbar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 600
|
||||||
|
},
|
||||||
|
fileName: {
|
||||||
|
type: String,
|
||||||
|
default: 'diagram'
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, context) {
|
||||||
|
return $setup({ props, context, template })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
<!--
|
||||||
|
* Copyright (c) 2022 - present TinyVue Authors.
|
||||||
|
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license.
|
||||||
|
*
|
||||||
|
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||||
|
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||||
|
*
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="tiny-process-designer" :style="{ height: typeof height === 'number' ? height + 'px' : height }">
|
||||||
|
<div v-if="showToolbar && !readonly" class="tiny-process-designer__toolbar">
|
||||||
|
<slot name="toolbar" :api="$attrs">
|
||||||
|
<button
|
||||||
|
class="tiny-process-designer__btn"
|
||||||
|
:disabled="!state.canUndo"
|
||||||
|
:title="t('ui.processDesigner.undo')"
|
||||||
|
@click="undo"
|
||||||
|
>
|
||||||
|
<icon-undo class="tiny-svg-size" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="tiny-process-designer__btn"
|
||||||
|
:disabled="!state.canRedo"
|
||||||
|
:title="t('ui.processDesigner.redo')"
|
||||||
|
@click="redo"
|
||||||
|
>
|
||||||
|
<icon-redo class="tiny-svg-size" />
|
||||||
|
</button>
|
||||||
|
<i class="tiny-process-designer__divider"></i>
|
||||||
|
<button class="tiny-process-designer__btn" :title="t('ui.processDesigner.zoomIn')" @click="zoomIn">
|
||||||
|
<icon-zoom-in class="tiny-svg-size" />
|
||||||
|
</button>
|
||||||
|
<button class="tiny-process-designer__btn" :title="t('ui.processDesigner.zoomOut')" @click="zoomOut">
|
||||||
|
<icon-zoom-out class="tiny-svg-size" />
|
||||||
|
</button>
|
||||||
|
<button class="tiny-process-designer__btn" :title="t('ui.processDesigner.fitViewport')" @click="fitViewport">
|
||||||
|
<icon-fullscreen class="tiny-svg-size" />
|
||||||
|
</button>
|
||||||
|
<i class="tiny-process-designer__divider"></i>
|
||||||
|
<button class="tiny-process-designer__btn" :title="t('ui.processDesigner.downloadXml')" @click="downloadXML">
|
||||||
|
<icon-download class="tiny-svg-size" />
|
||||||
|
<span class="tiny-process-designer__btn-label">XML</span>
|
||||||
|
</button>
|
||||||
|
<button class="tiny-process-designer__btn" :title="t('ui.processDesigner.downloadSvg')" @click="downloadSVG">
|
||||||
|
<icon-download class="tiny-svg-size" />
|
||||||
|
<span class="tiny-process-designer__btn-label">SVG</span>
|
||||||
|
</button>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div ref="canvas" class="tiny-process-designer__canvas"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="tsx">
|
||||||
|
import { renderless, api } from '@opentiny/vue-renderless/process-designer/vue'
|
||||||
|
import { props, setup, defineComponent } from '@opentiny/vue-common'
|
||||||
|
import { iconZoomIn, iconZoomOut, iconUndo, iconRedo, iconFullscreen, iconDownload } from '@opentiny/vue-icon'
|
||||||
|
import 'bpmn-js/dist/assets/diagram-js.css'
|
||||||
|
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
|
||||||
|
import 'bpmn-js/dist/assets/bpmn-js.css'
|
||||||
|
import '@opentiny/vue-theme/process-designer/index.less'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: [
|
||||||
|
'ready',
|
||||||
|
'import-done',
|
||||||
|
'import-error',
|
||||||
|
'save-xml',
|
||||||
|
'save-svg',
|
||||||
|
'element.click',
|
||||||
|
'element.dblclick',
|
||||||
|
'element.hover',
|
||||||
|
'element.out',
|
||||||
|
'element.changed',
|
||||||
|
'shape.added',
|
||||||
|
'shape.removed',
|
||||||
|
'connection.added',
|
||||||
|
'connection.removed',
|
||||||
|
'commandStack.changed',
|
||||||
|
'selection.changed',
|
||||||
|
'import.done',
|
||||||
|
'saveXML.done'
|
||||||
|
],
|
||||||
|
props: [...props, 'value', 'xml', 'readonly', 'showToolbar', 'height', 'fileName', 'options'],
|
||||||
|
components: {
|
||||||
|
IconZoomIn: iconZoomIn(),
|
||||||
|
IconZoomOut: iconZoomOut(),
|
||||||
|
IconUndo: iconUndo(),
|
||||||
|
IconRedo: iconRedo(),
|
||||||
|
IconFullscreen: iconFullscreen(),
|
||||||
|
IconDownload: iconDownload()
|
||||||
|
},
|
||||||
|
setup(props, context) {
|
||||||
|
return setup({ props, context, renderless, api })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
Loading…
Reference in New Issue