first commit
This commit is contained in:
@@ -0,0 +1,648 @@
|
||||
<script>
|
||||
module.exports = {
|
||||
name: "formButton",
|
||||
props: {
|
||||
task_id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
// 是否显示审核意见框
|
||||
show_comment: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自定义按钮配置
|
||||
custom_buttons: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 是否显示流程图
|
||||
show_process_diagram: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示办理记录
|
||||
show_process_records: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 办理记录展示模式:timeline, table
|
||||
record_display_mode: {
|
||||
type: String,
|
||||
default: "timeline",
|
||||
validator: (value) => {
|
||||
return ["timeline", "table"].includes(value)
|
||||
}
|
||||
},
|
||||
// 业务ID,用于获取流程记录
|
||||
business_id: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
comment: "", // 审核意见
|
||||
nextNodeInfo: null, // 下一节点信息
|
||||
currentTaskName: "部门审核", // 当前办理节点名称
|
||||
availableActions: [], // 可用操作
|
||||
sequenceFlows: [], // 目标节点
|
||||
selectedAction: "", // 选中的操作类型
|
||||
rejectTaskOptions: [], // 驳回到指定节点的选项
|
||||
selectedRejectTask: "", // 选中的驳回节点
|
||||
assigneeOptions: [], // 执行人选项
|
||||
selectedAssignee: "", // 选中的执行人
|
||||
|
||||
// 控制显示
|
||||
showProcessDiagram: true, // 是否显示流程图
|
||||
showProcessRecords: true, // 是否显示办理记录
|
||||
recordDisplayMode: 'timeline', // 办理记录展示模式:timeline, table
|
||||
|
||||
// 模拟数据 - 办理记录
|
||||
mockProcessRecords: [
|
||||
{
|
||||
nodeName: "发起申请",
|
||||
userName: "张三",
|
||||
time: "2023-05-10 09:30:00",
|
||||
action: "提交申请",
|
||||
comment: "请审批",
|
||||
type: "primary"
|
||||
},
|
||||
{
|
||||
nodeName: "部门审核",
|
||||
userName: "李四",
|
||||
time: "2023-05-10 11:20:00",
|
||||
action: "处理中",
|
||||
comment: "",
|
||||
type: "warning"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.task_id) {
|
||||
this.getNextNodeInfo()
|
||||
}
|
||||
|
||||
// 初始化显示状态
|
||||
this.showProcessDiagram = this.show_process_diagram
|
||||
this.showProcessRecords = this.show_process_records
|
||||
this.recordDisplayMode = this.record_display_mode
|
||||
|
||||
// 模拟获取流程记录
|
||||
this.mockGetProcessRecords()
|
||||
},
|
||||
|
||||
watch: {
|
||||
// 监听props变化
|
||||
show_process_diagram(val) {
|
||||
this.showProcessDiagram = val
|
||||
},
|
||||
show_process_records(val) {
|
||||
this.showProcessRecords = val
|
||||
},
|
||||
record_display_mode(val) {
|
||||
this.recordDisplayMode = val
|
||||
},
|
||||
business_id: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
// 清空现有记录
|
||||
this.mockProcessRecords = []
|
||||
// 重新获取流程记录
|
||||
this.mockGetProcessRecords()
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 获取下一节点信息
|
||||
getNextNodeInfo() {
|
||||
this.loading = true
|
||||
this.$axios
|
||||
.post("/easy-flowable/task/nextNodeVariables/" + this.task_id)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.nextNodeInfo = res.result
|
||||
// 获取当前办理节点名称
|
||||
if (res.result.task && res.result.task.name) {
|
||||
this.currentTaskName = res.result.task.name
|
||||
}
|
||||
// 处理可用操作
|
||||
if (res.result.attributes && res.result.attributes.actions) {
|
||||
this.availableActions = res.result.attributes.actions
|
||||
}
|
||||
// 处理目标节点
|
||||
if (res.result.sequenceFlow) {
|
||||
this.sequenceFlows = res.result.sequenceFlow
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.msg || "获取下一节点信息失败")
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("获取下一节点信息失败", error)
|
||||
this.$message.error("获取下一节点信息失败")
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 获取可驳回的节点列表
|
||||
getRejectTaskList() {
|
||||
this.loading = true
|
||||
this.$axios
|
||||
.get("/easy-flowable/task/flowBackNodes/" + this.task_id)
|
||||
.then((res) => {
|
||||
if (res.code === 0 && res.result) {
|
||||
this.rejectTaskOptions = res.result.map((item) => ({
|
||||
label: item.nodeName,
|
||||
value: item.nodeId
|
||||
}))
|
||||
if (this.rejectTaskOptions.length > 0) {
|
||||
this.selectedRejectTask = this.rejectTaskOptions[0].value
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.msg || "获取可驳回节点失败")
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("获取可驳回节点失败", error)
|
||||
this.$message.error("获取可驳回节点失败")
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 处理按钮点击
|
||||
handleAction(action) {
|
||||
this.selectedAction = action
|
||||
|
||||
// 如果是驳回到指定节点,需要获取可驳回的节点列表
|
||||
if (action === "REJECT_TO_TASK") {
|
||||
this.getRejectTaskList()
|
||||
return
|
||||
}
|
||||
|
||||
// 执行操作
|
||||
this.submitAction()
|
||||
},
|
||||
// 提交操作
|
||||
submitAction() {
|
||||
if (this.show_comment && !this.comment && ["AGREE", "REJECT", "REJECT_TO_TASK", "REBUT"].includes(this.selectedAction)) {
|
||||
this.$message.warning("请填写审核意见")
|
||||
return
|
||||
}
|
||||
|
||||
const params = {
|
||||
taskId: this.task_id,
|
||||
comment: this.comment,
|
||||
type: this.getActionCode(this.selectedAction)
|
||||
}
|
||||
|
||||
// 添加目标节点变量(自动选择第一个)
|
||||
if (this.sequenceFlows.length > 0 && ["AGREE"].includes(this.selectedAction)) {
|
||||
params.variables = {
|
||||
sequenceFlow: this.sequenceFlows[0].value
|
||||
}
|
||||
}
|
||||
|
||||
// 添加驳回节点信息
|
||||
if (this.selectedAction === "REJECT_TO_TASK" && this.selectedRejectTask) {
|
||||
params.targetTaskDefinitionKey = this.selectedRejectTask
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
this.$axios
|
||||
.post("/easy-flowable/task/complete", params)
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("操作成功")
|
||||
this.$emit("success", {
|
||||
action: this.selectedAction,
|
||||
result: res.result
|
||||
})
|
||||
} else {
|
||||
this.$message.error(res.msg || "操作失败")
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("操作失败", error)
|
||||
this.$message.error("操作失败")
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 获取操作类型代码
|
||||
getActionCode(action) {
|
||||
const actionMap = {
|
||||
START: "0",
|
||||
AGREE: "1",
|
||||
REBUT: "2",
|
||||
REVOCATION: "3",
|
||||
REJECT: "4",
|
||||
REJECT_TO_TASK: "5",
|
||||
DELEGATE: "6",
|
||||
ASSIGN: "7",
|
||||
STOP: "8",
|
||||
BEFORE_SIGN: "9",
|
||||
AFTER_SIGN: "10",
|
||||
CANCELLATION: "11",
|
||||
ADD_COMMENT: "12",
|
||||
DEL_COMMENT: "13",
|
||||
RESUBMIT: "14"
|
||||
}
|
||||
return actionMap[action] || "1"
|
||||
},
|
||||
// 获取操作按钮文本
|
||||
getActionText(action) {
|
||||
const actionTextMap = {
|
||||
START: "启动流程",
|
||||
AGREE: "同意",
|
||||
REBUT: "拒绝",
|
||||
REVOCATION: "撤回",
|
||||
REJECT: "驳回",
|
||||
REJECT_TO_TASK: "驳回到指定节点",
|
||||
DELEGATE: "委派",
|
||||
ASSIGN: "转办",
|
||||
STOP: "终止",
|
||||
BEFORE_SIGN: "前加签",
|
||||
AFTER_SIGN: "后加签",
|
||||
CANCELLATION: "作废",
|
||||
ADD_COMMENT: "添加评论",
|
||||
DEL_COMMENT: "删除评论",
|
||||
RESUBMIT: "重新提交"
|
||||
}
|
||||
return actionTextMap[action] || action
|
||||
},
|
||||
// 获取操作按钮类型
|
||||
getActionType(action) {
|
||||
const typeMap = {
|
||||
AGREE: "primary",
|
||||
REBUT: "danger",
|
||||
REJECT: "warning",
|
||||
REJECT_TO_TASK: "warning"
|
||||
}
|
||||
return typeMap[action] || "default"
|
||||
},
|
||||
|
||||
// 模拟获取流程记录
|
||||
mockGetProcessRecords() {
|
||||
// 这里可以根据实际需求调整模拟数据
|
||||
// 实际项目中,可以通过API获取真实数据
|
||||
// 例如:this.$axios.post("/platform/wf/processCommon/approvalRecord", { businessNo: this.business_id })
|
||||
|
||||
// 模拟异步获取数据
|
||||
setTimeout(() => {
|
||||
// 如果已经有模拟数据,则不再添加
|
||||
if (this.mockProcessRecords.length <= 2) {
|
||||
// 添加更多模拟数据
|
||||
this.mockProcessRecords.push(
|
||||
{
|
||||
nodeName: "主管审核",
|
||||
userName: "王五",
|
||||
time: "2023-05-09 14:30:00",
|
||||
action: "同意",
|
||||
comment: "同意申请,请总经理审批",
|
||||
type: "success"
|
||||
},
|
||||
{
|
||||
nodeName: "发起申请",
|
||||
userName: "张三",
|
||||
time: "2023-05-09 10:15:00",
|
||||
action: "提交申请",
|
||||
comment: "请审批我的申请",
|
||||
type: "info"
|
||||
}
|
||||
)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="form-button-container">
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 表单信息插槽 -->
|
||||
<div class="form-info-container">
|
||||
<slot name="form-info"></slot>
|
||||
</div>
|
||||
|
||||
<!-- 流程图展示 -->
|
||||
<div class="process-diagram-container" v-if="showProcessDiagram">
|
||||
<div class="section-title">流程图</div>
|
||||
<div class="process-diagram">
|
||||
<!-- 模拟流程图 -->
|
||||
<div class="mock-process-diagram">
|
||||
<div class="mock-node start">开始</div>
|
||||
<div class="mock-arrow"></div>
|
||||
<div class="mock-node" :class="{ active: currentTaskName === '部门审核' }">部门审核</div>
|
||||
<div class="mock-arrow"></div>
|
||||
<div class="mock-node">主管审核</div>
|
||||
<div class="mock-arrow"></div>
|
||||
<div class="mock-node">总经理审核</div>
|
||||
<div class="mock-arrow"></div>
|
||||
<div class="mock-node end">结束</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 办理记录 -->
|
||||
<div class="process-records-container" v-if="showProcessRecords">
|
||||
<div class="section-title">
|
||||
<span>办理记录</span>
|
||||
<div class="display-mode-selector">
|
||||
<el-radio-group v-model="recordDisplayMode" size="small">
|
||||
<el-radio-button label="timeline">时间线</el-radio-button>
|
||||
<el-radio-button label="table">表格</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="process-records">
|
||||
<!-- 时间线模式 -->
|
||||
<el-timeline v-if="recordDisplayMode === 'timeline'">
|
||||
<el-timeline-item v-for="(record, index) in mockProcessRecords" :key="index" :timestamp="record.time" :type="record.type">
|
||||
<div class="record-content">
|
||||
<div class="record-title">{{ record.nodeName }} - {{ record.userName }}</div>
|
||||
<div class="record-action">{{ record.action }}</div>
|
||||
<div class="record-comment" v-if="record.comment">{{ record.comment }}</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
||||
<!-- 表格模式 -->
|
||||
<el-table v-else-if="recordDisplayMode === 'table'" :data="mockProcessRecords" stripe style="width: 100%">
|
||||
<el-table-column prop="nodeName" label="节点名称" width="120"></el-table-column>
|
||||
<el-table-column prop="userName" label="操作人" width="100"></el-table-column>
|
||||
<el-table-column prop="time" label="操作时间" width="160"></el-table-column>
|
||||
<el-table-column prop="action" label="操作类型" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.type">{{ scope.row.action }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="comment" label="操作意见">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.comment">{{ scope.row.comment }}</span>
|
||||
<span v-else class="no-comment">无</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部固定操作区域 -->
|
||||
<div class="footer-actions">
|
||||
<!-- 当前办理节点 -->
|
||||
<div v-if="currentTaskName" class="current-task-container">
|
||||
<div class="current-task-label">当前节点:</div>
|
||||
<div class="current-task-info">{{ currentTaskName }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 目标节点展示 -->
|
||||
<div v-if="sequenceFlows.length > 0" class="flow-container">
|
||||
<div class="flow-label">目标节点:</div>
|
||||
<div class="target-node-info">
|
||||
<span v-for="item in sequenceFlows" :key="item.value">{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审核意见 -->
|
||||
<div v-if="show_comment" class="comment-container">
|
||||
<div class="comment-label">审核意见:</div>
|
||||
<el-input v-model="comment" type="textarea" :rows="3" placeholder="请输入审核意见"></el-input>
|
||||
</div>
|
||||
|
||||
<!-- 驳回到指定节点选择 -->
|
||||
<div v-if="selectedAction === 'REJECT_TO_TASK' && rejectTaskOptions.length > 0" class="reject-container">
|
||||
<div class="reject-label">驳回到:</div>
|
||||
<el-select v-model="selectedRejectTask" placeholder="请选择驳回节点">
|
||||
<el-option v-for="item in rejectTaskOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="submitAction">确认</el-button>
|
||||
<el-button @click="selectedAction = ''">取消</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="button-container">
|
||||
<!-- 系统预设按钮 -->
|
||||
<template v-for="action in availableActions">
|
||||
<el-button
|
||||
:key="action"
|
||||
:type="getActionType(action)"
|
||||
:loading="loading && selectedAction === action"
|
||||
@click="handleAction(action)"
|
||||
>
|
||||
{{ getActionText(action) }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 自定义按钮 -->
|
||||
<template v-for="(button, index) in custom_buttons">
|
||||
<el-button
|
||||
:key="'custom-' + index"
|
||||
:type="button.type || 'default'"
|
||||
:loading="loading && selectedAction === button.action"
|
||||
@click="handleAction(button.action)"
|
||||
>
|
||||
{{ button.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form-button-container {
|
||||
/*display: flex;
|
||||
//flex-direction: column;*/
|
||||
min-height: 500px;
|
||||
position: relative;
|
||||
height: 100vh; /* 使用视口高度 */
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 表单信息容器 */
|
||||
.form-info-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 流程图和办理记录共用样式 */
|
||||
.process-diagram-container,
|
||||
.process-records-container {
|
||||
margin-bottom: 30px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.display-mode-selector {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 流程图样式 */
|
||||
.process-diagram {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.mock-process-diagram {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.mock-node {
|
||||
padding: 10px 15px;
|
||||
background-color: #f5f7fa;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
margin: 0 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mock-node.start {
|
||||
background-color: #f0f9eb;
|
||||
border-color: #e1f3d8;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.mock-node.end {
|
||||
background-color: #f0f2f5;
|
||||
border-color: #dcdfe6;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.mock-node.active {
|
||||
background-color: #ecf5ff;
|
||||
border-color: #d9ecff;
|
||||
color: #409eff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mock-arrow {
|
||||
width: 30px;
|
||||
height: 2px;
|
||||
background-color: #dcdfe6;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mock-arrow:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -3px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 4px 0 4px 6px;
|
||||
border-color: transparent transparent transparent #dcdfe6;
|
||||
}
|
||||
|
||||
/* 办理记录样式 */
|
||||
.process-records {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
/* 时间线模式样式 */
|
||||
.record-content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.record-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.record-action {
|
||||
color: #409eff;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.record-comment {
|
||||
color: #606266;
|
||||
background-color: #f5f7fa;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* 表格模式样式 */
|
||||
.no-comment {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 底部固定操作区域 */
|
||||
.footer-actions {
|
||||
border-top: 1px solid #ebeef5;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
//position: sticky;
|
||||
//bottom: 0;
|
||||
z-index: 10;
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.comment-container,
|
||||
.current-task-container,
|
||||
.flow-container,
|
||||
.reject-container {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.comment-label,
|
||||
.current-task-label,
|
||||
.flow-label,
|
||||
.reject-label {
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.current-task-info {
|
||||
padding: 5px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.target-node-info {
|
||||
padding: 5px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user