first commit
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "snakerChart",
|
||||
data() {
|
||||
return {
|
||||
designVisible: false,
|
||||
designUrl: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpenFull(processDefineId,instanceId) {
|
||||
this.designVisible = true
|
||||
this.designUrl = `/flow/define/preview?defineId=${processDefineId}&instanceId=${instanceId}`
|
||||
},
|
||||
onOpen(processDefineKey){
|
||||
this.designVisible = true
|
||||
this.designUrl = `/flow/define/preview?defineKey=${processDefineKey}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog title="流程图预览" :visible.sync="designVisible" fullscreen width="80%" top="50px" append-to-body>
|
||||
<div class="diagram-viewer">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.diagram-viewer {
|
||||
/*height: 65vh;*/
|
||||
height: calc(100vh - 60px - 55px);
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.diagram-viewer iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,775 @@
|
||||
<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>{{ 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="isNewApplication">
|
||||
<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 }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">流程说明:</div>
|
||||
<div class="info-value">
|
||||
<div v-html="processDefinition.description"></div>
|
||||
</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>
|
||||
<div class="info-row">
|
||||
<div class="info-label">流程进度:</div>
|
||||
<div class="info-value">
|
||||
<span @click="showProcessDiagram" style="color: var(--color-primary); cursor: pointer">查看流程图</span>
|
||||
</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 type="text/javascript">
|
||||
class PjaxSync {
|
||||
constructor() {
|
||||
this.isLoading = false
|
||||
this.queue = []
|
||||
}
|
||||
|
||||
async request(url, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestData = { url, options, resolve, reject }
|
||||
|
||||
if (this.isLoading) {
|
||||
this.queue.push(requestData)
|
||||
return
|
||||
}
|
||||
|
||||
this._executeRequest(requestData)
|
||||
})
|
||||
}
|
||||
|
||||
_executeRequest({ url, options, resolve, reject }) {
|
||||
this.isLoading = true
|
||||
|
||||
const defaultOptions = {
|
||||
timeout: 10000,
|
||||
push: false,
|
||||
replace: false,
|
||||
...options
|
||||
}
|
||||
|
||||
const successHandler = (event, data, status, xhr) => {
|
||||
this._cleanup()
|
||||
resolve({ data, status, xhr })
|
||||
this._processQueue()
|
||||
}
|
||||
|
||||
const errorHandler = (event, xhr, textStatus, errorThrown) => {
|
||||
this._cleanup()
|
||||
reject(new Error(textStatus || "PJAX request failed"))
|
||||
this._processQueue()
|
||||
}
|
||||
|
||||
$(document).one("pjax:success", successHandler)
|
||||
$(document).one("pjax:error", errorHandler)
|
||||
|
||||
$.pjax({
|
||||
url: url,
|
||||
...defaultOptions
|
||||
})
|
||||
}
|
||||
|
||||
_cleanup() {
|
||||
this.isLoading = false
|
||||
$(document).off("pjax:success pjax:error")
|
||||
}
|
||||
|
||||
_processQueue() {
|
||||
if (this.queue.length > 0) {
|
||||
const nextRequest = this.queue.shift()
|
||||
this._executeRequest(nextRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建全局实例
|
||||
const pjaxSync = new PjaxSync()
|
||||
|
||||
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() {
|
||||
return this.instanceId !== null
|
||||
},
|
||||
|
||||
// 是否显示任务表单
|
||||
shouldShowTaskForm() {
|
||||
return this.taskId != null
|
||||
},
|
||||
|
||||
// 判断是否为新申请
|
||||
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() {
|
||||
debugger
|
||||
if (this.isNewApplication && this.processDefinition.id) {
|
||||
return `/flow/define/preview?defineId=${this.processDefinition.id}`
|
||||
} else if (this.isExistingProcess && this.processInstance.processDefineId) {
|
||||
return `/flow/define/preview?defineId=${this.processInstance.processDefineId}&instanceId=${this.instanceId}`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initComponent()
|
||||
// 监听消息
|
||||
new BroadcastChannel("zhgh-global-channel").addEventListener("message", (event) => {
|
||||
if (event.data.type === "task-complete") {
|
||||
this.initComponent()
|
||||
}
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
console.log("beforeDestroy")
|
||||
},
|
||||
methods: {
|
||||
// 初始化组件
|
||||
async initComponent() {
|
||||
this.loading = true
|
||||
if (this.isNewApplication) {
|
||||
// 新申请:加载流程定义信息
|
||||
await this.loadProcessDefinition()
|
||||
// 加载流程申请表单
|
||||
await this.loadApplicationForm()
|
||||
} else if (this.isExistingProcess) {
|
||||
// 已有流程:加载流程实例信息
|
||||
await this.loadProcessInstance()
|
||||
if (this.processInstance && this.processInstance.state === 30) {
|
||||
// 流程被撤回了 此时加载申请表单
|
||||
// 加载流程申请表单
|
||||
await this.loadApplicationForm()
|
||||
}
|
||||
|
||||
// 加载任务信息
|
||||
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,
|
||||
instanceId: this.instanceId
|
||||
})
|
||||
if (response.code === 0) {
|
||||
this.processDefinition = response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载流程定义信息失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 加载申请表单
|
||||
async loadApplicationForm() {
|
||||
if (!this.defineKey || !this.processDefinition.instanceUrl) return
|
||||
|
||||
this.pjaxLoading.apply = true
|
||||
|
||||
await pjaxSync.request(this.processDefinition.instanceUrl, {
|
||||
container: "#application-form-container"
|
||||
})
|
||||
|
||||
this.pjaxLoading.apply = false
|
||||
},
|
||||
|
||||
// 加载流程实例信息
|
||||
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.processDefinition = data.processInstance?.jsonObject
|
||||
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
|
||||
|
||||
await pjaxSync.request(this.currentTask.taskModel.form, {
|
||||
container: "#task-form-container"
|
||||
})
|
||||
},
|
||||
|
||||
// 加载历史办理过程
|
||||
async loadHistoryProcess() {
|
||||
if (!this.businessId) return
|
||||
const { instanceViewUrl } = this.processInstance.jsonObject
|
||||
if (instanceViewUrl) {
|
||||
this.pjaxLoading.historyProcess = true
|
||||
|
||||
await pjaxSync.request(`${instanceViewUrl}?businessId=${this.businessId}&instanceId=${this.instanceId}`, {
|
||||
container: "#history-process-container"
|
||||
})
|
||||
|
||||
this.pjaxLoading.historyProcess = false
|
||||
}
|
||||
},
|
||||
|
||||
// 显示流程图
|
||||
showProcessDiagram() {
|
||||
debugger
|
||||
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: 19000;
|
||||
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>
|
||||
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<div class="approval-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h4>审批记录</h4>
|
||||
</div>
|
||||
<div class="timeline-container" v-if="historyApprovalRecords.length > 0">
|
||||
<div class="timeline-wrapper">
|
||||
<div
|
||||
v-for="(record, index) in historyApprovalRecords"
|
||||
:key="record.id"
|
||||
class="timeline-item">
|
||||
|
||||
<!-- 时间线节点 -->
|
||||
<div class="timeline-node">
|
||||
<div class="timeline-dot" :class="getDotClass(record.taskState)"></div>
|
||||
<div class="timeline-line" v-if="index < historyApprovalRecords.length - 1"></div>
|
||||
</div>
|
||||
|
||||
<!-- 审批内容 -->
|
||||
<div class="timeline-content">
|
||||
<div class="approval-card">
|
||||
<div class="card-header">
|
||||
<span class="task-name">{{ record.displayName }}</span>
|
||||
<span class="approval-time">{{ formatTimestamp(record.finishTime || record.createdAt) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="info-item" v-if="record.operator && record.operator !== 'flow.auto'">
|
||||
<span class="label">办理人:</span>
|
||||
<span class="value">{{ getOperatorName(record) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-item" v-if="record.ext && record.ext.submitType">
|
||||
<span class="label">操作:</span>
|
||||
<span class="value submit-type" :class="'submit-' + record.ext.submitType">
|
||||
{{ getSubmitTypeText(record.ext.submitType) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="info-item" v-if="record.ext && record.ext.opinion">
|
||||
<span class="label">意见:</span>
|
||||
<span class="value">{{ record.ext.opinion }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-records">
|
||||
暂无审批记录
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "SnakerFlowHisApproval",
|
||||
props: {
|
||||
task_id: {
|
||||
type: String | Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 历史审批记录
|
||||
historyApprovalRecords: [],
|
||||
|
||||
// 加载状态
|
||||
loading: false,
|
||||
|
||||
// 任务状态枚举
|
||||
taskStateEnum: {
|
||||
DOING: 10,
|
||||
FINISHED: 20,
|
||||
WITHDRAW: 30,
|
||||
INTERRUPT: 40,
|
||||
PENDING: 50,
|
||||
ABANDON: 99
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 获取实例ID(从task_id获取流程实例信息)
|
||||
instanceId() {
|
||||
return GetQueryString("instanceId")
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadHistoryApprovalRecords()
|
||||
},
|
||||
watch: {
|
||||
task_id: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.loadHistoryApprovalRecords()
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载历史审批记录
|
||||
async loadHistoryApprovalRecords() {
|
||||
if (!this.instanceId && !this.task_id) return
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const params = this.instanceId ?
|
||||
{ instanceId: this.instanceId } :
|
||||
{ taskId: this.task_id }
|
||||
|
||||
const response = await $.post("/flow/common/approvalRecord", params)
|
||||
if (response.code === 0) {
|
||||
this.historyApprovalRecords = response.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载历史审批记录失败:", error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化时间戳
|
||||
formatTimestamp(timestamp) {
|
||||
if (!timestamp) return ""
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleString("zh-CN", {
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
})
|
||||
},
|
||||
|
||||
// 获取操作人姓名
|
||||
getOperatorName(record) {
|
||||
if (!record.operator || record.operator === 'flow.auto') {
|
||||
return "系统自动"
|
||||
}
|
||||
if (record.ext && record.ext.operatorName) {
|
||||
return record.ext.operatorName
|
||||
}
|
||||
return record.operator
|
||||
},
|
||||
|
||||
// 获取提交类型文本
|
||||
getSubmitTypeText(submitType) {
|
||||
const typeMap = {
|
||||
'agree': '同意',
|
||||
'reject': '拒绝',
|
||||
'back': '退回',
|
||||
'transfer': '转办',
|
||||
'delegate': '委托',
|
||||
'submit': '提交'
|
||||
}
|
||||
return typeMap[submitType] || submitType
|
||||
},
|
||||
|
||||
// 获取节点样式类
|
||||
getDotClass(state) {
|
||||
switch (state) {
|
||||
case this.taskStateEnum.FINISHED:
|
||||
return "dot-success"
|
||||
case this.taskStateEnum.WITHDRAW:
|
||||
return "dot-warning"
|
||||
case this.taskStateEnum.INTERRUPT:
|
||||
case this.taskStateEnum.ABANDON:
|
||||
return "dot-danger"
|
||||
case this.taskStateEnum.PENDING:
|
||||
return "dot-info"
|
||||
case this.taskStateEnum.DOING:
|
||||
return "dot-primary"
|
||||
default:
|
||||
return "dot-default"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.approval-sidebar {
|
||||
width: 320px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
height: fit-content;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
background: #f5f5f5;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 12px 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-header h4 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.timeline-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.timeline-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timeline-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.timeline-node {
|
||||
position: relative;
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #fff;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.dot-success {
|
||||
background: #52c41a;
|
||||
}
|
||||
|
||||
.dot-warning {
|
||||
background: #faad14;
|
||||
}
|
||||
|
||||
.dot-danger {
|
||||
background: #f5222d;
|
||||
}
|
||||
|
||||
.dot-primary {
|
||||
background: #1890ff;
|
||||
}
|
||||
|
||||
.dot-info {
|
||||
background: #999;
|
||||
}
|
||||
|
||||
.dot-default {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
|
||||
.timeline-line {
|
||||
width: 1px;
|
||||
background: #e8e8e8;
|
||||
flex: 1;
|
||||
margin-top: 4px;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.approval-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.approval-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
min-width: 50px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.submit-type {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.submit-agree {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.submit-reject {
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.submit-back {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.submit-transfer, .submit-delegate, .submit-submit {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.no-records {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.timeline-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.timeline-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.timeline-container::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
}
|
||||
|
||||
.timeline-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<div class="snaker-task-action" v-if="!loading || taskInfo">
|
||||
<!-- 指定下一节点处理人 -->
|
||||
<div class="next-handler-section" v-if="supportSpecifyNextHandler && candidates.length > 0">
|
||||
<el-checkbox v-model="specifyNextHandler" @change="onSpecifyNextHandlerChange">指定下一节点处理人</el-checkbox>
|
||||
<div class="handler-select-wrapper" v-if="specifyNextHandler">
|
||||
<el-select v-model="selectedHandler" placeholder="请选择处理人" clearable filterable class="handler-select">
|
||||
<el-option v-for="candidate in candidates" :key="candidate.userId" :label="candidate.userName" :value="candidate.userId">
|
||||
<span style="float: left">{{ candidate.userName }}({{ candidate.ext?.loginName }})</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ candidate.ext?.unitName }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第一个任务节点或新申请:显示提交、保存草稿、取消 -->
|
||||
<!-- <template v-if="isFirstTaskNodeOrNew">-->
|
||||
<!-- <el-button type="primary" @click="handleSubmit" :loading="actionLoading" icon="el-icon-upload2">提交</el-button>-->
|
||||
<!-- <!– 保存草稿只在流程未发起时显示 –>-->
|
||||
<!-- <el-button @click="handleSaveDraft" v-if="!actionLoading && show_save_draft && !instanceId" icon="el-icon-document">保存草稿</el-button>-->
|
||||
<!-- <el-button @click="handleCancel" :loading="actionLoading" v-if="show_cancel" icon="el-icon-close">取消</el-button>-->
|
||||
<!-- </template>-->
|
||||
|
||||
<!-- <!– 其他任务节点:显示审批按钮 –>-->
|
||||
<!-- <template v-else>-->
|
||||
<!-- <!– 会签任务按钮 –>-->
|
||||
<!-- <template v-if="is_countersign_task">-->
|
||||
<!-- <el-button type="primary" @click="handleAction('AGREE')" :loading="actionLoading" icon="el-icon-check">同意</el-button>-->
|
||||
<!-- <el-button type="danger" plain @click="handleAction('COUNTERSIGN_DISAGREE')" :loading="actionLoading" icon="el-icon-close">-->
|
||||
<!-- 不同意-->
|
||||
<!-- </el-button>-->
|
||||
<!-- </template>-->
|
||||
|
||||
<!-- <!– 普通任务按钮 –>-->
|
||||
<!-- <template v-else>-->
|
||||
<!-- <el-button type="primary" @click="handleAction('AGREE')" :loading="actionLoading" icon="el-icon-check">同意</el-button>-->
|
||||
<!-- <el-button type="danger" plain @click="handleAction('REJECT')" :loading="actionLoading" icon="el-icon-close">拒绝</el-button>-->
|
||||
<!-- <el-button @click="handleAction('ROLLBACK')" :loading="actionLoading" icon="el-icon-back">退回上一步</el-button>-->
|
||||
<!-- <el-button @click="handleAction('ROLLBACK_TO_OPERATOR')" :loading="actionLoading" icon="el-icon-d-arrow-left">退回发起人</el-button>-->
|
||||
<!-- <el-button @click="handleAction('JUMP')" :loading="actionLoading" icon="el-icon-position">跳转</el-button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </template>-->
|
||||
|
||||
<!--没有流程实例的时候-->
|
||||
<template v-if="!instanceId">
|
||||
<el-button type="primary" @click="handleSubmit">提交</el-button>
|
||||
<el-button @click="handleSaveDraft">保存</el-button>
|
||||
</template>
|
||||
|
||||
<el-row type="flex" justify="end">
|
||||
<el-button v-for="item in buttons" @click="handleAction(item.value)" :type="item.theme" :icon="item.icon" size="medium" :key="item.value">
|
||||
{{ item.replacementText || item.originalText }}
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-else class="snaker-task-action loading-state">
|
||||
<el-button loading icon="el-icon-loading">加载中...</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "SnakerFlowTaskFormAction",
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
// 加载状态
|
||||
loading: false,
|
||||
// 操作按钮加载状态
|
||||
actionLoading: false,
|
||||
// 任务信息
|
||||
taskInfo: null,
|
||||
// 任务操作枚举
|
||||
actionEnum: {
|
||||
APPLY: "0", // 发起申请
|
||||
AGREE: "1", // 同意
|
||||
REJECT: "2", // 拒绝
|
||||
COUNTERSIGN_DISAGREE: "20", // 会签不同意 拒绝申请
|
||||
ROLLBACK: "3", // 退回上一步
|
||||
ROLLBACK_TO_OPERATOR: "6", // 退回发起人
|
||||
JUMP: "4", // 跳转
|
||||
RE_APPLY: "5", // 重新提交
|
||||
SUBMIT: "0" // 发起申请
|
||||
},
|
||||
// 任务类型枚举
|
||||
performTypeEnum: {
|
||||
NORMAL: 0, // 普通任务
|
||||
COUNTERSIGN: 1 // 会签任务
|
||||
},
|
||||
// 指定下一节点处理人相关
|
||||
specifyNextHandler: false, // 是否指定下一节点处理人
|
||||
selectedHandler: null, // 选中的处理人ID
|
||||
candidates: [], // 候选人列表
|
||||
supportCandidate: false,
|
||||
|
||||
taskId: GetQueryString("taskId"),
|
||||
instanceId: GetQueryString("instanceId"),
|
||||
defineKey: GetQueryString("defineKey"),
|
||||
defineId: GetQueryString("defineId"),
|
||||
businessId: GetQueryString("businessId")
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否为会签任务
|
||||
is_countersign_task() {
|
||||
return this.taskInfo && this.taskInfo.performType === this.performTypeEnum.COUNTERSIGN
|
||||
},
|
||||
|
||||
// 是否为第一个任务节点或新申请
|
||||
isFirstTaskNodeOrNew() {
|
||||
// 没有任务ID,说明是新申请
|
||||
if (!this.taskId) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 有任务ID但是任务信息中标识为第一个节点
|
||||
return this.taskInfo && this.taskInfo.ext && this.taskInfo.ext.isFirstTaskNode
|
||||
},
|
||||
|
||||
// 审批按钮配置
|
||||
buttons() {
|
||||
if (this.taskInfo) {
|
||||
return this.taskInfo.taskModel.ext.buttonConfig.filter((v) => v.visible)
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
||||
// 是否支持选择下一步处理人
|
||||
supportSpecifyNextHandler() {
|
||||
return this.taskInfo && this.taskInfo.taskModel.ext.enableNextOperator
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 只有当有任务ID时才加载任务信息
|
||||
if (this.taskId) {
|
||||
this.loadTaskInfo()
|
||||
this.loadCandidates()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载任务信息
|
||||
loadTaskInfo() {
|
||||
if (!this.taskId) return
|
||||
|
||||
this.loading = true
|
||||
this.taskInfo = null
|
||||
|
||||
$.get("/flow/common/taskInfo", {
|
||||
taskId: this.taskId
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.taskInfo = res.data
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 处理提交操作
|
||||
handleSubmit() {
|
||||
// 根据是否有流程实例来确定操作类型
|
||||
const submitAction = this.instanceId ? this.actionEnum.RE_APPLY : this.actionEnum.APPLY
|
||||
|
||||
// 触发父组件事件,传递提交操作
|
||||
this.$emit("task-action", {
|
||||
submitType: submitAction,
|
||||
taskId: this.taskId,
|
||||
instanceId: this.instanceId,
|
||||
defineId: this.defineId,
|
||||
defineKey: this.defineKey,
|
||||
businessId: this.businessId
|
||||
})
|
||||
},
|
||||
|
||||
// 处理任务操作
|
||||
handleAction(actionKey) {
|
||||
if (this.actionLoading || !this.taskInfo) return
|
||||
|
||||
// 构建操作数据
|
||||
const actionData = {
|
||||
submitType: this.actionEnum[actionKey],
|
||||
taskId: this.taskId,
|
||||
processTaskId: this.taskId,
|
||||
instanceId: this.instanceId,
|
||||
defineId: this.defineId,
|
||||
defineKey: this.defineKey,
|
||||
businessId: this.businessId
|
||||
}
|
||||
|
||||
// 如果指定了下一节点处理人,添加到操作数据中
|
||||
if (this.specifyNextHandler && this.selectedHandler) {
|
||||
actionData.tf_nextNodeOperator = this.selectedHandler
|
||||
}
|
||||
|
||||
// 触发父组件事件,传递操作类型和任务信息
|
||||
this.$emit("task-action", actionData)
|
||||
},
|
||||
|
||||
// 保存草稿
|
||||
handleSaveDraft() {
|
||||
if (this.actionLoading) return
|
||||
|
||||
this.$emit("save-draft", {
|
||||
submitType: this.actionEnum.APPLY,
|
||||
defineKey: this.defineKey,
|
||||
defineId: this.defineId
|
||||
})
|
||||
},
|
||||
|
||||
// 取消操作
|
||||
handleCancel() {
|
||||
const func = () => window.close()
|
||||
this.$emit("cancel", func)
|
||||
if (!this.$listeners.cancel) {
|
||||
func()
|
||||
}
|
||||
},
|
||||
|
||||
// 刷新任务信息
|
||||
refresh() {
|
||||
if (this.taskId) {
|
||||
this.loadTaskInfo()
|
||||
}
|
||||
},
|
||||
|
||||
// 处理指定下一节点处理人勾选框变化
|
||||
onSpecifyNextHandlerChange(checked) {
|
||||
if (!checked) {
|
||||
this.selectedHandler = null
|
||||
}
|
||||
},
|
||||
|
||||
// 加载候选人列表
|
||||
loadCandidates() {
|
||||
$.get("/flow/common/candidate", {
|
||||
taskId: this.taskId
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.candidates = res.data.candidates
|
||||
this.supportCandidate = res.data.support
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.snaker-task-action {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #eee;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.snaker-task-action .el-button {
|
||||
margin: 0 8px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
/* 指定下一节点处理人样式 */
|
||||
.next-handler-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.handler-select-wrapper {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.handler-select {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.snaker-task-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.snaker-task-action .el-button {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.next-handler-section {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.handler-select {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "snakerStart",
|
||||
props: {
|
||||
define_key:{
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
openChart() {
|
||||
this.$refs.snakerChartRef.onOpen(this.define_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-row type="flex" justify="space-between" slot="header">
|
||||
<h3 style="color: var(--color-primary)">{{ label }}</h3>
|
||||
<div class="header-right">
|
||||
<slot name="header-right-label"></slot>
|
||||
<el-link type="primary" @click="openChart">流程图</el-link>
|
||||
</div>
|
||||
</el-row>
|
||||
<snaker-chart ref="snakerChartRef"></snaker-chart>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user