<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Activiti流程图设计器</title>
<!-- 引入BPMN-JS的CSS -->
<link rel="stylesheet" href="https://unpkg.com/bpmn-js@14.0.0/dist/assets/diagram-js.css">
<link rel="stylesheet" href="https://unpkg.com/bpmn-js@14.0.0/dist/assets/bpmn-js.css">
<link rel="stylesheet" href="https://unpkg.com/bpmn-js@14.0.0/dist/assets/bpmn-font/css/bpmn.css">
<style>
* {
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: 100vh;
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: 0;
right: 0;
width: 300px;
height: 100%;
background: white;
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1);
transform: translateX(100%);
transition: transform 0.3s ease;
z-index: 5;
padding: 20px;
overflow-y: auto;
}
.sidebar.open {
transform: translateX(0);
}
.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>
</head>
<body>
<div id="container">
<div id="header">
<div class="logo">
<!-- <div class="logo-icon">📊</div> -->
<div>
<div class="header-title">流程图设计器</div>
<!-- <div class="header-subtitle">基于BPMN.js的专业工作流设计工具</div> -->
</div>
</div>
<div class="button-group">
<button id="save-button" class="button button-save">
<i>💾</i> 保存
</button>
<!-- <button id="export-button" class="button button-export">
<i>📤</i> 导出XML
</button> -->
<button id="reset-button" class="button button-reset">
<i>🔄</i> 重置
</button>
</div>
</div>
<div id="canvas-container">
<button id="toggle-sidebar" class="toggle-sidebar">属性面板</button>
<div id="canvas"></div>
<!-- <div class="tools-palette">
<div class="tool-btn active" title="选择工具">↖️</div>
<div class="tool-btn" title="创建任务">📝</div>
<div class="tool-btn" title="创建网关">⚖️</div>
<div class="tool-btn" title="创建事件">🎯</div>
</div> -->
</div>
<div id="status-bar">
<div class="status-item">
<div class="status-indicator"></div>
<span>就绪</span>
</div>
<div class="status-item">
<span id="element-count">0 个元素</span>
</div>
<div class="status-item">
<span id="last-save">未保存</span>
</div>
</div>
</div>
<div id="notification" class="notification"></div>
<div id="sidebar" class="sidebar">
<h3>元素属性</h3>
<div class="properties-group">
<div class="property-item">
<label for="element-name">名称</label>
<input type="text" id="element-name" placeholder="输入元素名称">
</div>
<!-- <div class="property-item">
<label for="element-id">ID</label>
<input type="text" id="element-id" readonly>
</div> -->
<div class="property-item">
<label for="element-desc">描述</label>
<textarea id="element-desc" rows="3" placeholder="输入元素描述"></textarea>
</div>
<!-- <div class="property-item">
<label for="element-type">类型</label>
<input type="text" id="element-type" readonly>
</div> -->
</div>
<!-- <div class="properties-group">
<h4>执行属性</h4>
<div class="property-item">
<label for="element-assignee">负责人</label>
<input type="text" id="element-assignee" placeholder="输入负责人">
</div>
<div class="property-item">
<label for="element-candidate">候选组</label>
<input type="text" id="element-candidate" placeholder="输入候选组">
</div>
<div class="property-item">
<label for="element-due">到期时间</label>
<input type="text" id="element-due" placeholder="输入到期时间">
</div>
</div> -->
</div>
<!-- 引入BPMN-JS的JavaScript库 -->
<script src="https://unpkg.com/bpmn-js@14.0.0/dist/bpmn-modeler.development.js"></script>
<script>
// 初始化BPMN设计器
let bpmnModeler = null;
let isModelerReady = false;
// DOM元素引用
const canvas = document.getElementById('canvas');
const saveButton = document.getElementById('save-button');
const resetButton = document.getElementById('reset-button');
// const exportButton = document.getElementById('export-button');
const toggleSidebar = document.getElementById('toggle-sidebar');
const sidebar = document.getElementById('sidebar');
const notification = document.getElementById('notification');
const statusIndicator = document.querySelector('.status-indicator');
const elementCount = document.getElementById('element-count');
const lastSave = document.getElementById('last-save');
// 初始化设计器
async function initializeModeler() {
try {
// 创建BPMN模型实例
bpmnModeler = new BpmnJS({
container: canvas,
keyboard: {
bindTo: document
}
});
// 监听模型就绪事件
bpmnModeler.on('import.done', function(event) {
console.log('BPMN模型加载完成');
isModelerReady = true;
updateStatusIndicator('ready');
// 更新元素计数
updateElementCount();
// 显示成功通知
showNotification('流程图加载完成!', 'success');
});
// 监听错误事件
bpmnModeler.on('error', function(err) {
console.error('BPMN模型错误', err);
isModelerReady = false;
updateStatusIndicator('error');
showNotification(`模型错误: ${err.message}`, 'error');
});
// 监听元素变化事件
bpmnModeler.on('element.changed', function(event) {
updateElementCount();
});
// 创建新流程图
await createNewDiagram();
} catch (err) {
console.error('初始化BPMN模型失败', err);
showNotification(`初始化失败: ${err.message}`, 'error');
}
}
// 创建新的流程图
async function createNewDiagram() {
if (!bpmnModeler) return;
try {
updateStatusIndicator('loading');
await bpmnModeler.createDiagram();
console.log('创建流程图成功');
lastSave.textContent = '未保存';
showNotification('已创建新流程图', 'success');
} catch (err) {
console.error('创建流程图失败', err);
showNotification(`创建流程图失败: ${err.message}`, 'error');
}
}
// 保存流程图 - 解决saveXML未触发的问题
async function saveDiagram() {
if (!bpmnModeler || !isModelerReady) {
showNotification('模型尚未准备好,请稍后再试', 'warning');
return;
}
try {
updateStatusIndicator('saving');
// 使用Promise方式调用saveXML解决未触发问题
const { xml } = await bpmnModeler.saveXML({ format: true });
console.log('BPMN XML:', xml);
// 在实际应用中,这里可以发送到服务器保存
// await saveToServer(xml);
// 更新状态
const now = new Date();
lastSave.textContent = `最后保存: ${now.toLocaleTimeString()}`;
// 显示成功通知
showNotification('流程图保存成功!XML内容已输出到控制台', 'success');
// 恢复状态
setTimeout(() => updateStatusIndicator('ready'), 1000);
} catch (err) {
console.error('保存失败', err);
showNotification(`保存失败: ${err.message}`, 'error');
updateStatusIndicator('error');
}
}
// 导出XML
async function exportXML() {
if (!bpmnModeler || !isModelerReady) {
showNotification('模型尚未准备好,请稍后再试', 'warning');
return;
}
try {
const { xml } = await bpmnModeler.saveXML({ format: true });
// 创建下载链接
const blob = new Blob([xml], { type: 'application/xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'activiti-process.bpmn';
document.body.appendChild(a);
a.click();
// 清理
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
showNotification('BPMN XML已导出', 'success');
} catch (err) {
console.error('导出失败', err);
showNotification(`导出失败: ${err.message}`, 'error');
}
}
// 重置流程图
function resetDiagram() {
if (confirm('确定要重置吗?当前内容将丢失。')) {
createNewDiagram();
}
}
// 更新状态指示器
function updateStatusIndicator(status) {
switch (status) {
case 'ready':
statusIndicator.style.backgroundColor = '#2ecc71';
break;
case 'loading':
statusIndicator.style.backgroundColor = '#f39c12';
break;
case 'saving':
statusIndicator.style.backgroundColor = '#3498db';
break;
case 'error':
statusIndicator.style.backgroundColor = '#e74c3c';
break;
default:
statusIndicator.style.backgroundColor = '#95a5a6';
}
}
// 更新元素计数
function updateElementCount() {
if (!bpmnModeler) return;
try {
const elementRegistry = bpmnModeler.get('elementRegistry');
const count = elementRegistry ? elementRegistry.getAll().length : 0;
elementCount.textContent = `${count} 个元素`;
} catch (err) {
console.error('更新元素计数失败', err);
}
}
// 显示通知
function showNotification(message, type) {
notification.textContent = message;
notification.className = `notification ${type} show`;
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// 页面加载完成后初始化
window.addEventListener('load', async function() {
// 显示初始化通知
showNotification('正在初始化流程图设计器...', 'warning');
// 初始化设计器
await initializeModeler();
// 绑定按钮事件
saveButton.addEventListener('click', saveDiagram);
resetButton.addEventListener('click', resetDiagram);
// exportButton.addEventListener('click', exportXML);
toggleSidebar.addEventListener('click', function() {
sidebar.classList.toggle('open');
toggleSidebar.textContent = sidebar.classList.contains('open') ? '关闭面板' : '属性面板';
});
});
</script>
</body>
</html>
转化为vue文件
最新发布