..
This commit is contained in:
@@ -0,0 +1,728 @@
|
||||
<template>
|
||||
<div class="flow-container">
|
||||
<!-- 页面标题栏 -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="title-section">
|
||||
<h2 class="page-title">{{ pageTitle }}</h2>
|
||||
<div class="page-breadcrumb">
|
||||
<span>工作流管理</span>
|
||||
<span class="separator">></span>
|
||||
<span>{{ isNewApplication ? "发起申请" : "任务处理" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-section">
|
||||
<span class="status-label">状态:</span>
|
||||
<span class="status-badge" :class="statusClass">{{ processStatusText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 左侧内容 -->
|
||||
<div class="content-left">
|
||||
<!-- 流程基本信息 -->
|
||||
<div class="info-panel">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">流程信息</h3>
|
||||
<div class="panel-actions">
|
||||
<el-button size="small" type="text" @click="showProcessDiagram" v-if="canShowDiagram">
|
||||
<i class="el-icon-view"></i>
|
||||
查看流程图
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<div class="info-table">
|
||||
<!-- 新申请场景 -->
|
||||
<template v-if="isNewApplication">
|
||||
<div class="info-row">
|
||||
<div class="info-label">流程名称:</div>
|
||||
<div class="info-value">{{ processDefinition.displayName || processDefinition.name }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">流程说明:</div>
|
||||
<div class="info-value">{{ processDefinition.description }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 已有流程场景 -->
|
||||
<template v-else-if="isExistingProcess">
|
||||
<div class="info-row">
|
||||
<div class="info-label">流程名称:</div>
|
||||
<div class="info-value">{{ processInstance.displayName }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">发起人:</div>
|
||||
<div class="info-value">{{ processInstance?.ext?.initiatorName || "-" }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">发起时间:</div>
|
||||
<div class="info-value">{{ formatDate(processInstance.createdAt) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 历史办理过程面板 -->
|
||||
<div v-if="shouldShowHistoryProcess" class="history-panel">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">办理过程</h3>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<div id="history-process-container" class="form-content">
|
||||
<div v-if="pjaxLoading.historyProcess" class="loading-state">
|
||||
<i class="el-icon-loading"></i>
|
||||
<span>正在加载历史办理过程...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单处理区域 -->
|
||||
<div class="form-panel" v-if="isNewApplication || todoTasks.map((v) => v.id).includes(taskId)">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">{{ formPanelTitle }}</h3>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<!-- 新申请表单 -->
|
||||
<div v-if="isNewApplication" id="application-form-container" class="form-content">
|
||||
<div v-if="pjaxLoading.apply" class="loading-state">
|
||||
<i class="el-icon-loading"></i>
|
||||
<span>正在加载申请表单...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前任务表单 -->
|
||||
<div v-if="shouldShowTaskForm" id="task-form-container" class="form-content">
|
||||
<div v-if="pjaxLoading.taskForm" class="loading-state">
|
||||
<i class="el-icon-loading"></i>
|
||||
<span>正在加载任务表单...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧审批记录 -->
|
||||
<!-- <div class="content-right" v-if="isExistingProcess">-->
|
||||
<!-- <snaker-flow-history-approval :task_id="taskId"></snaker-flow-history-approval>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
|
||||
<!-- 流程图对话框 -->
|
||||
<el-dialog title="流程图预览" :visible.sync="designVisible" width="80%" top="8vh">
|
||||
<div class="diagram-viewer">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "SnakerFlow",
|
||||
data() {
|
||||
return {
|
||||
// URL参数
|
||||
taskId: GetQueryString("taskId") ? parseInt(GetQueryString("taskId")) : null,
|
||||
instanceId: GetQueryString("instanceId"),
|
||||
businessId: GetQueryString("businessId"),
|
||||
defineKey: GetQueryString("defineKey"), // 新增:流程定义KEY
|
||||
|
||||
processInstance: {},
|
||||
//流程定义信息
|
||||
processDefinition: {},
|
||||
todoTasks: [],
|
||||
currentTask: {},
|
||||
|
||||
// 加载状态
|
||||
loading: false,
|
||||
// 错误信息
|
||||
error: null,
|
||||
|
||||
// PJAX 加载状态
|
||||
pjaxLoading: {
|
||||
apply: false,
|
||||
taskForm: false,
|
||||
historyProcess: false
|
||||
},
|
||||
|
||||
// 显示历史记录
|
||||
showHistory: false,
|
||||
|
||||
// 流程图相关
|
||||
designVisible: false,
|
||||
designUrl: "",
|
||||
|
||||
// 流程状态枚举映射
|
||||
processStatusMap: {
|
||||
10: { text: "进行中", class: "doing" },
|
||||
20: { text: "已完成", class: "finished" },
|
||||
30: { text: "已撤回", class: "withdraw" },
|
||||
40: { text: "强行终止", class: "interrupt" },
|
||||
45: { text: "已拒绝", class: "reject" },
|
||||
50: { text: "挂起", class: "pending" },
|
||||
99: { text: "已废弃", class: "abandon" }
|
||||
},
|
||||
|
||||
// 任务状态枚举
|
||||
taskStateEnum: {
|
||||
DOING: 10,
|
||||
FINISHED: 20,
|
||||
WITHDRAW: 30,
|
||||
INTERRUPT: 40,
|
||||
PENDING: 50,
|
||||
ABANDON: 99
|
||||
},
|
||||
|
||||
// 任务类型枚举
|
||||
performTypeEnum: {
|
||||
NORMAL: 0, // 普通任务
|
||||
COUNTERSIGN: 1 // 会签任务
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 页面标题
|
||||
pageTitle() {
|
||||
if (this.isNewApplication) {
|
||||
return this.processDefinition.displayName || this.processDefinition.name || "发起申请"
|
||||
} else {
|
||||
return this.processInstance.displayName || "任务处理"
|
||||
}
|
||||
},
|
||||
|
||||
// 流程状态文本
|
||||
processStatusText() {
|
||||
if (this.isNewApplication) {
|
||||
return "待提交"
|
||||
} else {
|
||||
return this.processStatusMap[this.processInstance.state]?.text || "未知状态"
|
||||
}
|
||||
},
|
||||
|
||||
// 状态样式类
|
||||
statusClass() {
|
||||
if (this.isNewApplication) {
|
||||
return "status-pending"
|
||||
} else {
|
||||
const statusClass = this.processStatusMap[this.processInstance.state]?.class
|
||||
return `status-${statusClass || "unknown"}`
|
||||
}
|
||||
},
|
||||
|
||||
// 表单面板标题
|
||||
formPanelTitle() {
|
||||
if (this.isNewApplication) {
|
||||
return "申请表单"
|
||||
} else if (this.shouldShowTaskForm) {
|
||||
return this.currentTask.displayName || "任务表单"
|
||||
} else if (this.shouldShowHistoryProcess) {
|
||||
return "办理过程"
|
||||
} else {
|
||||
return "表单信息"
|
||||
}
|
||||
},
|
||||
|
||||
// 是否显示历史办理过程
|
||||
shouldShowHistoryProcess() {
|
||||
// debugger
|
||||
// return this.businessId && this.isTaskInProgress && !this.isFirstTaskNode
|
||||
return true
|
||||
},
|
||||
|
||||
// 是否显示任务表单
|
||||
shouldShowTaskForm() {
|
||||
debugger
|
||||
return this.taskId != null
|
||||
},
|
||||
|
||||
// 是否可以显示流程图
|
||||
canShowDiagram() {
|
||||
return (this.isNewApplication && this.processDefinition.id) || (this.isExistingProcess && this.processInstance.processId)
|
||||
},
|
||||
|
||||
// 判断是否为新申请
|
||||
isNewApplication() {
|
||||
return !this.taskId && !this.instanceId && this.defineKey
|
||||
},
|
||||
|
||||
// 判断是否为已有流程实例
|
||||
isExistingProcess() {
|
||||
return this.taskId || this.instanceId
|
||||
},
|
||||
|
||||
// 判断任务是否进行中
|
||||
isTaskInProgress() {
|
||||
return this.currentTask && this.currentTask.taskState === this.taskStateEnum.DOING
|
||||
},
|
||||
|
||||
// 判断是否为第一个任务节点
|
||||
isFirstTaskNode() {
|
||||
return this.currentTask && this.currentTask.ext && this.currentTask.ext.isFirstTaskNode
|
||||
},
|
||||
|
||||
// 流程图预览URL
|
||||
processDesignUrl() {
|
||||
if (this.isNewApplication && this.processDefinition.id) {
|
||||
return `/flow/define/preview?id=${this.processDefinition.id}`
|
||||
} else if (this.isExistingProcess && this.processInstance.processId) {
|
||||
return `/flow/define/preview?id=${this.processInstance.processId}`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initComponent()
|
||||
// 监听
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data.type === "task-complete") {
|
||||
this.initComponent()
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 初始化组件
|
||||
async initComponent() {
|
||||
this.loading = true
|
||||
|
||||
debugger
|
||||
|
||||
if (this.isNewApplication) {
|
||||
// 新申请:加载流程定义信息
|
||||
await this.loadProcessDefinition()
|
||||
// 加载流程申请表单
|
||||
await this.loadApplicationForm()
|
||||
} else if (this.isExistingProcess) {
|
||||
// 已有流程:加载流程实例信息
|
||||
await this.loadProcessInstance()
|
||||
// 加载任务信息
|
||||
await this.loadCurrentTask()
|
||||
// 如果有businessId且不是第一个任务节点,加载历史办理过程
|
||||
if (this.businessId) {
|
||||
await this.loadHistoryProcess()
|
||||
}
|
||||
} else {
|
||||
console.warn("URL中缺少必要的参数(defineKey、taskId或instanceId),无法初始化组件")
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
// 加载流程定义信息
|
||||
async loadProcessDefinition() {
|
||||
if (!this.defineKey) return
|
||||
|
||||
try {
|
||||
const response = await $.get("/flow/common/defineInfo", { defineKey: this.defineKey })
|
||||
if (response.code === 0) {
|
||||
this.processDefinition = response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载流程定义信息失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 加载申请表单
|
||||
async loadApplicationForm() {
|
||||
debugger
|
||||
if (!this.defineKey || !this.processDefinition.instanceUrl) return
|
||||
|
||||
this.pjaxLoading.apply = true
|
||||
|
||||
$.pjax({
|
||||
url: this.processDefinition.instanceUrl,
|
||||
container: "#application-form-container",
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
})
|
||||
.done(() => {
|
||||
this.pjaxLoading.apply = false
|
||||
})
|
||||
.fail(() => {
|
||||
this.pjaxLoading.apply = false
|
||||
console.log(this.processDefinition.instanceUrl, "申请表单加载失败")
|
||||
})
|
||||
},
|
||||
|
||||
// 加载流程实例信息
|
||||
async loadProcessInstance() {
|
||||
if (!this.instanceId) return
|
||||
const { code, data, msg } = await $.get("/flow/common/instanceInfo", { instanceId: this.instanceId })
|
||||
if (code === 0) {
|
||||
this.processInstance = data.processInstance
|
||||
this.todoTasks = data.todoTasks
|
||||
}
|
||||
},
|
||||
|
||||
// 加载任务信息
|
||||
async loadCurrentTask() {
|
||||
if (!this.taskId) return
|
||||
$.get("/flow/common/taskInfo", { taskId: this.taskId }, (response) => {
|
||||
if (response.code === 0) {
|
||||
this.currentTask = response.data
|
||||
this.loadTaskForm()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载任务表单
|
||||
async loadTaskForm() {
|
||||
if (!this.taskId || !this.currentTask?.taskModel?.form) return
|
||||
|
||||
if (!this.todoTasks.map((v) => v.id).includes(this.taskId)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.pjaxLoading.taskForm = true
|
||||
|
||||
$.pjax({
|
||||
url: this.currentTask.taskModel.form,
|
||||
container: "#task-form-container",
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
})
|
||||
.done(() => {
|
||||
this.pjaxLoading.taskForm = false
|
||||
})
|
||||
.fail(() => {
|
||||
this.pjaxLoading.taskForm = false
|
||||
console.log(this.currentTask.taskModel.form, "任务表单加载失败")
|
||||
})
|
||||
},
|
||||
|
||||
// 加载历史办理过程
|
||||
async loadHistoryProcess() {
|
||||
if (!this.businessId) return
|
||||
|
||||
this.pjaxLoading.historyProcess = true
|
||||
|
||||
$.pjax({
|
||||
url: `/platform/article/common/fullForm?businessId=${this.businessId}`,
|
||||
container: "#history-process-container",
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
})
|
||||
.done(() => {
|
||||
this.pjaxLoading.historyProcess = false
|
||||
})
|
||||
.fail(() => {
|
||||
this.pjaxLoading.historyProcess = false
|
||||
console.log("历史办理过程加载失败")
|
||||
})
|
||||
},
|
||||
|
||||
// 显示流程图
|
||||
showProcessDiagram() {
|
||||
if (this.processDesignUrl) {
|
||||
this.designUrl = this.processDesignUrl
|
||||
this.designVisible = true
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(state) {
|
||||
return this.processStatusMap[state]?.text || "未知状态"
|
||||
},
|
||||
|
||||
// 获取状态样式类
|
||||
getStatusClass(state) {
|
||||
return `status-${this.processStatusMap[state]?.class || "unknown"}`
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return "-"
|
||||
return new Date(dateStr).toLocaleString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 简洁OA风格样式 */
|
||||
.flow-container {
|
||||
background-color: #f5f6fa;
|
||||
min-height: 100vh;
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 页面头部 - 固定在顶部 */
|
||||
.page-header {
|
||||
background: white;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
padding: 16px 0;
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 主要内容区域 - 添加顶部间距 */
|
||||
.main-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
margin-top: 80px; /* 为固定头部留出空间 */
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-section .page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
|
||||
.page-breadcrumb {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
.status-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #fdf6ec;
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.status-doing {
|
||||
background: #ecf5ff;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.status-finished {
|
||||
background: #f0f9ff;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.status-withdraw {
|
||||
background: #f4f4f5;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.status-interrupt {
|
||||
background: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.status-reject {
|
||||
background: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.content-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.content-right {
|
||||
width: 320px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 面板样式 */
|
||||
.info-panel,
|
||||
.form-panel,
|
||||
.history-panel,
|
||||
.approval-panel {
|
||||
background: white;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panel-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.panel-content.no-padding {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 信息表格 */
|
||||
.info-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
min-width: 80px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 表单内容 */
|
||||
.form-content {
|
||||
min-height: 200px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #909399;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.loading-state i {
|
||||
font-size: 20px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.loading-state span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 流程图查看器 */
|
||||
.diagram-viewer {
|
||||
height: 65vh;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.diagram-viewer iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1024px) {
|
||||
.main-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-right {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user