550 lines
24 KiB
HTML
550 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>流程配置 - OA系统</title>
|
||
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
|
||
<!-- 统一布局样式 -->
|
||
<link rel="stylesheet" href="unified-layout.css">
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;
|
||
background: #f0f2f5;
|
||
overflow: hidden;
|
||
}
|
||
.layout-container {
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.main-container {
|
||
flex: 1;
|
||
display: flex;
|
||
overflow: hidden;
|
||
}
|
||
.app-main {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #f0f2f5;
|
||
}
|
||
.sidebar {
|
||
width: 210px;
|
||
background: #fff;
|
||
border-right: 1px solid #e6e6e6;
|
||
overflow-y: auto;
|
||
}
|
||
.menu-item { padding: 12px 24px; cursor: pointer; transition: background 0.3s; color: #303133; }
|
||
.menu-item:hover {
|
||
background: #ecf5ff;
|
||
color: #409EFF;
|
||
}
|
||
.menu-item.active { background: #409EFF; color: #fff; }
|
||
.content {
|
||
flex: 1;
|
||
padding: 20px;
|
||
overflow-y: auto;
|
||
background: #fff;
|
||
margin: 10px;
|
||
border-radius: 4px;
|
||
}
|
||
.page-header {
|
||
margin-bottom: 24px;
|
||
padding-bottom: 16px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.page-title { font-size: 20px; font-weight: 500; color: #303133; }
|
||
.workflow-tabs {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-bottom: 24px;
|
||
border-bottom: 2px solid #e6e6e6;
|
||
}
|
||
.tab-item {
|
||
padding: 12px 24px;
|
||
cursor: pointer;
|
||
border-bottom: 2px solid transparent;
|
||
margin-bottom: -2px;
|
||
color: #606266;
|
||
transition: all 0.3s;
|
||
}
|
||
.tab-item.active {
|
||
color: #409EFF;
|
||
border-bottom-color: #409EFF;
|
||
}
|
||
.workflow-card {
|
||
background: #fff;
|
||
border: 1px solid #e6e6e6;
|
||
border-radius: 4px;
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.card-title {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
.node-list {
|
||
margin-top: 16px;
|
||
}
|
||
.node-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16px;
|
||
background: #fafafa;
|
||
border-radius: 4px;
|
||
margin-bottom: 12px;
|
||
border: 1px solid #e6e6e6;
|
||
}
|
||
.node-order {
|
||
width: 32px;
|
||
height: 32px;
|
||
background: #409EFF;
|
||
color: #fff;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: bold;
|
||
margin-right: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
.node-info {
|
||
flex: 1;
|
||
}
|
||
.node-name {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #303133;
|
||
margin-bottom: 4px;
|
||
}
|
||
.node-desc {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
.node-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
.btn {
|
||
padding: 6px 16px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
.btn-primary {
|
||
background: #409EFF;
|
||
color: white;
|
||
}
|
||
.btn-primary:hover {
|
||
background: #66b1ff;
|
||
}
|
||
.btn-default {
|
||
background: white;
|
||
color: #606266;
|
||
border: 1px solid #dcdfe6;
|
||
}
|
||
.btn-default:hover {
|
||
color: #409EFF;
|
||
border-color: #409EFF;
|
||
}
|
||
.btn-small {
|
||
padding: 4px 12px;
|
||
font-size: 12px;
|
||
}
|
||
.config-form {
|
||
margin-top: 16px;
|
||
}
|
||
.form-row {
|
||
display: flex;
|
||
gap: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.form-item {
|
||
flex: 1;
|
||
}
|
||
.form-label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
color: #606266;
|
||
font-size: 14px;
|
||
}
|
||
.form-input, .form-select {
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border: 1px solid #dcdfe6;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
.form-input:focus, .form-select:focus {
|
||
outline: none;
|
||
border-color: #409EFF;
|
||
}
|
||
.action-buttons {
|
||
margin-top: 24px;
|
||
padding-top: 24px;
|
||
border-top: 1px solid #f0f0f0;
|
||
text-align: right;
|
||
}
|
||
</style>
|
||
<!-- Vue.js -->
|
||
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
|
||
<!-- Element UI JS -->
|
||
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
<div class="layout-container">
|
||
<div class="main-container">
|
||
<div class="sidebar">
|
||
<div class="menu-item" onclick="window.location.href='settings-org.html'">组织架构</div>
|
||
<div class="menu-item" onclick="window.location.href='settings-user.html'">账号管理</div>
|
||
<div class="menu-item" onclick="window.location.href='settings-role.html'">权限配置</div>
|
||
<div class="menu-item active" onclick="window.location.href='settings-workflow.html'">流程配置</div>
|
||
<div class="menu-item" onclick="window.location.href='settings-notice.html'">系统公告</div>
|
||
<div class="menu-item" onclick="window.location.href='settings-dict.html'">字典管理</div>
|
||
<div class="menu-item" onclick="window.location.href='settings-param.html'">参数配置</div>
|
||
<div class="menu-item" onclick="window.location.href='settings-log.html'">操作日志</div>
|
||
</div>
|
||
|
||
<div class="app-main">
|
||
<div class="content">
|
||
<div class="page-header">
|
||
<div class="page-title">流程配置</div>
|
||
<button class="btn btn-primary" onclick="saveWorkflow()">保存配置</button>
|
||
</div>
|
||
|
||
<div class="workflow-tabs">
|
||
<div class="tab-item active" onclick="switchTab('initiation')">立项审核流程</div>
|
||
<div class="tab-item" onclick="switchTab('start')">启动审核流程</div>
|
||
<div class="tab-item" onclick="switchTab('output')">成果审核流程</div>
|
||
<div class="tab-item" onclick="switchTab('invoice')">开票审核流程</div>
|
||
<div class="tab-item" onclick="switchTab('request')">请款审核流程</div>
|
||
</div>
|
||
|
||
<!-- 立项审核流程 -->
|
||
<div id="tab-initiation" class="tab-content">
|
||
<div class="workflow-card">
|
||
<div class="card-title">审核节点配置</div>
|
||
<div class="node-list">
|
||
<!-- 节点将动态渲染 -->
|
||
</div>
|
||
<button class="btn btn-primary" onclick="addNode('initiation')" style="margin-top: 12px;">+ 添加审核节点</button>
|
||
</div>
|
||
|
||
<div class="workflow-card">
|
||
<div class="card-title">流程规则配置</div>
|
||
<div class="config-form">
|
||
<div class="form-row">
|
||
<div class="form-item">
|
||
<label class="form-label">审核超时提醒</label>
|
||
<select class="form-select">
|
||
<option>开启</option>
|
||
<option>关闭</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-item">
|
||
<label class="form-label">默认超时时间(工作日)</label>
|
||
<input type="number" class="form-input" value="3" min="1">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-item">
|
||
<label class="form-label">金额阈值配置(万元)</label>
|
||
<input type="number" class="form-input" value="50" placeholder="超过此金额需总经理审核">
|
||
</div>
|
||
<div class="form-item">
|
||
<label class="form-label">自动提醒时间</label>
|
||
<select class="form-select">
|
||
<option>超时前1天</option>
|
||
<option>超时当天</option>
|
||
<option>超时后1天</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 其他流程标签页内容(默认隐藏) -->
|
||
<div id="tab-start" class="tab-content" style="display: none;">
|
||
<div class="workflow-card">
|
||
<div class="card-title">启动审核流程配置</div>
|
||
<div class="node-list">
|
||
<!-- 节点将动态渲染 -->
|
||
</div>
|
||
<button class="btn btn-primary" onclick="addNode('start')" style="margin-top: 12px;">+ 添加审核节点</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="tab-output" class="tab-content" style="display: none;">
|
||
<div class="workflow-card">
|
||
<div class="card-title">成果审核流程配置</div>
|
||
<div style="margin-bottom: 16px; padding: 12px; background: #f0f9ff; border-left: 4px solid #409EFF; border-radius: 4px;">
|
||
<strong>说明:</strong>成果审核分为初稿/对账审核和最终成果审核两种流程
|
||
</div>
|
||
<div style="margin-top: 20px; margin-bottom: 16px; font-weight: 500;">初稿/对账审核流程:</div>
|
||
<div class="node-list" id="output-draft-list">
|
||
<!-- 节点将动态渲染 -->
|
||
</div>
|
||
<button class="btn btn-primary" onclick="addNode('output-draft')" style="margin-top: 12px;">+ 添加审核节点</button>
|
||
<div style="margin-top: 24px; margin-bottom: 16px; font-weight: 500;">最终成果审核流程:</div>
|
||
<div class="node-list" id="output-final-list">
|
||
<!-- 节点将动态渲染 -->
|
||
</div>
|
||
<button class="btn btn-primary" onclick="addNode('output-final')" style="margin-top: 12px;">+ 添加审核节点</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="tab-invoice" class="tab-content" style="display: none;">
|
||
<div class="workflow-card">
|
||
<div class="card-title">开票审核流程配置</div>
|
||
<div class="node-list">
|
||
<!-- 节点将动态渲染 -->
|
||
</div>
|
||
<button class="btn btn-primary" onclick="addNode('invoice')" style="margin-top: 12px;">+ 添加审核节点</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="tab-request" class="tab-content" style="display: none;">
|
||
<div class="workflow-card">
|
||
<div class="card-title">请款审核流程配置</div>
|
||
<div class="node-list">
|
||
<!-- 节点将动态渲染 -->
|
||
</div>
|
||
<button class="btn btn-primary" onclick="addNode('request')" style="margin-top: 12px;">+ 添加审核节点</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="common.js"></script>
|
||
<script>
|
||
// 存储节点数据
|
||
let workflowNodes = {
|
||
'initiation': [
|
||
{ id: 1, name: '部门负责人审核', approver: '部门负责人角色', condition: '无条件', timeout: 3 },
|
||
{ id: 2, name: '经营管理部审核', approver: '经营管理部负责人', condition: '无条件', timeout: 3 },
|
||
{ id: 3, name: '总经理审核', approver: '王总', condition: '金额≥50万', timeout: 5 }
|
||
],
|
||
'start': [
|
||
{ id: 1, name: '经营管理部审核', approver: '经营管理部负责人', condition: '无条件', timeout: 3 },
|
||
{ id: 2, name: '总经理审核', approver: '王总', condition: '无条件', timeout: 5 }
|
||
],
|
||
'output-draft': [
|
||
{ id: 1, name: '组长审核', approver: '项目组长', condition: '所有人员提交完成', timeout: 3 },
|
||
{ id: 2, name: '总经理审核', approver: '王总', condition: '无条件', timeout: 5 }
|
||
],
|
||
'output-final': [
|
||
{ id: 1, name: '组长审核', approver: '项目组长', condition: '所有人员提交完成', timeout: 3 },
|
||
{ id: 2, name: '总经理审核', approver: '王总、戚总', condition: '无条件', timeout: 5 }
|
||
],
|
||
'invoice': [
|
||
{ id: 1, name: '财务审核', approver: '财务角色', condition: '项目有合同或状态为已提交正式成果', timeout: 2 }
|
||
],
|
||
'request': [
|
||
{ id: 1, name: '总经理审核', approver: '王总', condition: '无条件', timeout: 3 },
|
||
{ id: 2, name: '出纳放款', approver: '出纳角色', condition: '总经理审核通过', timeout: 0 }
|
||
]
|
||
};
|
||
|
||
function switchTab(tab) {
|
||
// 隐藏所有标签页内容
|
||
document.querySelectorAll('.tab-content').forEach(content => {
|
||
content.style.display = 'none';
|
||
});
|
||
// 移除所有标签激活状态
|
||
document.querySelectorAll('.tab-item').forEach(item => {
|
||
item.classList.remove('active');
|
||
});
|
||
// 显示选中的标签页
|
||
document.getElementById('tab-' + tab).style.display = 'block';
|
||
event.target.classList.add('active');
|
||
}
|
||
|
||
function renderNodes(workflowType) {
|
||
const nodes = workflowNodes[workflowType] || [];
|
||
let nodeList;
|
||
|
||
// 特殊处理成果审核的两个列表
|
||
if (workflowType === 'output-draft') {
|
||
nodeList = document.getElementById('output-draft-list');
|
||
} else if (workflowType === 'output-final') {
|
||
nodeList = document.getElementById('output-final-list');
|
||
} else {
|
||
nodeList = document.querySelector(`#tab-${workflowType.split('-')[0]} .node-list`);
|
||
}
|
||
|
||
if (!nodeList) return;
|
||
|
||
nodeList.innerHTML = nodes.map((node, index) => `
|
||
<div class="node-item">
|
||
<div class="node-order">${index + 1}</div>
|
||
<div class="node-info">
|
||
<div class="node-name">${node.name}</div>
|
||
<div class="node-desc">审核人:${node.approver} | 审核条件:${node.condition} | 超时:${node.timeout || '-'}个工作日</div>
|
||
</div>
|
||
<div class="node-actions">
|
||
<button class="btn btn-default btn-small" onclick="editNode('${workflowType}', ${node.id})">编辑</button>
|
||
<button class="btn btn-default btn-small" onclick="deleteNode('${workflowType}', ${node.id})">删除</button>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function addNode(workflowType) {
|
||
const content = `
|
||
<form id="node-form" style="padding: 10px 0;">
|
||
<div style="margin-bottom: 16px;">
|
||
<label style="display: block; margin-bottom: 8px; color: #606266;">节点名称 <span style="color: #f56c6c;">*</span></label>
|
||
<input type="text" id="node-name" class="form-input" placeholder="请输入节点名称" required>
|
||
</div>
|
||
<div style="margin-bottom: 16px;">
|
||
<label style="display: block; margin-bottom: 8px; color: #606266;">审核人 <span style="color: #f56c6c;">*</span></label>
|
||
<input type="text" id="node-approver" class="form-input" placeholder="请输入审核人" required>
|
||
</div>
|
||
<div style="margin-bottom: 16px;">
|
||
<label style="display: block; margin-bottom: 8px; color: #606266;">审核条件</label>
|
||
<input type="text" id="node-condition" class="form-input" placeholder="请输入审核条件(如:无条件、金额≥50万等)">
|
||
</div>
|
||
<div style="margin-bottom: 16px;">
|
||
<label style="display: block; margin-bottom: 8px; color: #606266;">超时时间(工作日)</label>
|
||
<input type="number" id="node-timeout" class="form-input" placeholder="请输入超时天数" min="0" value="3">
|
||
</div>
|
||
</form>
|
||
`;
|
||
|
||
CommonUtils.createModal('添加审核节点', content, () => {
|
||
const name = document.getElementById('node-name').value.trim();
|
||
const approver = document.getElementById('node-approver').value.trim();
|
||
const condition = document.getElementById('node-condition').value.trim() || '无条件';
|
||
const timeout = parseInt(document.getElementById('node-timeout').value) || 0;
|
||
|
||
if (!name || !approver) {
|
||
CommonUtils.showMessage('请填写必填项', 'error');
|
||
return false;
|
||
}
|
||
|
||
if (!workflowNodes[workflowType]) {
|
||
workflowNodes[workflowType] = [];
|
||
}
|
||
|
||
const maxId = workflowNodes[workflowType].length > 0
|
||
? Math.max(...workflowNodes[workflowType].map(n => n.id))
|
||
: 0;
|
||
|
||
workflowNodes[workflowType].push({
|
||
id: maxId + 1,
|
||
name: name,
|
||
approver: approver,
|
||
condition: condition,
|
||
timeout: timeout
|
||
});
|
||
|
||
renderNodes(workflowType);
|
||
CommonUtils.showMessage('节点添加成功');
|
||
return true;
|
||
});
|
||
}
|
||
|
||
function editNode(workflowType, nodeId) {
|
||
const nodes = workflowNodes[workflowType] || [];
|
||
const node = nodes.find(n => n.id === nodeId);
|
||
if (!node) return;
|
||
|
||
const content = `
|
||
<form id="node-form" style="padding: 10px 0;">
|
||
<div style="margin-bottom: 16px;">
|
||
<label style="display: block; margin-bottom: 8px; color: #606266;">节点名称 <span style="color: #f56c6c;">*</span></label>
|
||
<input type="text" id="node-name" class="form-input" value="${node.name}" required>
|
||
</div>
|
||
<div style="margin-bottom: 16px;">
|
||
<label style="display: block; margin-bottom: 8px; color: #606266;">审核人 <span style="color: #f56c6c;">*</span></label>
|
||
<input type="text" id="node-approver" class="form-input" value="${node.approver}" required>
|
||
</div>
|
||
<div style="margin-bottom: 16px;">
|
||
<label style="display: block; margin-bottom: 8px; color: #606266;">审核条件</label>
|
||
<input type="text" id="node-condition" class="form-input" value="${node.condition}">
|
||
</div>
|
||
<div style="margin-bottom: 16px;">
|
||
<label style="display: block; margin-bottom: 8px; color: #606266;">超时时间(工作日)</label>
|
||
<input type="number" id="node-timeout" class="form-input" value="${node.timeout || 0}" min="0">
|
||
</div>
|
||
</form>
|
||
`;
|
||
|
||
CommonUtils.createModal('编辑审核节点', content, () => {
|
||
const name = document.getElementById('node-name').value.trim();
|
||
const approver = document.getElementById('node-approver').value.trim();
|
||
const condition = document.getElementById('node-condition').value.trim() || '无条件';
|
||
const timeout = parseInt(document.getElementById('node-timeout').value) || 0;
|
||
|
||
if (!name || !approver) {
|
||
CommonUtils.showMessage('请填写必填项', 'error');
|
||
return false;
|
||
}
|
||
|
||
Object.assign(node, { name, approver, condition, timeout });
|
||
renderNodes(workflowType);
|
||
CommonUtils.showMessage('节点更新成功');
|
||
return true;
|
||
});
|
||
}
|
||
|
||
function deleteNode(workflowType, nodeId) {
|
||
CommonUtils.createModal('确认删除', '确定要删除该审核节点吗?删除后无法恢复。', () => {
|
||
const nodes = workflowNodes[workflowType] || [];
|
||
const index = nodes.findIndex(n => n.id === nodeId);
|
||
if (index > -1) {
|
||
nodes.splice(index, 1);
|
||
renderNodes(workflowType);
|
||
CommonUtils.showMessage('节点已删除');
|
||
}
|
||
return true;
|
||
});
|
||
}
|
||
|
||
function saveWorkflow() {
|
||
// 保存到localStorage(实际项目中应该保存到后端)
|
||
localStorage.setItem('workflowNodes', JSON.stringify(workflowNodes));
|
||
CommonUtils.showMessage('流程配置已保存');
|
||
}
|
||
|
||
// 初始化:从localStorage加载数据
|
||
window.addEventListener('DOMContentLoaded', () => {
|
||
const saved = localStorage.getItem('workflowNodes');
|
||
if (saved) {
|
||
try {
|
||
workflowNodes = JSON.parse(saved);
|
||
} catch (e) {
|
||
console.error('加载配置失败', e);
|
||
}
|
||
}
|
||
|
||
// 渲染所有节点
|
||
Object.keys(workflowNodes).forEach(key => {
|
||
renderNodes(key);
|
||
});
|
||
});
|
||
</script>
|
||
<script src="unified-layout.js"></script>
|
||
<script>
|
||
initUnifiedLayout('settings');
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|