someone-oa/pc/settings-workflow.html
2025-12-11 19:04:46 +08:00

550 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>