<template>
<div id="app">
<div id="container">
<div id="header">
<div class="logo">
<div>
<div class="header-title">流程图设计器</div>
</div>
</div>
<div class="button-group">
<button id="save-button" class="button button-save" @click="saveDiagram"><i>💾</i> 保存</button>
<button id="reset-button" class="button button-reset" @click="resetDiagram"><i>🔄</i> 重置</button>
</div>
</div>
<div id="canvas-container">
<button id="toggle-sidebar" class="toggle-sidebar" @click="toggleSidebar">
{{ sidebarOpen ? '关闭面板' : '属性面板' }}
</button>
<div id="canvas"></div>
</div>
<div id="status-bar">
<div class="status-item">
<div class="status-indicator" :style="statusIndicatorStyle"></div>
<span>{{ statusText }}</span>
</div>
<div class="status-item">
<span>{{ elementCount }} 个元素</span>
</div>
<div class="status-item">
<span>{{ lastSaveText }}</span>
</div>
</div>
</div>
<div id="notification" class="notification" :class="{ show: showNotification, [notificationType]: true }">
{{ notificationMessage }}
</div>
<div id="sidebar" class="sidebar" :class="{ open: sidebarOpen }">
<h3>元素属性</h3>
<div class="properties-group">
<div class="property-item">
<label for="element-name">名称</label>
<input
type="text"
id="element-name"
placeholder="输入元素名称"
v-model="elementName"
@input="updateElementProperties"
/>
</div>
<div class="property-item">
<label for="element-desc">描述</label>
<textarea
id="element-desc"
rows="3"
placeholder="输入元素描述"
v-model="elementDesc"
@input="updateElementProperties"
></textarea>
</div>
</div>
</div>
</div>
</template>
<script>
import BpmnJS from 'bpmn-js/dist/bpmn-modeler.development.js'
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
export default {
name: 'ActivitiDesigner',
data() {
return {
bpmnModeler: null,
isModelerReady: false,
sidebarOpen: false,
showNotification: false,
notificationMessage: '',
notificationType: '',
statusText: '就绪',
elementCount: 0,
lastSaveText: '未保存',
currentElement: null,
elementName: '',
elementDesc: '',
elementBusinessObject: null,
}
},
computed: {
statusIndicatorStyle() {
switch (this.statusText) {
case '就绪':
return { backgroundColor: '#2ecc71' }
case '加载中':
return { backgroundColor: '#f39c12' }
case '保存中':
return { backgroundColor: '#3498db' }
case '错误':
return { backgroundColor: '#e74c3c' }
default:
return { backgroundColor: '#95a5a6' }
}
},
},
mounted() {
this.initializeModeler()
},
methods: {
async initializeModeler() {
try {
this.showNotification = true
this.notificationMessage = '正在初始化流程图设计器...'
this.notificationType = 'warning'
// 创建BPMN模型实例
this.bpmnModeler = new BpmnJS({
container: '#canvas',
keyboard: {
bindTo: document,
},
})
// 监听模型就绪事件
this.bpmnModeler.on('import.done', (event) => {
console.log('BPMN模型加载完成')
this.isModelerReady = true
this.statusText = '就绪'
// 更新元素计数
this.updateElementCount()
// 显示成功通知
this.showNotificationMessage('流程图加载完成!', 'success')
// 监听元素选择事件
this.bpmnModeler.on('element.click', (event) => {
this.handleElementSelection(event.element)
})
})
// 监听错误事件
this.bpmnModeler.on('error', (err) => {
console.error('BPMN模型错误', err)
this.isModelerReady = false
this.statusText = '错误'
this.showNotificationMessage(`模型错误: ${err.message}`, 'error')
})
// 监听元素变化事件
this.bpmnModeler.on('element.changed', () => {
this.updateElementCount()
})
// 创建新流程图
await this.createNewDiagram()
} catch (err) {
console.error('初始化BPMN模型失败', err)
this.showNotificationMessage(`初始化失败: ${err.message}`, 'error')
}
},
async createNewDiagram() {
if (!this.bpmnModeler) return
try {
this.statusText = '加载中'
await this.bpmnModeler.createDiagram()
console.log('创建流程图成功')
this.lastSaveText = '未保存'
this.showNotificationMessage('已创建新流程图', 'success')
this.statusText = '就绪'
} catch (err) {
console.error('创建流程图失败', err)
this.showNotificationMessage(`创建流程图失败: ${err.message}`, 'error')
this.statusText = '错误'
}
},
async saveDiagram() {
if (!this.bpmnModeler || !this.isModelerReady) {
this.showNotificationMessage('模型尚未准备好,请稍后再试', 'warning')
return
}
try {
this.statusText = '保存中'
// 保存XML
const { xml } = await this.bpmnModeler.saveXML({ format: true })
console.log('BPMN XML:', xml)
// 在实际应用中,这里可以发送到服务器保存
// await this.saveToServer(xml);
// 更新状态
const now = new Date()
this.lastSaveText = `最后保存: ${now.toLocaleTimeString()}`
// 显示成功通知
this.showNotificationMessage('流程图保存成功!XML内容已输出到控制台', 'success')
// 恢复状态
setTimeout(() => {
this.statusText = '就绪'
}, 1000)
} catch (err) {
console.error('保存失败', err)
this.showNotificationMessage(`保存失败: ${err.message}`, 'error')
this.statusText = '错误'
}
},
resetDiagram() {
if (confirm('确定要重置吗?当前内容将丢失。')) {
this.createNewDiagram()
this.elementName = ''
this.elementDesc = ''
this.currentElement = null
}
},
toggleSidebar() {
this.sidebarOpen = !this.sidebarOpen
},
updateElementCount() {
if (!this.bpmnModeler) return
try {
const elementRegistry = this.bpmnModeler.get('elementRegistry')
this.elementCount = elementRegistry ? elementRegistry.getAll().length : 0
} catch (err) {
console.error('更新元素计数失败', err)
}
},
showNotificationMessage(message, type) {
this.notificationMessage = message
this.notificationType = type
this.showNotification = true
setTimeout(() => {
this.showNotification = false
}, 3000)
},
handleElementSelection(element) {
this.currentElement = element
this.elementBusinessObject = element.businessObject
console.log(element,'element')
// 加载元素属性
this.elementName = this.elementBusinessObject.name || ''
this.elementDesc = this.elementBusinessObject.description || ''
// 打开属性面板
this.sidebarOpen = true
},
updateElementProperties() {
if (!this.currentElement || !this.bpmnModeler || !this.elementBusinessObject) return
try {
const modeling = this.bpmnModeler.get('modeling')
// 更新名称
modeling.updateProperties(this.currentElement, {
name: this.elementName,
description: this.elementDesc,
})
this.showNotificationMessage('元素属性已更新', 'success')
} catch (err) {
console.error('更新元素属性失败', err)
this.showNotificationMessage(`更新属性失败: ${err.message}`, 'error')
}
},
},
}
</script>
<style scoped>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body,
html {
height: 100%;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
color: #333;
overflow: hidden;
}
#container {
display: flex;
flex-direction: column;
height: calc(100vh - 150px) ;
max-width: 100%;
margin: 0 auto;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.1);
}
#header {
background: linear-gradient(to right, #2c3e50, #4a6491);
color: white;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.logo {
display: flex;
align-items: center;
gap: 15px;
}
.logo-icon {
font-size: 28px;
color: #3498db;
}
.header-title {
font-size: 22px;
font-weight: 600;
}
.header-subtitle {
font-size: 14px;
opacity: 0.8;
margin-top: 3px;
}
.button-group {
display: flex;
gap: 12px;
}
.button {
padding: 10px 20px;
border: none;
border-radius: 5px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.button:active {
transform: translateY(0);
}
.button-save {
background: linear-gradient(to right, #27ae60, #2ecc71);
color: white;
}
.button-reset {
background: linear-gradient(to right, #e74c3c, #c0392b);
color: white;
}
.button-export {
background: linear-gradient(to right, #3498db, #2980b9);
color: white;
}
#canvas-container {
flex: 1;
position: relative;
overflow: hidden;
background: #f8f9fa;
}
#canvas {
height: 100%;
width: 100%;
}
#status-bar {
background: #2c3e50;
color: #ecf0f1;
padding: 8px 20px;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
}
.status-item {
display: flex;
align-items: center;
gap: 8px;
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #2ecc71;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
border-radius: 5px;
color: white;
font-weight: 500;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
z-index: 1000;
opacity: 0;
transform: translateX(100%);
transition: all 0.4s ease;
}
.notification.show {
opacity: 1;
transform: translateX(0);
}
.notification.success {
background: linear-gradient(to right, #27ae60, #2ecc71);
}
.notification.error {
background: linear-gradient(to right, #e74c3c, #c0392b);
}
.notification.warning {
background: linear-gradient(to right, #f39c12, #e67e22);
}
.sidebar {
position: absolute;
top: 260px;
right: 20px;
width: 300px;
height: 50%;
background: white;
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1);
z-index: 5;
padding: 20px;
overflow-y: auto;
display: none;
}
.sidebar.open {
display: inline
}
.sidebar h3 {
margin-bottom: 15px;
color: #2c3e50;
border-bottom: 2px solid #3498db;
padding-bottom: 10px;
}
.properties-group {
margin-bottom: 20px;
}
.property-item {
margin-bottom: 15px;
}
.property-item label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #2c3e50;
}
.property-item input,
.property-item textarea,
.property-item select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.toggle-sidebar {
position: absolute;
top: 20px;
right: 20px;
background: #3498db;
color: white;
border: none;
border-radius: 5px;
padding: 8px 15px;
cursor: pointer;
z-index: 6;
}
.tools-palette {
position: absolute;
top: 20px;
left: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
padding: 15px;
z-index: 5;
display: flex;
flex-direction: column;
gap: 10px;
}
.tool-btn {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: #f8f9fa;
border: 1px solid #e0e0e0;
cursor: pointer;
transition: all 0.2s;
}
.tool-btn:hover {
background: #3498db;
color: white;
transform: scale(1.1);
}
.tool-btn.active {
background: #3498db;
color: white;
}
</style>
描述没有更新
最新发布