..
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="approvalApp" v-cloak>
|
||||
<snaker-flow
|
||||
:task_id="taskId"
|
||||
:instance_id="instanceId"
|
||||
:business_id="businessId"
|
||||
:pjax_config="{
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
}"
|
||||
></snaker-flow>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#approvalApp",
|
||||
data() {
|
||||
return {
|
||||
taskId: null,
|
||||
instanceId: null,
|
||||
businessId: null,
|
||||
formConfig: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTaskSubmitted() {},
|
||||
handleCancel() {
|
||||
window.history.back()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.taskId = new URLSearchParams(window.location.search).get("taskId")
|
||||
this.instanceId = new URLSearchParams(window.location.search).get("instanceId")
|
||||
this.businessId = new URLSearchParams(window.location.search).get("businessId")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,49 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="flowCommonApprovalForm" v-cloak>
|
||||
<snaker-flow
|
||||
:task_id="taskId"
|
||||
:instance_id="instanceId"
|
||||
:business_id="businessId"
|
||||
:pjax_urls="{
|
||||
applicationInfo: '/platform/article/write/index_',
|
||||
taskForm: '/platform/article/write/index_'
|
||||
}"
|
||||
:pjax_config="{
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
}"
|
||||
></snaker-flow>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#flowCommonApprovalForm",
|
||||
data() {
|
||||
return {
|
||||
taskId: null,
|
||||
instanceId: null,
|
||||
businessId: null,
|
||||
formConfig: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTaskSubmitted() {},
|
||||
handleCancel() {
|
||||
window.history.back()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.taskId = new URLSearchParams(window.location.search).get("taskId")
|
||||
this.instanceId = new URLSearchParams(window.location.search).get("instanceId")
|
||||
this.businessId = new URLSearchParams(window.location.search).get("businessId")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,147 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.design-dialog .el-dialog__body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="名称">
|
||||
<el-input v-model="pageForm.displayName" placeholder="名称" clearable></el-input>
|
||||
</search-item>
|
||||
<search-item label="编码">
|
||||
<el-input v-model="pageForm.name" placeholder="编码" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="displayName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="name" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="categoryName" label="流程分类">
|
||||
<template slot-scope="{row}">{{categoryOptions.find(item => item.id ===
|
||||
row.category)?.categoryName}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本号"></el-table-column>
|
||||
<el-table-column prop="state" label="状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.state===1" type="success">启用</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.state===0" type="info">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间"></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="350px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button type="primary" size="mini" @click="onView(row)">查看</el-button>
|
||||
<el-button v-if="row.state===1" type="danger" size="mini" @click="onDisable(row)">停用
|
||||
</el-button>
|
||||
<el-button v-if="row.state===0" type="primary" size="mini" @click="onEnable(row)">启用
|
||||
</el-button>
|
||||
<el-button v-if="row.state===0" type="danger" size="mini" @click="onDelete(row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog title="查看流程图" :visible.sync="designVisible" class="design-dialog" width="80%">
|
||||
<div style="height: calc(100vh - 150px); width: 100%">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0" height="100%"
|
||||
style="height: 100%; width: inherit"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
categoryOptions: [],
|
||||
designVisible: false,
|
||||
designUrl: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onView(row) {
|
||||
this.designVisible = true
|
||||
this.designUrl = "/flow/define/preview?id=" + row.id
|
||||
},
|
||||
|
||||
onDisable(row) {
|
||||
this.$confirm("确定要停用该流程吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/define/disable", { id: row.id }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("停用成功")
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onEnable(row) {
|
||||
this.$confirm("确定要启用该流程吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/define/enable", { id: row.id }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("启用成功")
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onDelete(row) {
|
||||
this.$confirm("您确定要删除吗", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/define/delete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
listCategory() {
|
||||
// this.$axios.get("/platform/warmFlow/category/list").then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// this.categoryOptions = res.data
|
||||
// }
|
||||
// })
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.listCategory()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,66 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>流程设计器</title>
|
||||
<script src="/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<script src="/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/override.css" />
|
||||
<!-- 引入 core 包和对应 css-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/logic-flow.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/style/index.css" />
|
||||
<script src="/assets/platform/plugins/snaker/SnakerflowDesigner.umd.js"></script>
|
||||
<style>
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<snaker-flow-designer ref="designer"
|
||||
v-model="flowData"
|
||||
:show-doc="false"
|
||||
:viewer="true"
|
||||
node-render-type="html"
|
||||
:wf-config="{
|
||||
showHelp: false,
|
||||
}"
|
||||
:config="{
|
||||
grid:{ size: 25,visible: true,type: 'dot',config:{color: '#ababab',thickness: 1}}
|
||||
}"
|
||||
></snaker-flow-designer>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
Vue.use(SnakerflowDesigner.default)
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
id: "${id!}",
|
||||
flowData: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDetail() {
|
||||
$.get("/flow/define/detail", { id: this.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.flowData = res.data.content
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.id) {
|
||||
this.getDetail()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
@@ -0,0 +1,185 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>流程设计</title>
|
||||
<link rel="stylesheet" href="/assets/platform/plugins/snaker/snaker.css" type="text/css" media="all" />
|
||||
</head>
|
||||
<body class="pear-container">
|
||||
<div id="toolbox">
|
||||
<div id="toolbox_handle">工具集</div>
|
||||
<div class="node" id="save">
|
||||
<img src="/assets/platform/plugins/snaker/images/save.gif" />
|
||||
保存
|
||||
</div>
|
||||
<div>
|
||||
<hr />
|
||||
</div>
|
||||
<div class="node selectable" id="pointer">
|
||||
<img src="/assets/platform/plugins/snaker/images/select16.gif" />
|
||||
Select
|
||||
</div>
|
||||
<div class="node selectable" id="path">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/flow_sequence.png" />
|
||||
transition
|
||||
</div>
|
||||
<div>
|
||||
<hr />
|
||||
</div>
|
||||
<div class="node state" id="start" type="start">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/start_event_empty.png" />
|
||||
start
|
||||
</div>
|
||||
<div class="node state" id="end" type="end">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/end_event_terminate.png" />
|
||||
end
|
||||
</div>
|
||||
<div class="node state" id="task" type="task">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/task_empty.png" />
|
||||
task
|
||||
</div>
|
||||
<div class="node state" id="task" type="custom">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/task_empty.png" />
|
||||
custom
|
||||
</div>
|
||||
<div class="node state" id="task" type="subprocess">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/task_empty.png" />
|
||||
subprocess
|
||||
</div>
|
||||
<div class="node state" id="fork" type="decision">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/gateway_exclusive.png" />
|
||||
decision
|
||||
</div>
|
||||
<div class="node state" id="fork" type="fork">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/gateway_parallel.png" />
|
||||
fork
|
||||
</div>
|
||||
<div class="node state" id="join" type="join">
|
||||
<img src="/assets/platform/plugins/snaker/images/16/gateway_parallel.png" />
|
||||
join
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="properties">
|
||||
<div id="properties_handle">属性</div>
|
||||
<table class="properties_all" cellpadding="0" cellspacing="0"></table>
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="snakerflow"></div>
|
||||
|
||||
<script src="/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<script src="/assets/platform/plugins/snaker/raphael-min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/jquery-ui-1.8.4.custom/js/jquery.min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/jquery-ui-1.8.4.custom/js/jquery-ui.min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/dialog.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.designer.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.model.js" charset="utf-8" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.editors.js" type="text/javascript"></script>
|
||||
<script>
|
||||
const processId = "${processId!}"
|
||||
console.log(processId)
|
||||
|
||||
console.log(new URLSearchParams(window.location.search).get("processId"))
|
||||
|
||||
$.post("/snakerflow/define//process/modelJson", { processId }).then((res) => {
|
||||
console.log(res)
|
||||
if (res.code === 0) {
|
||||
let model = res.data
|
||||
if (model) {
|
||||
model = eval("(" + model + ")")
|
||||
}
|
||||
$("#snakerflow").snakerflow({
|
||||
basePath: "/assets/platform/plugins/snaker/",
|
||||
ctxPath: "/",
|
||||
restore: model,
|
||||
userJson: {},
|
||||
formPath: "forms/",
|
||||
tools: {
|
||||
save: {
|
||||
onclick: function (data) {
|
||||
saveModel(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function saveModel(data) {
|
||||
$.post("/snakerflow/define/saveProcessXml", { model: data, id: processId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
// alert("保存成功")
|
||||
window.parent.postMessage("success")
|
||||
} else {
|
||||
window.parent.postMessage("error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// layui.use(['table', 'form', 'jquery', 'common', 'easyAdmin'], function () {
|
||||
// let easyAdmin = layui.easyAdmin;
|
||||
// id = getQueryString("processId");
|
||||
// easyAdmin.httpGet("/flow/process/modelJson?processId=" + id, function (data) {
|
||||
// var model = "";
|
||||
// if (data) {
|
||||
// model = eval("(" + data + ")");
|
||||
// }
|
||||
// var userJson = {};
|
||||
// easyAdmin.httpGet("/sys/user/getAll", function (result) {
|
||||
// if (result.success) {
|
||||
// userJson = result.data;
|
||||
// }
|
||||
// }, true, false);
|
||||
//
|
||||
// var isSubmitting = false; // 添加一个变量用于标记是否正在提交中
|
||||
//
|
||||
// $('#snakerflow').snakerflow({
|
||||
// basePath: "/assets/platform/plugins/snaker/",
|
||||
// ctxPath: "/",
|
||||
// restore: model,
|
||||
// userJson: userJson,
|
||||
// formPath: "forms/",
|
||||
// tools: {
|
||||
// save: {
|
||||
// onclick: function (data) {
|
||||
// if (isSubmitting) {
|
||||
// return false; // 如果正在提交中,则不执行后续代码
|
||||
// }
|
||||
// isSubmitting = true; // 设置为正在提交中状态
|
||||
// saveModel(data);
|
||||
// isSubmitting = false; // 处理完成后重置提交状态
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
//
|
||||
//
|
||||
// function getQueryString(name) {
|
||||
// var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
|
||||
// var r = window.location.search.substr(1).match(reg);
|
||||
// if (r !== null)
|
||||
// return unescape(r[2]);
|
||||
// return "";
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // window.saveModel = function (data) {
|
||||
// // easyAdmin.http({
|
||||
// // type: 'POST',
|
||||
// // url: "/flow/process/deployXml",
|
||||
// // data: "model=" + data + "&id=" + id,
|
||||
// // async: false,
|
||||
// // globle: false,
|
||||
// // success: function (data) {
|
||||
// // parent.layer.close(parent.layer.getFrameIndex(window.name));//关闭当前页
|
||||
// // parent.layui.table.reload("user-table");
|
||||
// // }
|
||||
// // });
|
||||
// //
|
||||
// // }
|
||||
// })
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,235 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.design-dialog .el-dialog__body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="名称">
|
||||
<el-input v-model="pageForm.displayName" placeholder="名称" clearable></el-input>
|
||||
</search-item>
|
||||
<search-item label="编码">
|
||||
<el-input v-model="pageForm.name" placeholder="编码" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
<el-button type="primary" @click="onAdd" size="small" icon="el-icon-plus">新增</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="displayName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="name" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="categoryName" label="流程分类">
|
||||
<template slot-scope="{row}">{{categoryOptions.find(item => item.id ===
|
||||
row.category)?.categoryName}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本号"></el-table-column>
|
||||
<el-table-column prop="state" label="状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.state===1" type="success">启用</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.state===0" type="info">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间"></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="350px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="onDesign(row)">设计</el-button>
|
||||
</el-button>
|
||||
<el-button size="mini" type="primary" @click="onEdit(row)">编辑</el-button>
|
||||
<!-- <el-button type="text" size="mini" @click="onView(row)">查看</el-button>-->
|
||||
<el-button v-if="row.state===1" type="danger" size="mini" @click="onUndeploy(row)">停用
|
||||
</el-button>
|
||||
<el-button v-if="row.state===0" type="primary" size="mini" @click="onPublish(row)">发布
|
||||
</el-button>
|
||||
<el-button v-if="row.state===0" type="danger" size="mini" @click="onDelete(row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="800px">
|
||||
<el-form :model="formData" ref="formRef" label-width="120px">
|
||||
<el-form-item label="流程定义名称" prop="flowName" :rules="{ required: true, message: '请输入流程定义名称', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowName" placeholder="请输入流程定义名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程定义编码" prop="flowCode" :rules="{ required: true, message: '请输入流程定义编码', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowCode" placeholder="请输入流程定义编码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category" :rules="{ required: true, message: '请选择流程分类', trigger: 'change' }">
|
||||
<el-select v-model="formData.category" placeholder="请选择流程分类" clearable>
|
||||
<el-option v-for="item in categoryOptions" :key="item.id" :label="item.categoryName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="流程设计" :visible.sync="designVisible" fullscreen class="design-dialog">
|
||||
<div style="height: calc(100vh - 55px); width: 100%">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0" height="100%"
|
||||
style="height: 100%; width: inherit"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
<el-dialog title="xml编辑" :visible.sync="xmlVisible" width="70%">
|
||||
<el-input type="textarea" style="width: 100%;" autosize v-model="formData.model"></el-input>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="xmlVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSave">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
categoryOptions: [],
|
||||
designVisible: false,
|
||||
designUrl: null,
|
||||
xmlVisible: false,
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onAdd(){
|
||||
this.formData = {}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onSubmit(){
|
||||
$.post("/snakerflow/define/addProcess", this.formData)
|
||||
},
|
||||
|
||||
|
||||
onDesign(row = null) {
|
||||
this.designVisible = true
|
||||
this.iframeLoaded()
|
||||
this.designUrl = "/flow/define/designer2?processId=" + row?.id
|
||||
},
|
||||
|
||||
onEdit(row) {
|
||||
this.xmlVisible = true
|
||||
this.$axios.post("/snakerflow/define/getXml", { id: row.id }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.formData = {
|
||||
id: row.id,
|
||||
model: res.data
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onSave() {
|
||||
$.post("/snakerflow/define/saveProcessXml", { ...this.formData, xmlHeader: true }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.pageData()
|
||||
this.xmlVisible = false
|
||||
} else {
|
||||
this.$message.error("保存失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
iframeLoaded() {
|
||||
// iframe监听组件内设计器保存事件
|
||||
window.onmessage = (event) => {
|
||||
console.log(event)
|
||||
if (event.data === "success") {
|
||||
this.$message.success("保存成功")
|
||||
this.designVisible = false
|
||||
this.pageData()
|
||||
} else if (event.data === "error") {
|
||||
this.$message.error("保存失败")
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onUndeploy(row) {
|
||||
this.$confirm("您确定要停用吗", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/snakerflow/define/undeploy", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onPublish(row) {
|
||||
this.$confirm("您确定要发布吗", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/snakerflow/define/publish", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onDelete(row) {
|
||||
this.$confirm("您确定要删除吗", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/snakerflow/define/delete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
},
|
||||
|
||||
listCategory() {
|
||||
// this.$axios.get("/platform/warmFlow/category/list").then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// this.categoryOptions = res.data
|
||||
// }
|
||||
// })
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.listCategory()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,254 @@
|
||||
const assigneeForm = {
|
||||
template: /*language=HTML*/`
|
||||
<el-form-item label="参与者">
|
||||
<div>
|
||||
<!-- 模式选择 -->
|
||||
<div style="margin-bottom: 10px;">
|
||||
<el-radio-group v-model="assigneeMode" @change="handleModeChange" size="small">
|
||||
<el-radio-button label="manual" :disabled="isFirstTaskNode">手动输入</el-radio-button>
|
||||
<el-radio-button label="select" :disabled="isFirstTaskNode">选择输入</el-radio-button>
|
||||
<el-radio-button label="initiator">流程发起人</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- 手动输入模式 -->
|
||||
<el-input
|
||||
v-if="assigneeMode === 'manual'"
|
||||
v-model="manualInput"
|
||||
placeholder="请输入参与者,多个参与者用逗号分隔"
|
||||
@change="updateModelValue">
|
||||
</el-input>
|
||||
|
||||
<!-- 选择输入模式 -->
|
||||
<el-input
|
||||
v-if="assigneeMode === 'select'"
|
||||
v-model="assigneeText"
|
||||
readonly
|
||||
style="width: 100%"
|
||||
placeholder="请选择参与者">
|
||||
<el-button
|
||||
slot="append"
|
||||
type="primary"
|
||||
style="color: #fff;background-color: var(--color-primary);border-color: var(--color-primary)"
|
||||
icon="el-icon-setting"
|
||||
@click="openUserDialog">
|
||||
选择
|
||||
</el-button>
|
||||
</el-input>
|
||||
|
||||
<!-- 流程发起人模式 -->
|
||||
<el-input
|
||||
v-if="assigneeMode === 'initiator'"
|
||||
value="initiator"
|
||||
readonly
|
||||
disabled
|
||||
style="width: 100%"
|
||||
placeholder="流程发起人">
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<!-- 用户选择弹窗 -->
|
||||
<el-dialog title="选择参与者" :visible.sync="dialogVisible" width="80%" append-to-body>
|
||||
<div style="display: flex;">
|
||||
<!-- 左侧表格 -->
|
||||
<div style="flex: 3; margin-right: 10px; overflow: auto;">
|
||||
<div style="margin-bottom: 10px;">
|
||||
<el-input v-model="pageForm.searchKeyword" placeholder="请输入关键字搜索" style="width: 200px; margin-right: 10px;"></el-input>
|
||||
<el-button type="primary" @click="searchUsers">搜索</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="userTableRef"
|
||||
:data="userList"
|
||||
border
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column type="selection" width="55" :reserve-selection="true"></el-table-column>
|
||||
<el-table-column prop="loginname" label="工号" width="100"></el-table-column>
|
||||
<el-table-column prop="username" label="姓名" width="120"></el-table-column>
|
||||
<el-table-column prop="unitName" label="单位"></el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top: 10px; text-align: right;">
|
||||
<el-pagination
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageForm.pageNumber"
|
||||
:page-size="pageForm.pageSize"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
:total="pageForm.totalCount">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧已选列表 -->
|
||||
<div style="flex: 1; border: 1px solid #EBEEF5; border-radius: 4px; padding: 10px; overflow: auto;" v-if="$refs.userTableRef">
|
||||
<div style="font-weight: bold; margin-bottom: 10px;">已选择的参与者</div>
|
||||
<el-tag
|
||||
v-for="user in $refs.userTableRef.selection"
|
||||
:key="user.id"
|
||||
style="margin: 0 5px 5px 0;">
|
||||
{{ user.username }}
|
||||
</el-tag>
|
||||
<div v-if="$refs.userTableRef.selection.length === 0" style="color: #909399; font-size: 14px;">
|
||||
暂无选择的参与者
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="confirmSelection">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</el-form-item>
|
||||
`,
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
start_node_next_node_ids: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 判断当前节点是否为第一个任务节点
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isFirstTaskNode() {
|
||||
return this.start_node_next_node_ids.includes(this.model.name)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
assigneeMode: "manual",
|
||||
manualInput: "",
|
||||
assigneeText: "",
|
||||
dialogVisible: false,
|
||||
userList: [],
|
||||
selectedUsers: [],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
searchKeyword: "",
|
||||
totalCount: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 回显
|
||||
this.initFromModel()
|
||||
},
|
||||
methods: {
|
||||
initFromModel() {
|
||||
const value = this.model[this.field]
|
||||
this.assigneeText = this.model["assigneeText"] || ""
|
||||
this.assigneeMode = this.model["assigneeMode"] || "manual"
|
||||
|
||||
if(this.assigneeMode === "initiator"){
|
||||
this.assignee = "initiator"
|
||||
this.assigneeText = "流程发起人"
|
||||
}
|
||||
|
||||
if(this.assigneeMode === "manual"){
|
||||
this.manualInput = value || ""
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
handleModeChange() {
|
||||
this.assigneeText = ""
|
||||
this.manualInput = ""
|
||||
this.assignee = ""
|
||||
|
||||
// 如果切换到流程发起人模式,直接设置值
|
||||
if (this.assigneeMode === "initiator") {
|
||||
this.assignee = "{$initiator}"
|
||||
this.assigneeText = "流程发起人"
|
||||
}
|
||||
|
||||
this.updateModelValue()
|
||||
},
|
||||
openUserDialog() {
|
||||
this.dialogVisible = true
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.searchKeyword = ""
|
||||
this.loadSelectUsers()
|
||||
},
|
||||
|
||||
loadSelectUsers() {
|
||||
let userIds = this.model[this.field].split(",")
|
||||
$.get("/flow/design/assigneeEcho", { userIds: JSON.stringify(userIds) }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.selectedUsers = res.data
|
||||
this.loadUsers()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
loadUsers() {
|
||||
$.get("/flow/design/assigneePage", {
|
||||
...this.pageForm
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.userList = res.data.list
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
// 设置已选中的行
|
||||
this.$nextTick(() => {
|
||||
this.setTableSelection()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 设置表格选中数据
|
||||
setTableSelection() {
|
||||
// 如果当前表格中有该用户,选中TA
|
||||
this.userList.forEach(row => {
|
||||
const exist = this.selectedUsers.map(item => item.id).includes(row.id)
|
||||
if (exist) {
|
||||
this.$refs.userTableRef.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
confirmSelection() {
|
||||
const selection = this.$refs.userTableRef.selection
|
||||
this.assignee = selection.map(item => item.id).join(",")
|
||||
this.assigneeText = selection.map(item => item.username).join(",")
|
||||
this.updateModelValue()
|
||||
this.dialogVisible = false
|
||||
},
|
||||
updateModelValue() {
|
||||
// 更新model中的值
|
||||
if (this.assigneeMode === "manual") {
|
||||
this.assignee = this.manualInput
|
||||
this.assigneeText = this.manualInput
|
||||
} else if (this.assigneeMode === "initiator") {
|
||||
this.assignee = "initiator"
|
||||
this.assigneeText = "流程发起人"
|
||||
}
|
||||
// 选择模式的处理在confirmSelection中
|
||||
|
||||
this.$set(this.model, this.field, this.assignee)
|
||||
this.$set(this.model, "assigneeText", this.assigneeText)
|
||||
this.$set(this.model, "assigneeMode", this.assigneeMode)
|
||||
},
|
||||
searchUsers() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.loadUsers()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.pageForm.pageSize = val
|
||||
this.loadUsers()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageForm.pageNumber = val
|
||||
this.loadUsers()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>流程设计器</title>
|
||||
<script src="/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<script src="/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/override.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/root.css" />
|
||||
<!-- 引入 core 包和对应 css-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/logic-flow.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/style/index.css" />
|
||||
<script src="/assets/platform/plugins/snaker/SnakerflowDesigner.umd.js"></script>
|
||||
<style>
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<snaker-flow-designer ref="designer"
|
||||
v-model="flowData"
|
||||
@on-save="handleSave"
|
||||
:show-doc="false"
|
||||
node-render-type="html"
|
||||
:extend-property-keys="[
|
||||
'candidateUsers',
|
||||
'candidateGroups',
|
||||
'candidateHandler',
|
||||
'assigneeText',
|
||||
'assigneeMode'
|
||||
]"
|
||||
>
|
||||
<!-- <template v-slot:form-item-task-form="{model,field}">-->
|
||||
<!-- <el-form-item label="form">-->
|
||||
<!-- -->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </template>-->
|
||||
|
||||
<template v-slot:form-item-task-assignee="{model,field}">
|
||||
<assignee-form :model="model" :field="field"
|
||||
:start_node_next_node_ids="startNodeNextNodeIds"></assignee-form>
|
||||
</template>
|
||||
|
||||
<template v-slot:form-item-task-assignment-handler="{model,field}">
|
||||
<el-form-item label="参与者处理类">
|
||||
<el-select v-model="model[field]" style="width: 100%" clearable
|
||||
:disabled="startNodeNextNodeIds.includes(model.name)">
|
||||
<el-option v-for="item in assignmentHandlerClassOptions" :key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
</snaker-flow-designer>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
<!--#include('assigneeForm.js'){}#-->
|
||||
|
||||
Vue.use(SnakerflowDesigner.default)
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
components: {
|
||||
"assignee-form": assigneeForm
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: "${id!}",
|
||||
designerData: {},
|
||||
flowData: {},
|
||||
assignmentHandlerClassOptions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
startNodeNextNodeIds() {
|
||||
if (this.$refs.designer && this.$refs.designer.lf) {
|
||||
const graphData = this.$refs.designer.lf.getGraphData()
|
||||
if (graphData) {
|
||||
const { nodes, edges } = graphData
|
||||
const startNode = nodes.find(v => v.type === "snaker:start")
|
||||
if (startNode) {
|
||||
const targetNodeIds = edges.filter(v => v.sourceNodeId === startNode.id).map(v => v.targetNodeId)
|
||||
return targetNodeIds
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSave(val) {
|
||||
const design = {
|
||||
...this.designerData,
|
||||
content: val.json
|
||||
}
|
||||
$.post("/flow/design/updateContent", { design: JSON.stringify(design) }).then(res => {
|
||||
if (res.code === 0) {
|
||||
window.parent.postMessage("success")
|
||||
} else {
|
||||
window.parent.postMessage("error")
|
||||
}
|
||||
})
|
||||
},
|
||||
getDetail() {
|
||||
$.get("/flow/design/detail", { id: this.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.designerData = res.data
|
||||
if (res.data.content) {
|
||||
this.flowData = res.data.content
|
||||
} else {
|
||||
this.flowData = {}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
init() {
|
||||
$.get("/flow/design/assigmentHandlerClass").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.assignmentHandlerClassOptions = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getData() {
|
||||
console.log(this.$refs.designer)
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
mounted() {
|
||||
if (this.id) {
|
||||
this.getDetail()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
@@ -0,0 +1,214 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.design-dialog .el-dialog__body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="名称">
|
||||
<el-input v-model="pageForm.displayName" placeholder="名称" clearable></el-input>
|
||||
</search-item>
|
||||
<search-item label="编码">
|
||||
<el-input v-model="pageForm.name" placeholder="编码" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
<el-button type="primary" @click="onAdd" size="small" icon="el-icon-plus">新增</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="displayName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="name" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="type" label="流程分类"></el-table-column>
|
||||
<el-table-column prop="isDeployed" label="是否部署">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.isDeployed===1" type="success">是</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.isDeployed===0" type="info">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="450px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="onDesign(row)">设计</el-button>
|
||||
</el-button>
|
||||
<el-button size="mini" type="primary" @click="onEdit(row)">编辑</el-button>
|
||||
<!-- v-if="row.isDeployed===0"-->
|
||||
<el-button type="primary" size="mini" @click="onDeploy(row)">部署
|
||||
</el-button>
|
||||
<el-button type="danger" size="mini" @click="onDelete(row)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="800px">
|
||||
<el-form :model="formData" ref="formRef" label-width="120px">
|
||||
<el-form-item label="流程定义名称" prop="displayName"
|
||||
:rules="{ required: true, message: '请输入流程定义名称', trigger: 'blur' }">
|
||||
<el-input v-model="formData.displayName" placeholder="请输入流程定义名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程定义编码" prop="name"
|
||||
:rules="{ required: true, message: '请输入流程定义编码', trigger: 'blur' }">
|
||||
<el-input v-model="formData.name" placeholder="请输入流程定义编码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category"
|
||||
:rules="{ required: false, message: '请选择流程分类', trigger: 'change' }"
|
||||
style="width: 100%">
|
||||
<el-select v-model="formData.category" placeholder="请选择流程分类" clearable>
|
||||
<el-option v-for="item in categoryOptions" :key="item.id" :label="item.categoryName"
|
||||
:value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="电脑端发起地址" prop="instanceUrl">
|
||||
<el-input v-model="formData.instanceUrl" placeholder="请输入电脑端发起地址"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机端发起地址" prop="h5InstanceUrl">
|
||||
<el-input v-model="formData.h5InstanceUrl" placeholder="请输入手机端发起地址"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="图标" prop="icon">
|
||||
<el-input v-model="formData.icon" placeholder="请输入图标"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="流程设计" :visible.sync="designVisible" fullscreen class="design-dialog">
|
||||
<div style="height: calc(100vh - 55px); width: 100%">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0" height="100%"
|
||||
style="height: 100%; width: inherit"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
categoryOptions: [],
|
||||
designVisible: false,
|
||||
designUrl: null,
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onAdd() {
|
||||
this.formData = {}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onEdit(row) {
|
||||
this.dialogVisible = true
|
||||
this.formData = { ...row }
|
||||
},
|
||||
onSubmit() {
|
||||
$.post("/flow/design/" + (this.formData.id ? "update" : "insert"), { design: JSON.stringify(this.formData) }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
this.dialogVisible = false
|
||||
}
|
||||
})
|
||||
},
|
||||
onDesign(row) {
|
||||
this.designVisible = true
|
||||
this.iframeLoaded()
|
||||
this.designUrl = "/flow/design/designer?id=" + row.id
|
||||
},
|
||||
|
||||
iframeLoaded() {
|
||||
// iframe监听组件内设计器保存事件
|
||||
window.onmessage = (event) => {
|
||||
console.log(event)
|
||||
if (event.data === "success") {
|
||||
this.$message.success("保存成功")
|
||||
this.designVisible = false
|
||||
this.pageData()
|
||||
} else if (event.data === "error") {
|
||||
this.$message.error("保存失败")
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDeploy(row) {
|
||||
this.$confirm("部署会生成新的流程定义版本,确定部署吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/design/deploy", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onRedeploy(row) {
|
||||
this.$confirm("重新部署会覆盖最新版流程定义,确定重新部署吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/design/redeploy", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onDelete(row) {
|
||||
this.$confirm("您确定要删除吗", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/design/delete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onView(row) {
|
||||
},
|
||||
|
||||
listCategory() {
|
||||
// this.$axios.get("/platform/warmFlow/category/list").then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// this.categoryOptions = res.data
|
||||
// }
|
||||
// })
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.listCategory()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,80 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>流程设计器</title>
|
||||
<script src="/assets/platform/plugins/vue/vue.js"></script>
|
||||
<script src="/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<script src="/assets/platform/plugins/element-ui/lib/index.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.net/element-ui/2.15.14/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index.css" />
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/override.css" />
|
||||
<!-- 引入 core 包和对应 css-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/logic-flow.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@logicflow/core@1.2.12/dist/style/index.css" />
|
||||
<script src="/assets/platform/plugins/snaker/SnakerflowDesigner.umd.js"></script>
|
||||
<style>
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<snaker-flow-designer ref="designer"
|
||||
v-model="flowData"
|
||||
@on-save="handleSave"
|
||||
:show-doc="false"
|
||||
node-render-type="html"
|
||||
:extendPropertyKeys="[
|
||||
'candidateUsers',
|
||||
'candidateGroups',
|
||||
'candidateHandler',
|
||||
'assigneeText',
|
||||
]"
|
||||
></snaker-flow-designer>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
Vue.use(SnakerflowDesigner.default)
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
id: "${id!}",
|
||||
flowData: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSave(val) {
|
||||
console.log(val)
|
||||
const data = {
|
||||
processDesignId: this.id,
|
||||
...val.json
|
||||
}
|
||||
$.post("/flow/design/updateDesign", { json: JSON.stringify(data) }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
window.parent.postMessage("success")
|
||||
} else {
|
||||
window.parent.postMessage("error")
|
||||
}
|
||||
})
|
||||
},
|
||||
getDetail() {
|
||||
$.get("/flow/design/detail", { id: this.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.flowData = res.data.jsonObject
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.id) {
|
||||
this.getDetail()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
@@ -3,8 +3,6 @@ layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<!-- <iframe src="/uflo/designer"></iframe>-->
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<el-row type="flex">
|
||||
<el-button @click="openDesigner" size="medium" type="primary">
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>流程状态</title>
|
||||
<link rel="stylesheet" href="/assets/platform/plugins/snaker/snaker.css" type="text/css" media="all"/>
|
||||
<link rel="stylesheet" href="/assets/platform/plugins/snaker/style.css" type="text/css" media="all"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</div>
|
||||
|
||||
<table class="properties_all" align="center" border="1" cellpadding="0" cellspacing="0" style="margin-top: 0px">
|
||||
<div id="snakerflow"
|
||||
style="border: 1px solid #d2dde2; margin-top:10px; margin-left:10px; margin-bottom:10px; width:98%;">
|
||||
</div>
|
||||
</table>
|
||||
|
||||
<script src="/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<script src="/assets/platform/plugins/snaker/raphael-min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/jquery-ui-1.8.4.custom/js/jquery.min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/jquery-ui-1.8.4.custom/js/jquery-ui.min.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/dialog.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.designer.js" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.model.js" charset="utf-8" type="text/javascript"></script>
|
||||
<script src="/assets/platform/plugins/snaker/snaker.editors.js" type="text/javascript"></script>
|
||||
<script>
|
||||
|
||||
const orderId = "${orderId!}"
|
||||
const processId = "${processId!}"
|
||||
|
||||
$(function(){
|
||||
$.post("/snakerflow/define/process/json", { processId, orderId }).then(res => {
|
||||
if (res.code === 0) {
|
||||
display(res.data.process, res.data.state)
|
||||
}
|
||||
})
|
||||
|
||||
function display(process, state) {
|
||||
/** view*/
|
||||
$('#snakerflow').snakerflow($.extend(true, {
|
||||
basePath: "/assets/platform/plugins/snaker/",
|
||||
// ctxPath: easyAdmin.GetAdminServerUrl(),
|
||||
// token: easyAdmin.GetTokenQueryString(),
|
||||
orderId: orderId,
|
||||
restore: eval("(" + process + ")"),
|
||||
editable: false
|
||||
}, eval("(" + state + ")")
|
||||
));
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,604 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div class="task-todo-center">
|
||||
<!-- 统计数据区域 -->
|
||||
<el-row :gutter="20" class="statistics-section">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon todo-icon">
|
||||
<i class="el-icon-s-claim"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{todoCount}}</div>
|
||||
<div class="stat-label">待办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon done-icon">
|
||||
<i class="el-icon-finished"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{doneCount}}</div>
|
||||
<div class="stat-label">已办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon started-icon">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{startedCount}}</div>
|
||||
<div class="stat-label">我发起的</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<el-card shadow="never" class="filter-section">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input v-model="searchForm.keyword" placeholder="请输入关键词搜索" prefix-icon="el-icon-search" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="searchForm.category" placeholder="所有流程类型" clearable>
|
||||
<el-option v-for="type in processTypes" :key="type.value" :label="type.label" :value="type.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">搜索</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务列表区域 -->
|
||||
<el-card shadow="never" class="task-section">
|
||||
<div slot="header" class="task-header">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="待办任务" name="todo"></el-tab-pane>
|
||||
<el-tab-pane label="已办任务" name="done"></el-tab-pane>
|
||||
<el-tab-pane label="我发起的" name="started"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 表格展示 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tasks"
|
||||
style="width: 100%"
|
||||
:key="activeTab"
|
||||
:header-cell-style="{backgroundColor: '#f5f7fa'}"
|
||||
:row-class-name="tableRowClassName"
|
||||
>
|
||||
<el-table-column prop="processInstanceName" label="流程名称" min-width="180" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span class="task-title">{{scope.row.processInstanceName || scope.row.title}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskName" label="任务节点" min-width="150" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="processDefinitionName" label="流程类型" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{scope.row.processDefinitionName || scope.row.processType}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="initiatorName" label="申请人" min-width="120">
|
||||
<template slot-scope="{row}">
|
||||
{{row.variable.initiatorName}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时间" min-width="180">
|
||||
<template slot-scope="scope">
|
||||
<div v-if="activeTab === 'todo'">
|
||||
<i class="el-icon-time"></i>
|
||||
{{scope.row.createdAt}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'done'">
|
||||
<i class="el-icon-check"></i>
|
||||
{{scope.row.finishTime}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'started'">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
{{scope.row.createdAt}}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="activeTab === 'started'" label="状态" width="100">
|
||||
<template slot-scope="{row}">
|
||||
{{processStatusMap[row.state]?.text}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" size="mini" @click="handleTask(scope.row)">处理</el-button>
|
||||
<el-button type="info" size="mini" @click="viewTaskDetail(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态展示 -->
|
||||
<el-empty v-if="tasks.length === 0" :description="getEmptyText()"></el-empty>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="tasks.length > 0">
|
||||
<el-pagination
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="total"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务详情对话框 -->
|
||||
<el-dialog title="任务详情" :visible.sync="dialogVisible" width="600px" :close-on-click-modal="false">
|
||||
<div v-if="currentTask" class="task-detail">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="流程名称">{{currentTask.processInstanceName || currentTask.title}}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">{{currentTask.taskName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="流程类型">{{currentTask.processDefinitionName || currentTask.processType}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{currentTask.applyUserName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请部门">{{currentTask.applyUserUnitId || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{currentTask.createTime}}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.description" label="任务描述">
|
||||
<div class="description-content">{{currentTask.description}}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.taskMobileFormUrl" label="表单链接">
|
||||
<el-link type="primary" :href="currentTask.taskMobileFormUrl" target="_blank">点击查看详细表单</el-link>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" @click="handleTask(currentTask)">处理任务</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.task-todo-center {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.statistics-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-card .el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.todo-icon {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.done-icon {
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.started-icon {
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 搜索区域样式 */
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-section .el-card__body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form .el-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 任务列表区域样式 */
|
||||
.task-section .el-card__header {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 优化后的 tabs 样式 */
|
||||
.task-header .el-tabs__header {
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 15px;
|
||||
color: #606266;
|
||||
padding: 0 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item.is-active {
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__active-bar {
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 表格行样式 */
|
||||
.el-table .hover-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 优化后的分页样式 */
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination {
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev,
|
||||
.pagination-container .el-pagination .btn-next {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev:hover,
|
||||
.pagination-container .el-pagination .btn-next:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 3px;
|
||||
font-weight: normal;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__jump {
|
||||
margin-left: 15px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input {
|
||||
width: 50px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input .el-input__inner {
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 任务详情样式 */
|
||||
.task-detail .el-descriptions {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.description-content {
|
||||
background-color: #f5f7fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
// 统计数据
|
||||
todoCount: 0,
|
||||
doneCount: 0,
|
||||
startedCount: 0,
|
||||
|
||||
// 搜索表单
|
||||
searchForm: {
|
||||
keyword: "",
|
||||
category: ""
|
||||
},
|
||||
|
||||
// 流程类型选项
|
||||
processTypes: [],
|
||||
|
||||
// 任务列表
|
||||
tasks: [],
|
||||
loading: false,
|
||||
|
||||
// 分页
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// 标签页
|
||||
activeTab: "todo",
|
||||
|
||||
// 当前任务
|
||||
currentTask: null,
|
||||
dialogVisible: false,
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
totalPages() {
|
||||
return Math.ceil(this.total / this.pageSize)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.initData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
await Promise.all([this.getStatistics(), this.getProcessTypes(), this.getTasks()])
|
||||
},
|
||||
|
||||
// 获取统计数据
|
||||
async getStatistics() {
|
||||
try {
|
||||
const res = await $.post("/flow/todoCenter/statistics")
|
||||
if (res.code === 0) {
|
||||
const { todoCount, doneCount, startedCount } = res.data
|
||||
this.todoCount = todoCount
|
||||
this.doneCount = doneCount
|
||||
this.startedCount = startedCount
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取统计数据失败")
|
||||
console.error("获取统计数据失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取流程类型
|
||||
async getProcessTypes() {
|
||||
try {
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/category")
|
||||
if (res.code === 0) {
|
||||
this.processTypes = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取流程类型失败")
|
||||
console.error("获取流程类型失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取任务列表
|
||||
async getTasks() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
type: this.activeTab,
|
||||
keyword: this.searchForm.keyword,
|
||||
category: this.searchForm.category
|
||||
}
|
||||
|
||||
const res = await $.post("/flow/todoCenter/" + this.activeTab, params)
|
||||
if (res.code === 0) {
|
||||
this.tasks = res.data.list
|
||||
this.total = res.data.totalCount
|
||||
this.dialogVisible = false
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取任务列表失败")
|
||||
console.error("获取任务列表失败:", error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 处理标签页点击
|
||||
handleTabClick(tab) {
|
||||
this.activeTab = tab.name
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 搜索
|
||||
search() {
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
this.getStatistics()
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
reset() {
|
||||
this.searchForm = {
|
||||
keyword: "",
|
||||
category: ""
|
||||
}
|
||||
this.search()
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 查看任务详情
|
||||
viewTaskDetail(task) {
|
||||
// this.currentTask = task
|
||||
// this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 处理任务
|
||||
async handleTask(task) {
|
||||
if (!task) return
|
||||
|
||||
// 如果有表单链接,直接跳转
|
||||
if (task.taskMobileFormUrl) {
|
||||
window.open(task.taskMobileFormUrl, "_blank")
|
||||
return
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态对应的类型
|
||||
getStatusType(task) {
|
||||
if (!task.status) return "info"
|
||||
|
||||
switch (task.status) {
|
||||
case "审批中":
|
||||
return "primary"
|
||||
case "已完成":
|
||||
return "success"
|
||||
case "已通过":
|
||||
return "success"
|
||||
case "已拒绝":
|
||||
return "danger"
|
||||
default:
|
||||
return "info"
|
||||
}
|
||||
},
|
||||
|
||||
// 获取空状态文本
|
||||
getEmptyText() {
|
||||
switch (this.activeTab) {
|
||||
case "todo":
|
||||
return "您当前没有需要办理的任务,辛苦了"
|
||||
case "done":
|
||||
return "您当前没有已办理的任务记录"
|
||||
case "started":
|
||||
return "您当前没有发起的流程"
|
||||
default:
|
||||
return "暂无数据"
|
||||
}
|
||||
},
|
||||
|
||||
// 表格行类名
|
||||
tableRowClassName({ row, rowIndex }) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -1,10 +1,12 @@
|
||||
const SYS_MENU_BASIC_FORM_COMPONENT = {
|
||||
template: `
|
||||
<el-dialog :title="formData.id ? '编辑菜单' : '新增菜单'" :show-close="false" :visible.sync="visibleDialog" :close-on-click-modal="false" width="50%">
|
||||
template: /*language=HTML*/ `
|
||||
<el-dialog :title="formData.id ? '编辑菜单' : '新增菜单'" :show-close="false" :visible.sync="visibleDialog"
|
||||
:close-on-click-modal="false" width="50%">
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" size="small" label-width="80px">
|
||||
<el-form-item label="所属模块" prop="moduleId">
|
||||
<el-select v-model="formData.moduleId" clearable placeholder="请选择模块">
|
||||
<el-option v-for="item in moduleOptions" :value="item.id" :key="item.id" :label="item.name"></el-option>
|
||||
<el-option v-for="item in moduleOptions" :value="item.id" :key="item.id"
|
||||
:label="item.name"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="parentId" label="上级菜单" label-width="80px">
|
||||
@@ -39,28 +41,37 @@ const SYS_MENU_BASIC_FORM_COMPONENT = {
|
||||
<el-input maxlength="100" placeholder="URL" v-model="formData.href" auto-complete="off" tabindex="6"
|
||||
type="text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="icon" label="图标">
|
||||
<el-input maxlength="100"
|
||||
v-if="platform==='PC'"
|
||||
placeholder="图标"
|
||||
v-model="formData.icon"
|
||||
auto-complete="off"
|
||||
tabindex="7"
|
||||
type="text">
|
||||
<template slot="append">
|
||||
<el-button @click="$refs.iconSelector.showIconModal(formData.icon)">图标选择</el-button>
|
||||
<icon-selector ref="iconSelector" v-model="formData.icon"></icon-selector>
|
||||
</template>
|
||||
</el-input>
|
||||
<file-upload
|
||||
v-if="platform==='H5'"
|
||||
:value.sync="formData.icon"
|
||||
:upload_number="1"
|
||||
upload_mode="image"
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
<el-form-item prop="picIcon" label="图片图标">
|
||||
<!-- <template v-if="platform==='PC'">-->
|
||||
<!-- <el-input maxlength="100" placeholder="图标" v-model="formData.icon"></el-input>-->
|
||||
<!-- <i :class="formData.icon" v-if="formData.icon"></i>-->
|
||||
<!-- </template> -->
|
||||
<!-- <el-input maxlength="100" -->
|
||||
<!-- v-if="platform==='PC'"-->
|
||||
<!-- placeholder="图标" -->
|
||||
<!-- v-model="formData.icon" -->
|
||||
<!-- auto-complete="off" -->
|
||||
<!-- tabindex="7"-->
|
||||
<!-- type="text">-->
|
||||
<!-- <template slot="append">-->
|
||||
<!-- <el-button @click="$refs.iconSelector.showIconModal(formData.icon)">图标选择</el-button>-->
|
||||
<!-- <icon-selector ref="iconSelector" v-model="formData.icon"></icon-selector>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-input>-->
|
||||
<file-upload
|
||||
:value.sync="formData.picIcon"
|
||||
:upload_number="1"
|
||||
upload_mode="image"
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="图标" prop="icon">
|
||||
<el-input maxlength="100" placeholder="图标" v-model="formData.icon"></el-input>
|
||||
<i :class="formData.icon" v-if="formData.icon"></i>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="disabled" label="启用状态">
|
||||
<el-switch v-model="formData.disabled" active-color="#ff4949" inactive-color="#13ce66"></el-switch>
|
||||
</el-form-item>
|
||||
@@ -83,10 +94,12 @@ const SYS_MENU_BASIC_FORM_COMPONENT = {
|
||||
<el-input v-model="menu.name" placeholder="权限名称"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="13">
|
||||
<el-input v-model="menu.permission" placeholder="权限标识,如 sys.manager.menu.add"></el-input>
|
||||
<el-input v-model="menu.permission"
|
||||
placeholder="权限标识,如 sys.manager.menu.add"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-button @click.prevent="formRemoveMenu(menu)" icon="el-icon-delete" size="small"></el-button>
|
||||
<el-button @click.prevent="formRemoveMenu(menu)" icon="el-icon-delete"
|
||||
size="small"></el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
@@ -38,8 +38,8 @@ layout("/layouts/platform.html"){
|
||||
</div>
|
||||
</el-image>
|
||||
</template>
|
||||
<template scope="{row}" v-else-if="column.prop=='containsMenu'">
|
||||
{{firstMenus.filter(v=>row.containsMenu.includes(v.id)).map(v=>v.name).join('、')}}
|
||||
<template scope="{row}" v-else-if="column.prop=='faIcon'">
|
||||
<i :class="row.faIcon"></i>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200px">
|
||||
@@ -62,7 +62,7 @@ layout("/layouts/platform.html"){
|
||||
<el-form-item label="模块名称" prop="name">
|
||||
<el-input v-model="formData.name" maxlength="8"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="模块图标" prop="icon">
|
||||
<el-form-item label="模块图片" prop="icon">
|
||||
<file-upload
|
||||
:value.sync="formData.icon"
|
||||
:upload_number="1"
|
||||
@@ -71,6 +71,10 @@ layout("/layouts/platform.html"){
|
||||
upload_result_type="url"
|
||||
></file-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="图标" prop="faIcon">
|
||||
<el-input maxlength="100" placeholder="图标" v-model="formData.faIcon"></el-input>
|
||||
<i :class="formData.faIcon" v-if="formData.faIcon"></i>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序编号" prop="sortNum">
|
||||
<el-input-number v-model="formData.sortNum" :min="1" :precision="0"></el-input-number>
|
||||
</el-form-item>
|
||||
@@ -100,14 +104,16 @@ layout("/layouts/platform.html"){
|
||||
platform: [{ required: true, message: "请填写", trigger: ["blur", "change"] }],
|
||||
name: [{ required: true, message: "请填写", trigger: ["blur", "change"] }],
|
||||
icon: [{ required: true, message: "请填写", trigger: ["blur", "change"] }],
|
||||
sortNum: [{ required: true, message: "请输入", trigger: ["blur", "change"] }]
|
||||
sortNum: [{ required: true, message: "请输入", trigger: ["blur", "change"] }],
|
||||
faIcon: [{ required: true, message: "请填写", trigger: ["blur", "change"] }]
|
||||
},
|
||||
firstMenus: [],
|
||||
tableColumns: [
|
||||
{ label: "排序编号", prop: "sortNum", sortable: true },
|
||||
{ label: "所属平台", prop: "platform", sortable: true },
|
||||
{ label: "模块名称", prop: "name" },
|
||||
{ label: "模块图标", prop: "icon" }
|
||||
{ label: "模块图标", prop: "icon" },
|
||||
{ label: "模块图标", prop: "faIcon" }
|
||||
],
|
||||
pageForm: {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="名称">
|
||||
<el-input v-model="pageForm.flowName" placeholder="名称" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
<el-button type="primary" @click="onAdd" size="small" icon="el-icon-plus">新增</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="flowName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="flowCode" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="categoryName" label="流程分类"></el-table-column>
|
||||
<el-table-column prop="version" label="版本号"></el-table-column>
|
||||
<el-table-column prop="activityStatus" label="激活状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.activityStatus===1" type="success">激活</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.activityStatus===0" type="info">挂起</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isPublish" label="发布状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.isPublish===0" type="info">未发布</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.isPublish===1" type="success">已发布</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.isPublish===9" type="info">失效</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="250px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button type="text" size="mini" @click="onDesign(row)">设计</el-button>
|
||||
<el-button type="text" size="mini" @click="onView(row)">查看</el-button>
|
||||
<el-button v-if="row.isPublish !== 1" type="text" size="mini" @click="onPublish(row)">发布</el-button>
|
||||
<el-button type="text" size="mini" @click="onDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="800px">
|
||||
<el-form :model="formData" ref="formRef" label-width="120px">
|
||||
<el-form-item label="流程定义名称" prop="flowName" :rules="{ required: true, message: '请输入流程定义名称', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowName" placeholder="请输入流程定义名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程定义编码" prop="flowCode" :rules="{ required: true, message: '请输入流程定义编码', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowCode" placeholder="请输入流程定义编码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="categoryId">
|
||||
<el-select v-model="formData.categoryId" placeholder="请选择流程分类" clearable>
|
||||
<el-option v-for="item in categoryOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,195 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.design-dialog .el-dialog__body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="是否发布">
|
||||
<el-select v-model="pageForm.isPublish" placeholder="请选择是否发布" clearable style="width: 100%">
|
||||
<el-option label="全部" :value="null"></el-option>
|
||||
<el-option label="已发布" :value="true"></el-option>
|
||||
<el-option label="未发布" :value="false"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="名称">
|
||||
<el-input v-model="pageForm.flowName" placeholder="名称" clearable></el-input>
|
||||
</search-item>
|
||||
<search-item label="编码">
|
||||
<el-input v-model="pageForm.flowCode" placeholder="编码" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<table-tool>
|
||||
<el-button type="primary" @click="onAdd" size="small" icon="el-icon-plus">新增</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
|
||||
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="flowName" label="流程定义名称"></el-table-column>
|
||||
<el-table-column prop="flowCode" label="流程定义编码"></el-table-column>
|
||||
<el-table-column prop="categoryName" label="流程分类">
|
||||
<template slot-scope="{row}">{{categoryOptions.find(item => item.id === row.category)?.categoryName}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本号"></el-table-column>
|
||||
<el-table-column prop="activityStatus" label="激活状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.activityStatus===1" type="success">激活</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.activityStatus===0" type="info">挂起</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isPublish" label="发布状态">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" v-if="row.isPublish===0" type="info">未发布</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.isPublish===1" type="success">已发布</el-tag>
|
||||
<el-tag size="mini" v-else-if="row.isPublish===9" type="info">失效</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="250px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button v-if="row.isPublish !== 1" type="text" size="mini" @click="onDesign(row)">设计</el-button>
|
||||
<el-button type="text" size="mini" @click="onView(row)">查看</el-button>
|
||||
<el-button v-if="row.isPublish !== 1" type="text" size="mini" @click="onPublish(row)">发布</el-button>
|
||||
<el-button type="text" size="mini" @click="onDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="800px">
|
||||
<el-form :model="formData" ref="formRef" label-width="120px">
|
||||
<el-form-item label="流程定义名称" prop="flowName" :rules="{ required: true, message: '请输入流程定义名称', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowName" placeholder="请输入流程定义名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程定义编码" prop="flowCode" :rules="{ required: true, message: '请输入流程定义编码', trigger: 'blur' }">
|
||||
<el-input v-model="formData.flowCode" placeholder="请输入流程定义编码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category" :rules="{ required: true, message: '请选择流程分类', trigger: 'change' }">
|
||||
<el-select v-model="formData.category" placeholder="请选择流程分类" clearable>
|
||||
<el-option v-for="item in categoryOptions" :key="item.id" :label="item.categoryName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="流程设计" :visible.sync="designVisible" fullscreen class="design-dialog">
|
||||
<div style="height: calc(100vh - 55px); width: 100%">
|
||||
<iframe v-if="designVisible" :src="designUrl" frameborder="0" height="100%" style="height: 100%; width: inherit"></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
categoryOptions: [],
|
||||
designVisible: false,
|
||||
designUrl: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onAdd() {
|
||||
this.formData = {}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onDesign(row, disabled = false) {
|
||||
this.designVisible = true
|
||||
this.iframeLoaded()
|
||||
this.designUrl = "/warm-flow-ui/index.html?id=" + row.id + "&disabled=" + disabled
|
||||
},
|
||||
|
||||
iframeLoaded() {
|
||||
// iframe监听组件内设计器保存事件
|
||||
window.onmessage = (event) => {
|
||||
switch (event.data.method) {
|
||||
case "close":
|
||||
close()
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDelete(row) {
|
||||
this.$confirm("是否确认删除流程定义编码为【" + row.flowCode + "】版本为【" + row.version + "】的数据项?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/warmFlow/definition/delete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.message)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onView(row) {},
|
||||
// 发布
|
||||
onPublish(row) {
|
||||
this.$confirm(
|
||||
"是否确认发布流程定义编码为【" + row.flowCode + "】版本为【" + row.version + "】的数据项?,发布后会将已发布流程定义改为失效!",
|
||||
"提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}
|
||||
).then(() => {
|
||||
this.$axios.post("/platform/warmFlow/definition/publish", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.message)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$axios.post("/platform/warmFlow/definition/" + (this.formData.id ? "update" : "insert"), this.formData).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.dialogVisible = false
|
||||
this.$message.success(res.message)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
listCategory() {
|
||||
this.$axios.get("/platform/warmFlow/category/list").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.categoryOptions = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
this.listCategory()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,586 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div class="task-todo-center">
|
||||
<!-- 统计数据区域 -->
|
||||
<el-row :gutter="20" class="statistics-section">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon todo-icon">
|
||||
<i class="el-icon-s-claim"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{todoCount}}</div>
|
||||
<div class="stat-label">待办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon done-icon">
|
||||
<i class="el-icon-finished"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{doneCount}}</div>
|
||||
<div class="stat-label">已办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon started-icon">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{startedCount}}</div>
|
||||
<div class="stat-label">我发起的</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<el-card shadow="never" class="filter-section">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input v-model="searchForm.keyword" placeholder="请输入关键词搜索" prefix-icon="el-icon-search" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="searchForm.processType" placeholder="所有流程类型" clearable>
|
||||
<el-option v-for="type in processTypes" :key="type.value" :label="type.label" :value="type.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">搜索</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务列表区域 -->
|
||||
<el-card shadow="never" class="task-section">
|
||||
<div slot="header" class="task-header">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="待办任务" name="todo"></el-tab-pane>
|
||||
<el-tab-pane label="已办任务" name="done"></el-tab-pane>
|
||||
<el-tab-pane label="我发起的" name="started"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 表格展示 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tasks"
|
||||
style="width: 100%"
|
||||
:key="activeTab"
|
||||
:header-cell-style="{backgroundColor: '#f5f7fa'}"
|
||||
:row-class-name="tableRowClassName"
|
||||
>
|
||||
<el-table-column prop="processInstanceName" label="流程名称" min-width="180" show-overflow-tooltip>
|
||||
<template slot-scope="{row}">
|
||||
<span class="task-title">{{row.instance_ext?.instanceName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="node_name" label="任务节点" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<div v-if="activeTab === 'todo'">{{scope.row.node_name}}</div>
|
||||
<div v-else-if="activeTab === 'done'">{{scope.row.node_name}}</div>
|
||||
<div v-else-if="activeTab === 'started'">{{scope.row.assigneeInfo}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="processDefinitionName" label="流程类型" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{scope.row.processDefinitionName || scope.row.processType}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="instance_ext.initiatorUserName" label="申请人" min-width="120">
|
||||
<template slot-scope="{row}">{{row.instance_ext?.initiatorUserName}} ({{row.instance_ext?.initiatorLoginName}})</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时间" min-width="180">
|
||||
<template slot-scope="scope">
|
||||
<div v-if="activeTab === 'todo'">
|
||||
<i class="el-icon-time"></i>
|
||||
{{scope.row.create_time}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'done'">
|
||||
<i class="el-icon-check"></i>
|
||||
{{scope.row.update_time}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'started'">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
{{scope.row.create_time}}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="activeTab === 'started'" label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getStatusType(scope.row)" size="small" effect="light">{{scope.row.status || '进行中'}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" size="mini" @click="handleTask(scope.row)">处理</el-button>
|
||||
<el-button type="info" size="mini" @click="viewTaskDetail(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态展示 -->
|
||||
<el-empty v-if="tasks.length === 0" :description="getEmptyText()"></el-empty>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="tasks.length > 0">
|
||||
<el-pagination
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="total"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务详情对话框 -->
|
||||
<el-dialog title="任务详情" :visible.sync="dialogVisible" width="600px" :close-on-click-modal="false">
|
||||
<div v-if="currentTask" class="task-detail">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="流程名称">{{currentTask.processInstanceName || currentTask.title}}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">{{currentTask.taskName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="流程类型">{{currentTask.processDefinitionName || currentTask.processType}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{currentTask.applyUserName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请部门">{{currentTask.applyUserUnitId || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{currentTask.createTime}}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.description" label="任务描述">
|
||||
<div class="description-content">{{currentTask.description}}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.taskMobileFormUrl" label="表单链接">
|
||||
<el-link type="primary" :href="currentTask.taskMobileFormUrl" target="_blank">点击查看详细表单</el-link>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" @click="handleTask(currentTask)">处理任务</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.task-todo-center {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.statistics-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-card .el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.todo-icon {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.done-icon {
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.started-icon {
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 搜索区域样式 */
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-section .el-card__body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form .el-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 任务列表区域样式 */
|
||||
.task-section .el-card__header {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 优化后的 tabs 样式 */
|
||||
.task-header .el-tabs__header {
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 15px;
|
||||
color: #606266;
|
||||
padding: 0 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item.is-active {
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__active-bar {
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 表格行样式 */
|
||||
.el-table .hover-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 优化后的分页样式 */
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination {
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev,
|
||||
.pagination-container .el-pagination .btn-next {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev:hover,
|
||||
.pagination-container .el-pagination .btn-next:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 3px;
|
||||
font-weight: normal;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__jump {
|
||||
margin-left: 15px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input {
|
||||
width: 50px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input .el-input__inner {
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 任务详情样式 */
|
||||
.task-detail .el-descriptions {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.description-content {
|
||||
background-color: #f5f7fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
// 统计数据
|
||||
todoCount: 0,
|
||||
doneCount: 0,
|
||||
startedCount: 0,
|
||||
|
||||
// 搜索表单
|
||||
searchForm: {
|
||||
keyword: "",
|
||||
processType: ""
|
||||
},
|
||||
|
||||
// 流程类型选项
|
||||
processTypes: [],
|
||||
|
||||
// 任务列表
|
||||
tasks: [],
|
||||
loading: false,
|
||||
|
||||
// 分页
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// 标签页
|
||||
activeTab: "todo",
|
||||
|
||||
// 当前任务
|
||||
currentTask: null,
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
totalPages() {
|
||||
return Math.ceil(this.total / this.pageSize)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.initData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
await Promise.all([this.getStatistics(), this.getProcessTypes(), this.getTasks()])
|
||||
},
|
||||
|
||||
// 获取统计数据
|
||||
async getStatistics() {
|
||||
try {
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/statistics")
|
||||
if (res.code === 0) {
|
||||
const { todoCount, doneCount, startedCount } = res.data
|
||||
this.todoCount = todoCount
|
||||
this.doneCount = doneCount
|
||||
this.startedCount = startedCount
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取统计数据失败")
|
||||
console.error("获取统计数据失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取流程类型
|
||||
async getProcessTypes() {
|
||||
try {
|
||||
const res = await $.post("/platform/workflow/todoCenter/process/types")
|
||||
if (res.code === 0) {
|
||||
this.processTypes = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取流程类型失败")
|
||||
console.error("获取流程类型失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取任务列表
|
||||
async getTasks() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
type: this.activeTab,
|
||||
keyword: this.searchForm.keyword,
|
||||
processType: this.searchForm.processType
|
||||
}
|
||||
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/" + this.activeTab, params)
|
||||
if (res.code === 0) {
|
||||
this.tasks = res.data.list
|
||||
this.total = res.data.totalCount
|
||||
this.dialogVisible = false
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取任务列表失败")
|
||||
console.error("获取任务列表失败:", error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 处理标签页点击
|
||||
handleTabClick(tab) {
|
||||
this.activeTab = tab.name
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 搜索
|
||||
search() {
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
this.getStatistics()
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
reset() {
|
||||
this.searchForm = {
|
||||
keyword: "",
|
||||
processType: ""
|
||||
}
|
||||
this.search()
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 查看任务详情
|
||||
viewTaskDetail(task) {
|
||||
this.currentTask = task
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 处理任务
|
||||
async handleTask(task) {
|
||||
if (!task) return
|
||||
|
||||
// 如果有表单链接,直接跳转
|
||||
if (task.taskMobileFormUrl) {
|
||||
window.open(task.taskMobileFormUrl, "_blank")
|
||||
return
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态对应的类型
|
||||
getStatusType(task) {
|
||||
if (!task.status) return "info"
|
||||
|
||||
switch (task.status) {
|
||||
case "审批中":
|
||||
return "primary"
|
||||
case "已完成":
|
||||
return "success"
|
||||
case "已通过":
|
||||
return "success"
|
||||
case "已拒绝":
|
||||
return "danger"
|
||||
default:
|
||||
return "info"
|
||||
}
|
||||
},
|
||||
|
||||
// 获取空状态文本
|
||||
getEmptyText() {
|
||||
switch (this.activeTab) {
|
||||
case "todo":
|
||||
return "您当前没有需要办理的任务,辛苦了"
|
||||
case "done":
|
||||
return "您当前没有已办理的任务记录"
|
||||
case "started":
|
||||
return "您当前没有发起的流程"
|
||||
default:
|
||||
return "暂无数据"
|
||||
}
|
||||
},
|
||||
|
||||
// 表格行类名
|
||||
tableRowClassName({ row, rowIndex }) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,115 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="流程实例">
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="onOpen">新增</el-button>
|
||||
</table-tool>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
@sort-change="pageOrder"
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
style="width: 100%"
|
||||
v-loading="tableLoading"
|
||||
>
|
||||
<el-table-column
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:sortable="column.sortable"
|
||||
:width="column.width"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
></el-table-column>
|
||||
<el-table-column label="操作" width="150px" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" @click="onEdit(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" @click="onDelete(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="60%">
|
||||
<el-form ref="formRef" :model="formData" label-width="80px">
|
||||
<el-form-item label="名称" prop="name" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]">
|
||||
<el-input v-model="formData.name" maxlength="100" placeholder="请输入名称" show-word-limit></el-input></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="表达式" prop="expression" :rules="[{ required: true, message: '请输入表达式', trigger: 'blur' }]">
|
||||
<el-input v-model="formData.expression" maxlength="255" placeholder="请输入表达式" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSave">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
tableColumns: [
|
||||
{ label: "名称", prop: "name" },
|
||||
{ label: "表达式", prop: "expression" },
|
||||
{ label: "创建时间", prop: "createdBy" }
|
||||
],
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSave() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$axios.post("/platform/workflow/expression/" + (this.formData.id ? "update" : "save"), this.formData).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.dialogVisible = false
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onOpen() {
|
||||
this.dialogVisible = true
|
||||
this.formData = {}
|
||||
},
|
||||
onEdit(row) {
|
||||
this.dialogVisible = true
|
||||
this.formData = { ...row }
|
||||
},
|
||||
onDelete(id){
|
||||
this.$confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$axios.post('/platform/workflow/expression/delete', {id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,48 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>1流程设计器 - JeecgBoot低代码平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" style="padding-top: 1px">
|
||||
<vue-bpmn-designer ref="FlowDesigner" :is-add="true" token="" style="overflow: hidden"></vue-bpmn-designer>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
window._CONFIG = {
|
||||
domianURL: "http://localhost:8080/"
|
||||
}
|
||||
</script>
|
||||
<script src="https://api3.boot.jeecg.com/desflow/vue/polyfill.min.js?v=1749544752.10"></script>
|
||||
<script src="https://api3.boot.jeecg.com/desflow/vue/vue.min.js?v=1749544752.10"></script>
|
||||
<script src="https://api3.boot.jeecg.com/desflow/moment/moment-with-locales.min.js?v=1749544752.10"></script>
|
||||
<script src="https://api3.boot.jeecg.com/desflow/antd/antd-with-locales.js?v=1749544752.10"></script>
|
||||
<link rel="stylesheet" href="https://api3.boot.jeecg.com/desflow/antd/antd.css?v=1749544752.10" />
|
||||
<link rel="stylesheet" href="https://api3.boot.jeecg.com/desflow/lib/index.css?v=1749544752.10" />
|
||||
<script src="/assets/platform/plugins/bpmn-designer/index.umd.min.js?v=1.0.1"></script>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
processId: "${modelId!}",
|
||||
category: "${category!}"
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
mounted: function () {
|
||||
console.log(this.processId)
|
||||
this.$refs.FlowDesigner.show({
|
||||
processId: this.processId,
|
||||
typeid: this.category
|
||||
})
|
||||
console.log(this.processId)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script></script>
|
||||
</html>
|
||||
@@ -0,0 +1,229 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="流程定义">
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="openDesigner()">新建流程模型</el-button>
|
||||
</table-tool>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
@sort-change="pageOrder"
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
style="width: 100%"
|
||||
v-loading="tableLoading"
|
||||
>
|
||||
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
|
||||
<el-table-column
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:sortable="column.sortable"
|
||||
:width="column.width"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
>
|
||||
<template scope="{row}" v-if="column.prop=='deploymentId'">
|
||||
<el-tag v-if="row.deploymentId" size="mini">已部署</el-tag>
|
||||
<el-tag v-else size="mini">未部署</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="400px" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" @click="openDesigner(scope.row)">设计</el-button>
|
||||
<el-button size="mini" type="primary" @click="deploy(scope.row.id)">部署</el-button>
|
||||
<el-button size="mini" type="info" @click="showHistory(scope.row)">历史版本</el-button>
|
||||
<el-button size="mini" type="danger" @click="onDelete(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<el-dialog title="流程设计" :visible.sync="dialogVisible" fullscreen>
|
||||
<div style="height: calc(100vh - 115px)">
|
||||
<iframe
|
||||
:src="designerUrl"
|
||||
v-if="dialogVisible"
|
||||
frameborder="0"
|
||||
scrolling="auto"
|
||||
allowtransparency="true"
|
||||
allowfullscreen="true"
|
||||
width="100%"
|
||||
height="100%"
|
||||
></iframe>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 历史版本对话框 -->
|
||||
<el-dialog title="历史版本" :visible.sync="historyDialogVisible" width="60%">
|
||||
<el-table :data="historyVersions" style="width: 100%" v-loading="historyLoading">
|
||||
<el-table-column label="版本号" prop="version" width="100" align="center"></el-table-column>
|
||||
<el-table-column label="名称" prop="name"></el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime" width="180"></el-table-column>
|
||||
<el-table-column label="最后更新时间" prop="lastUpdateTime" width="180"></el-table-column>
|
||||
<el-table-column label="操作" width="200" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" @click="viewHistoryXml(scope.row)">查看XML</el-button>
|
||||
<el-button size="mini" type="success" @click="restoreVersion(scope.row)" :disabled="currentModel.version === scope.row.version">恢复</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看XML对话框 -->
|
||||
<el-dialog title="模型XML" :visible.sync="xmlDialogVisible" width="80%">
|
||||
<pre v-if="modelXml" style="max-height: 500px; overflow: auto;">{{ modelXml }}</pre>
|
||||
<div v-else>加载中...</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
tableColumns: [
|
||||
{ label: "名称", prop: "name" },
|
||||
{ label: "Key", prop: "key" },
|
||||
{ label: "分类", prop: "category" },
|
||||
{ label: "是否部署", prop: "deploymentId" },
|
||||
{ label: "版本", prop: "version" },
|
||||
{ label: "创建时间", prop: "createTime" }
|
||||
],
|
||||
dialogVisible: false,
|
||||
designerUrl: "/platform/workflow/process/definition' + modelId",
|
||||
historyDialogVisible: false,
|
||||
historyVersions: [],
|
||||
historyLoading: false,
|
||||
currentModel: {},
|
||||
xmlDialogVisible: false,
|
||||
modelXml: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openDesigner(row = null) {
|
||||
this.dialogVisible = true
|
||||
if (row) {
|
||||
this.designerUrl = "/platform/workflow/designer?modelId=" + row.id + "&category=" + row.category
|
||||
} else {
|
||||
this.designerUrl = "/platform/workflow/designer?modelId=&category="
|
||||
}
|
||||
},
|
||||
deploy(modelId) {
|
||||
this.$confirm("确定要部署吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$axios.post("/platform/workflow/model/deploy", { modelId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
onDelete(modelId) {
|
||||
this.$confirm("确定要删除吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$axios.post("/platform/workflow/model/delete", { modelId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
// 显示历史版本
|
||||
showHistory(row) {
|
||||
this.currentModel = row;
|
||||
this.historyDialogVisible = true;
|
||||
this.historyLoading = true;
|
||||
|
||||
// 调用后端接口获取历史版本
|
||||
this.$axios.get("/platform/workflow/model/history", {
|
||||
params: { modelKey: row.key }
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.historyVersions = res.data;
|
||||
} else {
|
||||
this.$message.error(res.msg || "获取历史版本失败");
|
||||
}
|
||||
this.historyLoading = false;
|
||||
}).catch(() => {
|
||||
this.historyLoading = false;
|
||||
this.$message.error("获取历史版本失败");
|
||||
});
|
||||
},
|
||||
// 查看历史版本XML
|
||||
viewHistoryXml(row) {
|
||||
this.xmlDialogVisible = true;
|
||||
this.modelXml = "加载中...";
|
||||
|
||||
this.$axios.get("/platform/workflow/model/history/xml", {
|
||||
params: { modelId: row.id }
|
||||
}).then(res => {
|
||||
this.modelXml = res.data;
|
||||
}).catch(() => {
|
||||
this.modelXml = "加载失败";
|
||||
this.$message.error("获取模型XML失败");
|
||||
});
|
||||
},
|
||||
// 恢复历史版本
|
||||
restoreVersion(row) {
|
||||
this.$confirm('确定要恢复到版本'+row.version+'吗?', "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
// 获取历史版本的XML
|
||||
this.$axios.get("/platform/workflow/model/history/xml", {
|
||||
params: { modelId: row.id }
|
||||
}).then(xml => {
|
||||
// 使用历史版本的XML保存为新版本
|
||||
this.$axios.post("/platform/workflow/model/save", {
|
||||
modelId: this.currentModel.id,
|
||||
processName: this.currentModel.name,
|
||||
processKey: this.currentModel.key,
|
||||
category: this.currentModel.category,
|
||||
bpmnXml: xml
|
||||
}).then(res => {
|
||||
if (res.success) {
|
||||
this.$message.success("版本恢复成功");
|
||||
this.historyDialogVisible = false;
|
||||
this.pageData();
|
||||
} else {
|
||||
this.$message.error(res.msg || "版本恢复失败");
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.error("版本恢复失败");
|
||||
});
|
||||
}).catch(() => {
|
||||
this.$message.error("获取历史版本XML失败");
|
||||
});
|
||||
}).catch(() => {});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,123 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="流程定义"></table-tool>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
@sort-change="pageOrder"
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
style="width: 100%"
|
||||
v-loading="tableLoading"
|
||||
>
|
||||
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
|
||||
<el-table-column
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
:sortable="column.sortable"
|
||||
:width="column.width"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
v-for="column in tableColumns"
|
||||
>
|
||||
<template scope="{row}" v-if="column.prop=='suspended'">
|
||||
<el-tag v-if="row.suspended" size="mini" type="info">已挂起</el-tag>
|
||||
<el-tag v-else size="mini" type="success">已激活</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200px" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" @click="openVersion(scope.row)">版本管理</el-button>
|
||||
<el-button size="mini" type="primary" @click="openNodeConfig(scope.row)">配置</el-button>
|
||||
<!-- <el-button size="mini" type="primary" @click="activate(scope.row.id)">激活</el-button>-->
|
||||
<!-- <el-button size="mini" type="danger" @click="suspend(scope.row.id)">挂起</el-button>-->
|
||||
<!-- <el-button size="mini" type="danger" @click="onDelete(scope.row.deploymentId)">删除</el-button>-->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<el-dialog title="流程设计" :visible.sync="dialogVisible" width="80%">
|
||||
<iframe
|
||||
:src="designerUrl"
|
||||
frameborder="0"
|
||||
scrolling="auto"
|
||||
allowtransparency="true"
|
||||
allowfullscreen="true"
|
||||
width="100%"
|
||||
height="600px"
|
||||
></iframe>
|
||||
</el-dialog>
|
||||
|
||||
<process-version ref="versionRef"></process-version>
|
||||
<node-config ref="nodeConfigRef"></node-config>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<!--#include('processVersion.js'){}#-->
|
||||
<!--#include('nodeConfig.js'){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
components: {
|
||||
"process-version": processVersion,
|
||||
"node-config": nodeConfig
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableColumns: [
|
||||
{ label: "ID", prop: "id" },
|
||||
{ label: "名称", prop: "name" },
|
||||
{ label: "Key", prop: "key" },
|
||||
{ label: "分类", prop: "category" },
|
||||
{ label: "状态", prop: "suspended" },
|
||||
{ label: "版本", prop: "version" },
|
||||
{ label: "创建时间", prop: "createTime" }
|
||||
],
|
||||
dialogVisible: false,
|
||||
designerUrl: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openVersion(row) {
|
||||
this.$refs.versionRef.onOpen(row.key)
|
||||
},
|
||||
openNodeConfig(row){
|
||||
this.$refs.nodeConfigRef.onOpen(row.id)
|
||||
},
|
||||
|
||||
|
||||
deploy(modelId) {
|
||||
this.$confirm("确定要部署吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$axios.post("/platform/workflow/process/definition/deploy", { modelId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("部署成功")
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,93 @@
|
||||
const nodeConfig = {
|
||||
template: /*language=HTML*/ `
|
||||
<el-dialog title="节点配置" :visible.sync="visible" fullscreen append-to-body>
|
||||
<el-table :data="tableData" border>
|
||||
<el-table-column prop="nodeName" label="节点名称" />
|
||||
<el-table-column prop="nodeId" label="节点编码" />
|
||||
<el-table-column prop="pcFormUrl" label="PC表单地址" />
|
||||
<el-table-column prop="mobileFormUrl" label="移动端表单地址" />
|
||||
<el-table-column prop="allowSelectNextUser" label="选择下一步处理人" />
|
||||
<el-table-column prop="allowSelectCcUser" label="是否允许选择抄送人" />
|
||||
<el-table-column prop="allowTransfer" label="是否允许转办" />
|
||||
<el-table-column prop="allowReject" label="是否允许驳回" />
|
||||
<el-table-column label="操作" width="150px">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="onEdit(scope.row)">配置</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog title="节点配置" :visible.sync="editVisible" width="700px" append-to-body>
|
||||
<el-form ref="form" :model="formData" label-width="150px">
|
||||
<el-form-item label="节点名称">
|
||||
<el-input v-model="formData.nodeName" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="节点编码">
|
||||
<el-input v-model="formData.nodeId" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="PC表单地址" prop="pcFormUrl">
|
||||
<el-input v-model="formData.pcFormUrl" />
|
||||
</el-form-item>
|
||||
<el-form-item label="移动端表单地址" prop="mobileFormUrl">
|
||||
<el-input v-model="formData.mobileFormUrl" />
|
||||
</el-form-item>
|
||||
<el-form-item label="选择下一步处理人" prop="allowSelectNextUser">
|
||||
<el-switch v-model="formData.allowSelectNextUser" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否允许选择抄送人" prop="allowSelectCcUser">
|
||||
<el-switch v-model="formData.allowSelectCcUser" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否允许转办" prop="allowTransfer">
|
||||
<el-switch v-model="formData.allowTransfer" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否允许驳回" prop="allowReject">
|
||||
<el-switch v-model="formData.allowReject" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="editVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSave">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</el-dialog>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
title: "标题",
|
||||
processDefinitionId: null,
|
||||
tableData: [],
|
||||
|
||||
editVisible: false,
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(processDefinitionId) {
|
||||
this.visible = true
|
||||
this.processDefinitionId = processDefinitionId
|
||||
this.loadData()
|
||||
},
|
||||
loadData() {
|
||||
this.$axios.post("/platform/workflow/process/definition/userTasksConfig/" + this.processDefinitionId).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
onEdit(row) {
|
||||
this.editVisible = true
|
||||
this.formData = { ...row }
|
||||
},
|
||||
onSave() {
|
||||
this.$axios.post("/platform/workflow/process/definition/saveUserTasksConfig", this.formData).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.editVisible = false
|
||||
this.loadData()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
const processVersion = {
|
||||
template: /*language=HTML*/ `
|
||||
<el-dialog title="版本信息" :visible.sync="visible" fullscreen>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column label="序号" type="index" width="80px"></el-table-column>
|
||||
<el-table-column label="名称" prop="name"></el-table-column>
|
||||
<el-table-column label="key" prop="key"></el-table-column>
|
||||
<el-table-column label="版本" prop="version"></el-table-column>
|
||||
<el-table-column label="状态" prop="suspended">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.suspended" size="mini" type="info">挂起</el-tag>
|
||||
<el-tag v-else size="mini" type="success">激活</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="部署状态" prop="deploymentValid">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.deploymentValid" size="mini" type="success">有效</el-tag>
|
||||
<el-tag v-else size="mini" type="danger">无效</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="部署时间" prop="deploymentTime">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.deploymentTime ? new Date(scope.row.deploymentTime).toLocaleString() : '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="300px">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click="openDiagram(scope.row.id)">流程图</el-button>
|
||||
<el-button size="mini" type="text" v-if="scope.row.suspended" @click="activate(scope.row.id)">
|
||||
激活
|
||||
</el-button>
|
||||
<el-button size="mini" type="text" v-if="!scope.row.suspended" @click="suspend(scope.row.id)">
|
||||
挂起
|
||||
</el-button>
|
||||
<el-button size="mini" type="text" @click="onDelete(scope.row.deploymentId)">删除</el-button>
|
||||
<el-button size="mini" type="text" v-if="!scope.row.deploymentValid" @click="cleanupInvalidDefinition(scope.row.id)">清理</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog title="流程图" :visible.sync="diagramVisible" width="70%" append-to-body>
|
||||
<img :src="diagramUrl" alt="">
|
||||
</el-dialog>
|
||||
|
||||
</el-dialog>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
tableData: [],
|
||||
processKey: null,
|
||||
diagramVisible: false,
|
||||
diagramUrl: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(processKey) {
|
||||
this.visible = true
|
||||
this.processKey = processKey
|
||||
this.loadData()
|
||||
},
|
||||
loadData() {
|
||||
this.$axios.post("/platform/workflow/process/definition/versions", { processKey: this.processKey }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
activate(processDefinitionId) {
|
||||
this.$confirm("确定要激活吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/workflow/process/definition/activate/" + processDefinitionId).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("激活成功")
|
||||
this.loadData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
suspend(processDefinitionId) {
|
||||
this.$confirm("确定要挂起吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/workflow/process/definition/suspend/" + processDefinitionId).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("挂起成功")
|
||||
this.loadData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
onDelete(deploymentId) {
|
||||
this.$confirm("确定要删除吗?删除后将无法恢复", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
// 添加级联删除选项
|
||||
this.$confirm("是否级联删除?(级联删除会同时删除相关的流程实例)", "提示", {
|
||||
confirmButtonText: "是",
|
||||
cancelButtonText: "否",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
// 级联删除
|
||||
this.$axios.post("/platform/workflow/process/definition/delete/" + deploymentId + "?cascade=true").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.loadData()
|
||||
} else {
|
||||
this.$message.error(res.msg || "删除失败")
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
// 非级联删除
|
||||
this.$axios.post("/platform/workflow/process/definition/delete/" + deploymentId + "?cascade=false").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.loadData()
|
||||
} else {
|
||||
this.$message.error(res.msg || "删除失败")
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
cleanupInvalidDefinition(processDefinitionId) {
|
||||
this.$confirm("确定要清理此无效流程定义吗?此操作将从数据库中删除此记录。", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/workflow/process/definition/cleanup/" + processDefinitionId).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("清理成功")
|
||||
this.loadData()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
openDiagram(processDefinitionId) {
|
||||
this.diagramVisible = true
|
||||
this.diagramUrl = "/platform/workflow/process/definition/diagram/" + processDefinitionId
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div class="task-todo-center">
|
||||
<!-- 统计数据区域 -->
|
||||
<el-row :gutter="20" class="statistics-section">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon todo-icon">
|
||||
<i class="el-icon-s-claim"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{todoCount}}</div>
|
||||
<div class="stat-label">待办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon done-icon">
|
||||
<i class="el-icon-finished"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{doneCount}}</div>
|
||||
<div class="stat-label">已办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon started-icon">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{startedCount}}</div>
|
||||
<div class="stat-label">我发起的</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<el-card shadow="never" class="filter-section">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input v-model="searchForm.keyword" placeholder="请输入关键词搜索" prefix-icon="el-icon-search" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="searchForm.category" placeholder="所有流程类型" clearable>
|
||||
<el-option v-for="type in processTypes" :key="type.value" :label="type.label" :value="type.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">搜索</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务列表区域 -->
|
||||
<el-card shadow="never" class="task-section">
|
||||
<div slot="header" class="task-header">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="待办任务" name="todo"></el-tab-pane>
|
||||
<el-tab-pane label="已办任务" name="done"></el-tab-pane>
|
||||
<el-tab-pane label="我发起的" name="started"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 表格展示 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tasks"
|
||||
style="width: 100%"
|
||||
:key="activeTab"
|
||||
:header-cell-style="{backgroundColor: '#f5f7fa'}"
|
||||
:row-class-name="tableRowClassName"
|
||||
>
|
||||
<el-table-column prop="processInstanceName" label="流程名称" min-width="180" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span class="task-title">{{scope.row.processInstanceName || scope.row.title}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskName" label="任务节点" min-width="150" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="processDefinitionName" label="流程类型" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{scope.row.processDefinitionName || scope.row.processType}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="applyUserName" label="申请人" min-width="120"></el-table-column>
|
||||
<el-table-column label="时间" min-width="180">
|
||||
<template slot-scope="scope">
|
||||
<div v-if="activeTab === 'todo'">
|
||||
<i class="el-icon-time"></i>
|
||||
{{scope.row.createTime}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'done'">
|
||||
<i class="el-icon-check"></i>
|
||||
{{scope.row.endTime}}
|
||||
</div>
|
||||
<div v-else-if="activeTab === 'started'">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
{{scope.row.startTime}}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="activeTab === 'started'" label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getStatusType(scope.row)" size="small" effect="light">{{scope.row.status || '进行中'}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" size="mini" @click="handleTask(scope.row)">处理</el-button>
|
||||
<el-button type="info" size="mini" @click="viewTaskDetail(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态展示 -->
|
||||
<el-empty v-if="tasks.length === 0" :description="getEmptyText()"></el-empty>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="tasks.length > 0">
|
||||
<el-pagination
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="total"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务详情对话框 -->
|
||||
<el-dialog title="任务详情" :visible.sync="dialogVisible" width="600px" :close-on-click-modal="false">
|
||||
<div v-if="currentTask" class="task-detail">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="流程名称">{{currentTask.processInstanceName || currentTask.title}}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">{{currentTask.taskName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="流程类型">{{currentTask.processDefinitionName || currentTask.processType}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{currentTask.applyUserName || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请部门">{{currentTask.applyUserUnitId || '-'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{currentTask.createTime}}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.description" label="任务描述">
|
||||
<div class="description-content">{{currentTask.description}}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="currentTask.taskMobileFormUrl" label="表单链接">
|
||||
<el-link type="primary" :href="currentTask.taskMobileFormUrl" target="_blank">点击查看详细表单</el-link>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
<el-button v-if="activeTab === 'todo'" type="primary" @click="handleTask(currentTask)">处理任务</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.task-todo-center {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.statistics-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-card .el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.todo-icon {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.done-icon {
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.started-icon {
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 搜索区域样式 */
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-section .el-card__body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form .el-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 任务列表区域样式 */
|
||||
.task-section .el-card__header {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 优化后的 tabs 样式 */
|
||||
.task-header .el-tabs__header {
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 15px;
|
||||
color: #606266;
|
||||
padding: 0 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item.is-active {
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__active-bar {
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 表格行样式 */
|
||||
.el-table .hover-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 优化后的分页样式 */
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination {
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev,
|
||||
.pagination-container .el-pagination .btn-next {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
padding: 0 10px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .btn-prev:hover,
|
||||
.pagination-container .el-pagination .btn-next:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li {
|
||||
background-color: #f4f6f8;
|
||||
color: #606266;
|
||||
border-radius: 4px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 3px;
|
||||
font-weight: normal;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li:hover {
|
||||
color: var(--color-primary);
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pager li.active {
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__jump {
|
||||
margin-left: 15px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input {
|
||||
width: 50px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination .el-pagination__editor.el-input .el-input__inner {
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 任务详情样式 */
|
||||
.task-detail .el-descriptions {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.description-content {
|
||||
background-color: #f5f7fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
// 统计数据
|
||||
todoCount: 0,
|
||||
doneCount: 0,
|
||||
startedCount: 0,
|
||||
|
||||
// 搜索表单
|
||||
searchForm: {
|
||||
keyword: "",
|
||||
category: ""
|
||||
},
|
||||
|
||||
// 流程类型选项
|
||||
processTypes: [],
|
||||
|
||||
// 任务列表
|
||||
tasks: [],
|
||||
loading: false,
|
||||
|
||||
// 分页
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
// 标签页
|
||||
activeTab: "todo",
|
||||
|
||||
// 当前任务
|
||||
currentTask: null,
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
totalPages() {
|
||||
return Math.ceil(this.total / this.pageSize)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.initData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
await Promise.all([this.getStatistics(), this.getProcessTypes(), this.getTasks()])
|
||||
},
|
||||
|
||||
// 获取统计数据
|
||||
async getStatistics() {
|
||||
try {
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/statistics")
|
||||
if (res.code === 0) {
|
||||
const { todoCount, doneCount, startedCount } = res.data
|
||||
this.todoCount = todoCount
|
||||
this.doneCount = doneCount
|
||||
this.startedCount = startedCount
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取统计数据失败")
|
||||
console.error("获取统计数据失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取流程类型
|
||||
async getProcessTypes() {
|
||||
try {
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/category")
|
||||
if (res.code === 0) {
|
||||
this.processTypes = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取流程类型失败")
|
||||
console.error("获取流程类型失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取任务列表
|
||||
async getTasks() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
type: this.activeTab,
|
||||
keyword: this.searchForm.keyword,
|
||||
category: this.searchForm.category
|
||||
}
|
||||
|
||||
const res = await $.post("/platform/warmFlow/todoCenter/" + this.activeTab, params)
|
||||
if (res.code === 0) {
|
||||
this.tasks = res.data.list
|
||||
this.total = res.data.totalCount
|
||||
this.dialogVisible = false
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取任务列表失败")
|
||||
console.error("获取任务列表失败:", error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 处理标签页点击
|
||||
handleTabClick(tab) {
|
||||
this.activeTab = tab.name
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 搜索
|
||||
search() {
|
||||
this.currentPage = 1
|
||||
this.getTasks()
|
||||
this.getStatistics()
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
reset() {
|
||||
this.searchForm = {
|
||||
keyword: "",
|
||||
category: ""
|
||||
}
|
||||
this.search()
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 查看任务详情
|
||||
viewTaskDetail(task) {
|
||||
this.currentTask = task
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 处理任务
|
||||
async handleTask(task) {
|
||||
if (!task) return
|
||||
|
||||
// 如果有表单链接,直接跳转
|
||||
if (task.taskMobileFormUrl) {
|
||||
window.open(task.taskMobileFormUrl, "_blank")
|
||||
return
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态对应的类型
|
||||
getStatusType(task) {
|
||||
if (!task.status) return "info"
|
||||
|
||||
switch (task.status) {
|
||||
case "审批中":
|
||||
return "primary"
|
||||
case "已完成":
|
||||
return "success"
|
||||
case "已通过":
|
||||
return "success"
|
||||
case "已拒绝":
|
||||
return "danger"
|
||||
default:
|
||||
return "info"
|
||||
}
|
||||
},
|
||||
|
||||
// 获取空状态文本
|
||||
getEmptyText() {
|
||||
switch (this.activeTab) {
|
||||
case "todo":
|
||||
return "您当前没有需要办理的任务,辛苦了"
|
||||
case "done":
|
||||
return "您当前没有已办理的任务记录"
|
||||
case "started":
|
||||
return "您当前没有发起的流程"
|
||||
default:
|
||||
return "暂无数据"
|
||||
}
|
||||
},
|
||||
|
||||
// 表格行类名
|
||||
tableRowClassName({ row, rowIndex }) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<div id="approvalApp" v-cloak>
|
||||
<snaker-flow
|
||||
:task_id="taskId"
|
||||
:instance_id="instanceId"
|
||||
:business_id="businessId"
|
||||
:pjax_urls="{
|
||||
applicationInfo: '/platform/article/write/index_',
|
||||
taskForm: '/platform/article/write/index_'
|
||||
}"
|
||||
:pjax_config="{
|
||||
push: false,
|
||||
replace: false,
|
||||
timeout: 10000
|
||||
}"
|
||||
></snaker-flow>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#approvalApp",
|
||||
data() {
|
||||
return {
|
||||
taskId: null,
|
||||
instanceId: null,
|
||||
businessId: null,
|
||||
formConfig: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTaskSubmitted() {},
|
||||
handleCancel() {
|
||||
window.history.back()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.taskId = new URLSearchParams(window.location.search).get("taskId")
|
||||
this.instanceId = new URLSearchParams(window.location.search).get("instanceId")
|
||||
this.businessId = new URLSearchParams(window.location.search).get("businessId")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<div id="article_branch_union_approval_form">
|
||||
<el-form :model="formData" ref="approvalFormRef" label-position="left" label-width="80px">
|
||||
<el-form-item label="审批意见" prop="approvalOpinion"
|
||||
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<user-opinion-textarea v-model="formData.approvalOpinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="电子签名" prop="approvalSignature" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">-->
|
||||
<!-- <pc-signature v-model="formData.approvalSignature"></pc-signature>-->
|
||||
<!-- </el-form-item>-->
|
||||
</el-form>
|
||||
<snaker-flow-task-form-action @task-action="handleTaskAction"
|
||||
@cancel="handleCancel">
|
||||
</snaker-flow-task-form-action>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#article_branch_union_approval_form",
|
||||
data() {
|
||||
return {
|
||||
taskId: GetQueryString("taskId"),
|
||||
formData: {
|
||||
approval: "",
|
||||
approvalOpinion: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTaskAction(val) {
|
||||
console.log(val)
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify(val)
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("操作成功")
|
||||
// 发送完成消息
|
||||
window.parent.postMessage({
|
||||
type: "task-complete"
|
||||
}, "*")
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCancel(val) {
|
||||
console.log(val)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
+29
-20
@@ -36,11 +36,17 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column prop="unitName" label="投稿人单位"></el-table-column>
|
||||
<el-table-column prop="unionName" label="投稿人工会"></el-table-column>
|
||||
<el-table-column prop="submitTime" label="投稿时间"></el-table-column>
|
||||
<el-table-column label="当前节点" prop="processInstanceNodeName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" fixed="right">
|
||||
<el-table-column prop="taskName" label="当前节点"></el-table-column>
|
||||
<el-table-column prop="instanceState" label="流程状态">
|
||||
<template slot-scope="{row}">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200px" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button @click="onOpen(row.id)" size="mini" type="primary">查看</el-button>
|
||||
<el-button v-if="row.processInstanceTaskStatus==='ACTIVE'" @click="openApproval(row)" size="mini" type="primary">
|
||||
<el-button v-if="row.taskState === 10" @click="openApproval(row)" size="mini" type="primary">
|
||||
审核
|
||||
</el-button>
|
||||
<el-button
|
||||
@@ -57,19 +63,25 @@ layout("/layouts/platform.html"){
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<template #public>
|
||||
<el-dialog title="" :visible.sync="showApprovalForm" width="90%">
|
||||
<info ref="infoRef"></info>
|
||||
<el-row type="flex">
|
||||
<flow-form-button :task_id="taskId"></flow-form-button>
|
||||
|
||||
<!-- <el-button plain @click="$refs.guava.index()">取消</el-button>-->
|
||||
<!-- <el-button type="danger" @click="doApproval('BACK')">退回修改</el-button>-->
|
||||
<!-- <el-button type="primary" @click="doApproval('PASS')">同意</el-button>-->
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
|
||||
<template #public>
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{formData.processInstanceNodeName}}</div>
|
||||
<el-form :model="formData" ref="approvalFormRef" label-position="left" label-width="80px">
|
||||
<el-form-item label="审核意见" prop="approvalOpinion" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<user-opinion-textarea v-model="formData.approvalOpinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row type="flex" justify="end">
|
||||
<el-button plain @click="$refs.guava.index()">取消</el-button>
|
||||
<el-button type="danger" @click="doApproval('BACK')">退回修改</el-button>
|
||||
<el-button type="primary" @click="doApproval('PASS')">同意</el-button>
|
||||
<el-row type="flex">
|
||||
<flow-form-button :task_id="taskId"></flow-form-button>
|
||||
|
||||
<!-- <el-button plain @click="$refs.guava.index()">取消</el-button>-->
|
||||
<!-- <el-button type="danger" @click="doApproval('BACK')">退回修改</el-button>-->
|
||||
<!-- <el-button type="primary" @click="doApproval('PASS')">同意</el-button>-->
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
@@ -89,7 +101,8 @@ layout("/layouts/platform.html"){
|
||||
pageForm: {
|
||||
approval: false
|
||||
},
|
||||
showApprovalForm: false
|
||||
showApprovalForm: false,
|
||||
taskId: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -100,11 +113,7 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
},
|
||||
openApproval(row) {
|
||||
this.$refs.guava.public(() => {
|
||||
this.$refs.infoRef.onOpen(row.id)
|
||||
this.formData = row.approvalParam
|
||||
this.showApprovalForm = true
|
||||
})
|
||||
window.open("/flow/common/approval/form?taskId=" + row.taskId + "&instanceId=" + row.instanceId + "&businessId=" + row.businessNo)
|
||||
},
|
||||
doApproval(approvalType) {
|
||||
this.formData.bpmTaskApprovalType = approvalType
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<div id="article-full-form">
|
||||
<el-descriptions :column="2" border class="flow-task-form">
|
||||
<el-descriptions-item label="投稿人姓名">{{ viewData.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人工号">{{ viewData.loginName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人单位">{{ viewData.unitName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人工会">{{ viewData.unionName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人联系方式">{{ viewData.mobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="稿件修改人联系方式">{{ viewData.mobile2 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿标题" :span="2">{{ viewData.title }}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿说明" :span="2">{{ viewData.excerpt }}</el-descriptions-item>
|
||||
<el-descriptions-item label="稿件" :span="2">
|
||||
<file-preview :files="viewData.files" complete_result></file-preview>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<template v-for="task in doneTasks">
|
||||
<div class="task-panel mt10">
|
||||
<div class="task-panel-header">
|
||||
{{ task.displayName }}
|
||||
</div>
|
||||
<el-descriptions border class="flow-task-form" :column="2" :key="task.id">
|
||||
<el-descriptions-item label="办理用户">{{ task.operator }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#article-full-form",
|
||||
data() {
|
||||
return {
|
||||
businessId: GetQueryString("businessId"),
|
||||
instanceId: GetQueryString("instanceId"),
|
||||
viewData: {},
|
||||
doneTasks: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
info() {
|
||||
this.$axios.post("/platform/article/common/info", { id: this.businessId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.viewData = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
getDoneTasks() {
|
||||
this.$axios.post("/flow/common/doneTasks", { instanceId: this.instanceId }).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.doneTasks = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.info()
|
||||
this.getDoneTasks()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.task-panel{
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-panel-header{
|
||||
background: rgb(250, 250, 250);
|
||||
border: 1px solid rgb(228, 231, 237);
|
||||
border-bottom: none;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -41,16 +41,19 @@ const info = {
|
||||
v-if="task.extVariable.bpmTaskApprovalType === 'REJECT'">拒绝
|
||||
</el-tag>
|
||||
<template v-if="task.extVariable.bpmTaskApprovalType === 'DYNAMIC'">
|
||||
<el-tag v-if="task.extVariable.RESULT === 'BACK_TO_START'" type="danger" size="mini">
|
||||
<el-tag v-if="task.extVariable.RESULT === 'BACK_TO_START'" type="danger"
|
||||
size="mini">
|
||||
退回至投稿人
|
||||
<span v-if="task.extVariable.processAgain===false">不需要重新走流程</span>
|
||||
<span v-if="task.extVariable.processAgain===true">需要重新走流程</span>
|
||||
</el-tag>
|
||||
<el-tag v-if="task.extVariable.RESULT === 'PASS'" type="success" size="mini">同意</el-tag>
|
||||
<el-tag v-if="task.extVariable.RESULT === 'PASS'" type="success" size="mini">同意
|
||||
</el-tag>
|
||||
<el-tag v-if="task.extVariable.RESULT === 'BACK_TO_CLUB'" type="danger" size="mini">
|
||||
退回到协会
|
||||
</el-tag>
|
||||
<el-tag v-if="task.extVariable.RESULT === 'BACK_TO_UNION'" type="danger" size="mini">
|
||||
<el-tag v-if="task.extVariable.RESULT === 'BACK_TO_UNION'" type="danger"
|
||||
size="mini">
|
||||
退回到分工会
|
||||
</el-tag>
|
||||
</template>
|
||||
@@ -72,8 +75,8 @@ const info = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(id) {
|
||||
this.$axios.post("/platform/article/common/info", { id }).then((res) => {
|
||||
onOpen(row) {
|
||||
this.$axios.post("/platform/article/common/info", { id: row.businessKey }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.viewData = res.data
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ layout("/layouts/platform.html"){
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker v-model="pageForm.year" type="year" value-format="yyyy" placeholder="年度" style="width: 100%"></el-date-picker>
|
||||
<el-date-picker v-model="pageForm.year" type="year" value-format="yyyy" placeholder="年度"
|
||||
style="width: 100%"></el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="标题">
|
||||
<el-input v-model="pageForm.title" placeholder="标题" clearable></el-input>
|
||||
@@ -25,27 +26,25 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column prop="unitName" label="投稿人单位"></el-table-column>
|
||||
<el-table-column prop="unionName" label="投稿人工会"></el-table-column>
|
||||
<el-table-column prop="submitTime" label="投稿时间"></el-table-column>
|
||||
<el-table-column label="当前节点" prop="processInstanceNodeName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="taskName" label="当前节点"></el-table-column>
|
||||
<el-table-column prop="instanceState" label="流程状态">
|
||||
<template slot-scope="{row}">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="300px">
|
||||
<template slot-scope="{row}">
|
||||
<el-button @click="onOpen(row.id)" size="mini" type="primary">查看</el-button>
|
||||
<el-button v-if="[10,30,34,38,50,65].includes(row.processInstanceNodeCode)" @click="onEdit(row.id)" size="mini" type="primary">
|
||||
<el-button v-if="row.taskKey === 'apply' || !row.instanceId" @click="onEdit(row)" size="mini"
|
||||
type="primary">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="[20].includes(row.processInstanceNodeCode) && !row.nextTaskIsComplete"
|
||||
size="mini"
|
||||
type="danger"
|
||||
@click="onRevoke(row.id)"
|
||||
>
|
||||
撤销
|
||||
<el-button v-if="['waiting'].includes(row.flow_status)" size="mini" type="danger"
|
||||
@click="onRevoke(row.id)">撤销
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="[10,30,34,50,70].includes(row.processInstanceNodeCode)||$auth.hasRoleOr('SYSADMIN')"
|
||||
@click="onDelete(row.id)"
|
||||
size="mini"
|
||||
type="danger"
|
||||
>
|
||||
<el-button v-if="row.taskKey === 'apply' || !row.instanceId"
|
||||
@click="onDelete(row.id)" size="mini" type="danger">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -77,8 +76,19 @@ layout("/layouts/platform.html"){
|
||||
this.$refs.infoRef.onOpen(id)
|
||||
})
|
||||
},
|
||||
onEdit(id) {
|
||||
this.$store.dispatch("pjaxRoute", "/platform/article/write?id=" + id)
|
||||
onEdit(row) {
|
||||
window.open("/flow/common/approval/form?taskId=" + (row.taskId || "") + "&instanceId=" + (row.instanceId || "") + "&businessId=" + row.id
|
||||
+ "&defineKey=XWTG")
|
||||
|
||||
// $.pjax({
|
||||
// url: "/platform/article/write?id=" + businessNo + "&taskId=" + taskId + "&instanceId=" + instanceId,
|
||||
// container: "#sub-app-container-main-content",
|
||||
// maxCacheLength: 0,
|
||||
// push: false,
|
||||
// replace: true,
|
||||
// fragment: "#sub-app-container-main-content",
|
||||
// timeout: 8000
|
||||
// })
|
||||
},
|
||||
onRevoke(id) {
|
||||
this.$confirm("您确定要撤销申请吗?", "提示", {
|
||||
@@ -86,7 +96,7 @@ layout("/layouts/platform.html"){
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/article/mine/revokeApply", { id }).then((resp) => {
|
||||
this.$axios.post("/platform/article/write/revokeApply", { id }).then((resp) => {
|
||||
this.$message.success(resp.msg)
|
||||
this.doSearch()
|
||||
})
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<div id="article_school_union_approval_form">
|
||||
<el-form :model="formData" ref="approvalFormRef" label-position="left" label-width="80px">
|
||||
<el-form-item label="审批意见" prop="approvalOpinion"
|
||||
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<user-opinion-textarea v-model="formData.approvalOpinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="电子签名" prop="approvalSignature" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">-->
|
||||
<!-- <pc-signature v-model="formData.approvalSignature"></pc-signature>-->
|
||||
<!-- </el-form-item>-->
|
||||
</el-form>
|
||||
<snaker-flow-task-form-action @task-action="handleTaskAction"
|
||||
@cancel="handleCancel">
|
||||
</snaker-flow-task-form-action>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#article_school_union_approval_form",
|
||||
data() {
|
||||
return {
|
||||
taskId: GetQueryString("taskId"),
|
||||
formData: {
|
||||
approval: "",
|
||||
approvalOpinion: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTaskAction(val) {
|
||||
console.log(val)
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify(val)
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("操作成功")
|
||||
// 发送完成消息
|
||||
window.parent.postMessage({
|
||||
type: "task-complete"
|
||||
}, "*")
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCancel(val) {
|
||||
console.log(val)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
+39
-53
@@ -7,26 +7,18 @@ layout("/layouts/platform.html"){
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度">
|
||||
<el-date-picker v-model="pageForm.year" type="year" value-format="yyyy" placeholder="年度" style="width: 100%"></el-date-picker>
|
||||
<el-date-picker
|
||||
:clearable="true"
|
||||
v-model="pageForm.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="年度"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="标题">
|
||||
<el-input v-model="pageForm.title" placeholder="标题" clearable></el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" placeholder="请选择所属工会" filterable clearable style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="所属单位:">
|
||||
<el-select v-model="pageForm.unitId" placeholder="请选择所属单位" filterable clearable style="width: 100%">
|
||||
<el-option v-for="item in unitOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="投稿来源:">
|
||||
<el-select v-model="pageForm.origin" placeholder="请选择投稿来源" filterable clearable style="width: 100%">
|
||||
<el-option v-for="item in dict.type.ARTICLE_ORIGIN" :key="item.code" :label="item.label" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
<el-card shadow="never">
|
||||
@@ -42,25 +34,19 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column prop="loginName" label="投稿人工号"></el-table-column>
|
||||
<el-table-column prop="userName" label="投稿人姓名"></el-table-column>
|
||||
<el-table-column prop="unitName" label="投稿人单位"></el-table-column>
|
||||
<el-table-column
|
||||
prop="unionName"
|
||||
label="投稿人工会"
|
||||
v-if="$auth.hasRoleOr(['SCHOOL_UNION_ARTICLE_FGH_ADMIN','SYSADMIN'])"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="clubName"
|
||||
label="投稿人协会"
|
||||
v-if="$auth.hasRoleOr(['SCHOOL_UNION_ARTICLE_CLUB_ADMIN','SYSADMIN'])"
|
||||
></el-table-column>
|
||||
<el-table-column prop="unionName" label="投稿人工会"></el-table-column>
|
||||
<el-table-column prop="submitTime" label="投稿时间"></el-table-column>
|
||||
<el-table-column prop="mode" label="投稿来源">
|
||||
<template slot-scope="{row}">{{dict.type.ARTICLE_ORIGIN.find(v=>v.code===row.origin)?.label}}</template>
|
||||
<el-table-column prop="taskName" label="当前节点"></el-table-column>
|
||||
<el-table-column prop="instanceState" label="流程状态">
|
||||
<template slot-scope="{row}">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
|
||||
size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前节点" prop="processInstanceNodeName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="300px">
|
||||
<el-table-column label="操作" width="200px" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button @click="onOpen(row.id)" size="mini" type="primary">查看</el-button>
|
||||
<el-button v-if="row.processInstanceTaskStatus==='ACTIVE'" @click="openApproval(row)" size="mini" type="primary">
|
||||
<el-button v-if="row.taskState === 10" @click="openApproval(row)" size="mini" type="primary">
|
||||
审核
|
||||
</el-button>
|
||||
<el-button
|
||||
@@ -77,19 +63,25 @@ layout("/layouts/platform.html"){
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<template #public>
|
||||
<el-dialog title="" :visible.sync="showApprovalForm" width="90%">
|
||||
<info ref="infoRef"></info>
|
||||
<el-row type="flex">
|
||||
<flow-form-button :task_id="taskId"></flow-form-button>
|
||||
|
||||
<!-- <el-button plain @click="$refs.guava.index()">取消</el-button>-->
|
||||
<!-- <el-button type="danger" @click="doApproval('BACK')">退回修改</el-button>-->
|
||||
<!-- <el-button type="primary" @click="doApproval('PASS')">同意</el-button>-->
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
|
||||
<template #public>
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{formData.processInstanceNodeName}}</div>
|
||||
<el-form :model="formData" ref="approvalFormRef" label-position="left" label-width="80px">
|
||||
<el-form-item label="审核意见" prop="approvalOpinion" :rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<user-opinion-textarea v-model="formData.approvalOpinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row type="flex" justify="end">
|
||||
<el-button plain @click="$refs.guava.index()">取消</el-button>
|
||||
<el-button type="danger" @click="doApproval('BACK')">退回到编辑初审</el-button>
|
||||
<el-button type="primary" @click="doApproval('PASS')">同意</el-button>
|
||||
<el-row type="flex">
|
||||
<flow-form-button :task_id="taskId"></flow-form-button>
|
||||
|
||||
<!-- <el-button plain @click="$refs.guava.index()">取消</el-button>-->
|
||||
<!-- <el-button type="danger" @click="doApproval('BACK')">退回修改</el-button>-->
|
||||
<!-- <el-button type="primary" @click="doApproval('PASS')">同意</el-button>-->
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
@@ -103,14 +95,14 @@ layout("/layouts/platform.html"){
|
||||
el: "#app",
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
dicts: ["ARTICLE_ORIGIN"],
|
||||
components: { info },
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
approval: false
|
||||
},
|
||||
showApprovalForm: false
|
||||
showApprovalForm: false,
|
||||
taskId: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -121,18 +113,14 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
},
|
||||
openApproval(row) {
|
||||
this.$refs.guava.public(() => {
|
||||
this.$refs.infoRef.onOpen(row.id)
|
||||
this.formData = row.approvalParam
|
||||
this.showApprovalForm = true
|
||||
})
|
||||
window.open("/flow/common/approval/form?taskId=" + row.taskId + "&instanceId=" + row.instanceId + "&businessId=" + row.businessNo)
|
||||
},
|
||||
doApproval(approvalType) {
|
||||
this.formData.bpmTaskApprovalType = approvalType
|
||||
this.$refs.approvalFormRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$axios
|
||||
.post("/platform/article/schoolUnionApproval/approval", {
|
||||
.post("/platform/article/branchUnionApproval/approval", {
|
||||
approval: JSON.stringify(this.formData)
|
||||
})
|
||||
.then((res) => {
|
||||
@@ -151,7 +139,7 @@ layout("/layouts/platform.html"){
|
||||
cancelButtonText: "取消",
|
||||
type: "info"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/article/schoolUnionApproval/revoke", { taskId }).then((res) => {
|
||||
this.$axios.post("/platform/article/branchUnionApproval/revoke", { taskId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.doSearch()
|
||||
@@ -161,8 +149,6 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
|
||||
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -55,11 +55,11 @@ layout("/layouts/platform.html"){
|
||||
</el-form-item>
|
||||
<el-form-item prop="files" label="稿件">
|
||||
<file-upload
|
||||
:value.sync="formData.files"
|
||||
:upload_number="5"
|
||||
upload_result_category="array"
|
||||
complete_result
|
||||
upload_mode="drag"
|
||||
:value.sync="formData.files"
|
||||
:upload_number="5"
|
||||
upload_result_category="array"
|
||||
complete_result
|
||||
upload_mode="drag"
|
||||
></file-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -86,15 +86,17 @@ layout("/layouts/platform.html"){
|
||||
data() {
|
||||
return {
|
||||
id: GetQueryString("id"),
|
||||
taskId: GetQueryString("taskId"),
|
||||
instanceId: GetQueryString("instanceId"),
|
||||
formData: {},
|
||||
formRules: {
|
||||
title: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
excerpt: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
clubId: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
files: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
origin: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
mobile: [{required: true, message: "必填", trigger: ["change", "blur"]}],
|
||||
mobile2: [{required: true, message: "必填", trigger: ["change", "blur"]}]
|
||||
title: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
excerpt: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
clubId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
files: [{ required: false, message: "必填", trigger: ["change", "blur"] }],
|
||||
origin: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
mobile: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
mobile2: [{ required: true, message: "必填", trigger: ["change", "blur"] }]
|
||||
},
|
||||
permission: {}
|
||||
}
|
||||
@@ -106,7 +108,11 @@ layout("/layouts/platform.html"){
|
||||
this.$message.warning("请填写标题")
|
||||
return
|
||||
}
|
||||
this.$axios.post("/platform/article/write/save", {article: JSON.stringify(this.formData)}).then((res) => {
|
||||
this.$axios.post("/platform/article/write/save", {
|
||||
article: JSON.stringify(this.formData),
|
||||
taskId: this.taskId,
|
||||
instanceId: this.instanceId
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.$store.dispatch("pjaxRoute", "/platform/article/mine")
|
||||
@@ -117,7 +123,11 @@ layout("/layouts/platform.html"){
|
||||
doSubmit() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$axios.post("/platform/article/write/submit", {article: JSON.stringify(this.formData)}).then((res) => {
|
||||
this.$axios.post("/platform/article/write/submit", {
|
||||
article: JSON.stringify(this.formData),
|
||||
taskId: this.taskId,
|
||||
instanceId: this.instanceId
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("提交成功")
|
||||
this.$store.dispatch("pjaxRoute", "/platform/article/mine")
|
||||
@@ -137,13 +147,13 @@ layout("/layouts/platform.html"){
|
||||
|
||||
init() {
|
||||
if (this.id) {
|
||||
this.$axios.post("/platform/article/write/get", {id: this.id}).then((res) => {
|
||||
this.$axios.post("/platform/article/write/get", { id: this.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.formData = res.data
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const {username, loginname, id, unit, union, mobile} = this.$store.state.user
|
||||
const { username, loginname, id, unit, union, mobile } = this.$store.state.user
|
||||
this.formData = {
|
||||
userName: username,
|
||||
loginName: loginname,
|
||||
@@ -153,7 +163,7 @@ layout("/layouts/platform.html"){
|
||||
unionId: union?.id,
|
||||
unionName: union?.name,
|
||||
mobile: mobile,
|
||||
mobile2: mobile,
|
||||
mobile2: mobile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
<div id="artile_write">
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=":" class="flow-task-form">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="投稿人姓名">{{formData.userName}}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人工号">{{formData.loginName}}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人单位">{{formData.unitName}}</el-descriptions-item>
|
||||
<el-descriptions-item label="投稿人工会">{{formData.unionName}}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="投稿来源" :span="2">
|
||||
<el-form-item prop="origin" label="投稿来源" label-width="0">
|
||||
<el-radio-group v-model="formData.origin" size="small">
|
||||
<el-radio border v-for="item in origins" :label="item.code" :key="item.code">{{item.name}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="投稿人协会" v-if="formData.origin === 'ARTICLE_ORIGIN_CLUB'">
|
||||
<el-form-item prop="clubId" label="投稿人协会" v-if="formData.origin === 'ARTICLE_ORIGIN_CLUB'">
|
||||
<el-select v-model="formData.clubId" placeholder="请选择" style="width: 100%">
|
||||
<el-option v-for="item in permission?.clubs" :key="item.id" :label="item.clubName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="投稿人联系方式" :span="2">
|
||||
<el-form-item prop="title" label="投稿标题">
|
||||
<el-input v-model="formData.title" maxlength="100" show-word-limit placeholder="请输入标题"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="投稿人联系方式" :span="2">
|
||||
<el-form-item prop="mobile" label="投稿人联系方式">
|
||||
<el-input v-model="formData.mobile" placeholder="请输入投稿人联系方式"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="修改人联系方式" :span="2">
|
||||
<el-form-item prop="mobile2" label="修改人联系方式">
|
||||
<el-input v-model="formData.mobile2" placeholder="请输入修改人联系方式"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="投稿说明" :span="2">
|
||||
<el-form-item prop="excerpt" label="投稿说明">
|
||||
<el-input v-model="formData.excerpt" type="textarea" maxlength="500" show-word-limit placeholder="请输入投稿说明"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="稿件" :span="2">
|
||||
<file-upload
|
||||
:value.sync="formData.files"
|
||||
:upload_number="5"
|
||||
upload_result_category="array"
|
||||
complete_result
|
||||
upload_mode="drag"
|
||||
></file-upload>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-form>
|
||||
<snaker-flow-task-form-action @task-action="handleTaskAction" @save-draft="handleSaveDraft" @cancel="handleCancel"></snaker-flow-task-form-action>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#artile_write",
|
||||
store,
|
||||
dicts: ["ARTICLE_ORIGIN"],
|
||||
computed: {
|
||||
origins() {
|
||||
if (this.dict?.type?.ARTICLE_ORIGIN && this.permission && this.permission.roles) {
|
||||
return this.dict.type.ARTICLE_ORIGIN.filter((item) => this.permission && this.permission.roles.includes(item.code))
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: GetQueryString("businessId"),
|
||||
taskId: GetQueryString("taskId"),
|
||||
instanceId: GetQueryString("instanceId"),
|
||||
formData: {},
|
||||
formRules: {
|
||||
title: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
excerpt: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
clubId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
files: [{ required: false, message: "必填", trigger: ["change", "blur"] }],
|
||||
origin: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
mobile: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
mobile2: [{ required: true, message: "必填", trigger: ["change", "blur"] }]
|
||||
},
|
||||
permission: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
if (this.id) {
|
||||
this.$axios.post("/platform/article/write/get", { id: this.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.formData = res.data
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const { username, loginname, id, unit, union, mobile } = this.$store.state.user
|
||||
this.formData = {
|
||||
userName: username,
|
||||
loginName: loginname,
|
||||
userId: id,
|
||||
unitId: unit?.id,
|
||||
unitName: unit?.name,
|
||||
unionId: union?.id,
|
||||
unionName: union?.name,
|
||||
mobile: mobile,
|
||||
mobile2: mobile
|
||||
}
|
||||
}
|
||||
},
|
||||
checkPermission() {
|
||||
this.$axios.post("/platform/article/write/checkPermission").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.permission = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleTaskAction(val) {
|
||||
console.log(val)
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$axios
|
||||
.post("/flow/common/startInstanceAndExecute", {
|
||||
...val,
|
||||
bizData: JSON.stringify(this.formData)
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("操作成功")
|
||||
// 发送完成消息
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "task-complete"
|
||||
},
|
||||
"*"
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleSaveDraft() {
|
||||
this.$message.success("保存成功")
|
||||
this.$refs.formRef.validateField(["title"], (errMsg) => {
|
||||
if (errMsg) {
|
||||
this.$message.warning("请填写标题")
|
||||
return
|
||||
}
|
||||
this.$axios
|
||||
.post("/flow/common/startInstance", {
|
||||
...val,
|
||||
bizData: JSON.stringify(this.formData)
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("操作成功")
|
||||
// 发送完成消息
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "task-complete"
|
||||
},
|
||||
"*"
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleCancel() {}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
this.checkPermission()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak></div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,878 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: rgb(0, 78, 100);
|
||||
--primary-light: rgba(0, 78, 100, 0.1);
|
||||
--secondary-color: #37A6BD;
|
||||
--text-color: #333333;
|
||||
--text-secondary: #666666;
|
||||
--text-light: #999999;
|
||||
--bg-color: #f5f7fa;
|
||||
--card-bg: #ffffff;
|
||||
--border-color: #eeeeee;
|
||||
--success-color: #07c160;
|
||||
--warning-color: #ff976a;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 筛选区域样式 */
|
||||
.filter-section {
|
||||
background-color: var(--card-bg);
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.filter-section.collapsed {
|
||||
max-height: 84px;
|
||||
}
|
||||
|
||||
.filter-section.expanded {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.filter-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.filter-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.filter-toggle {
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-toggle .van-icon {
|
||||
transition: transform 0.3s;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.filter-toggle .van-icon.rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.filter-content.hidden {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.filter-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-right: 10px;
|
||||
min-width: 60px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-tag {
|
||||
padding: 4px 10px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 6px;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.filter-tag.active {
|
||||
background-color: var(--primary-light);
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-search {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.filter-search .van-search {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-search .van-search__content {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
.suggestion-list {
|
||||
padding: 16px;
|
||||
background-color: var(--bg-color);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.van-pull-refresh, .van-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.suggestion-card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.suggestion-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.suggestion-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.suggestion-status {
|
||||
font-size: 12px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
margin-left: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background-color: #fff7e6;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
word-break: break-all;
|
||||
white-space: pre-line;
|
||||
max-height: 3.2em;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.suggestion-submitter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.suggestion-submitter .van-icon {
|
||||
margin-right: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.suggestion-unit {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.suggestion-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
border-top: 1px solid #f5f5f5;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.suggestion-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.suggestion-time .van-icon {
|
||||
font-size: 14px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.suggestion-action {
|
||||
color: var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.suggestion-action .van-icon {
|
||||
font-size: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 64px 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
color: #ddd;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 15px;
|
||||
color: var(--text-light);
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 详情弹窗样式 */
|
||||
.detail-popup {
|
||||
padding: 24px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.detail-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--text-light);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
color: var(--text-color);
|
||||
line-height: 1.8;
|
||||
font-size: 15px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.submitter-info {
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: var(--text-secondary);
|
||||
width: 80px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: var(--text-color);
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-reply {
|
||||
background-color: #f9f9f9;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.detail-attachments {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.attachment-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.reply-form {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.reply-textarea {
|
||||
box-sizing: border-box;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
font-size: 14px;
|
||||
background-color: var(--card-bg);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.reply-attachments {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.reply-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.no-reply {
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.no-reply-icon {
|
||||
font-size: 36px;
|
||||
color: #ccc;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-reply-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.van-button--primary {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 下拉刷新和上拉加载样式 */
|
||||
.van-pull-refresh__track {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.van-list {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* 统计面板 */
|
||||
.stats-panel {
|
||||
background-color: var(--card-bg);
|
||||
padding: 16px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar title="意见箱管理" left-arrow @click-left="history.go(-1)" fixed placeholder></van-nav-bar>
|
||||
|
||||
<!-- 统计面板 -->
|
||||
<div class="stats-panel">
|
||||
<div class="stats-item">
|
||||
<div class="stats-value">{{ stats.total }}</div>
|
||||
<div class="stats-label">总意见数</div>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div class="stats-value">{{ stats.pending }}</div>
|
||||
<div class="stats-label">待回复</div>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div class="stats-value">{{ stats.replied }}</div>
|
||||
<div class="stats-label">已回复</div>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div class="stats-value">{{ stats.today }}</div>
|
||||
<div class="stats-label">今日新增</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<div class="filter-section" :class="filters.isCollapsed ? 'collapsed' : 'expanded'">
|
||||
<div class="filter-header">
|
||||
<div class="filter-title">筛选条件</div>
|
||||
<div class="filter-toggle" @click="toggleFilterCollapse">
|
||||
<span>{{ filters.isCollapsed ? '展开' : '收起' }}</span>
|
||||
<van-icon :name="filters.isCollapsed ? 'arrow-down' : 'arrow-up'"
|
||||
:class="{ rotate: !filters.isCollapsed }"></van-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-search">
|
||||
<van-search v-model="filters.keyword" placeholder="搜索意见标题、内容或提交人"
|
||||
@search="onSearch"></van-search>
|
||||
</div>
|
||||
|
||||
<div class="filter-content" :class="{ hidden: filters.isCollapsed }">
|
||||
<div class="filter-row">
|
||||
<div class="filter-label">状态</div>
|
||||
<div class="filter-options">
|
||||
<div class="filter-tag" :class="{ active: filters.status === '' }" @click="setFilter('status', '')">
|
||||
全部
|
||||
</div>
|
||||
<div class="filter-tag" :class="{ active: filters.status === '0' }"
|
||||
@click="setFilter('status', '0')">
|
||||
待回复
|
||||
</div>
|
||||
<div class="filter-tag" :class="{ active: filters.status === '1' }"
|
||||
@click="setFilter('status', '1')">
|
||||
已回复
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-row">
|
||||
<div class="filter-label">时间</div>
|
||||
<div class="filter-options">
|
||||
<div class="filter-tag" :class="{ active: filters.time === '' }" @click="setFilter('time', '')">全部
|
||||
</div>
|
||||
<div class="filter-tag" :class="{ active: filters.time === 'today' }"
|
||||
@click="setFilter('time', 'today')">今日
|
||||
</div>
|
||||
<div class="filter-tag" :class="{ active: filters.time === 'week' }"
|
||||
@click="setFilter('time', 'week')">
|
||||
本周
|
||||
</div>
|
||||
<div class="filter-tag" :class="{ active: filters.time === 'month' }"
|
||||
@click="setFilter('time', 'month')">本月
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="suggestion-list">
|
||||
<!-- 下拉刷新和上拉加载更多 -->
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list
|
||||
v-model="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="loadMore"
|
||||
>
|
||||
<!-- 空状态 -->
|
||||
<div class="empty-list" v-if="suggestions.length === 0 && !loading">
|
||||
<van-icon name="comment-circle-o" class="empty-icon"></van-icon>
|
||||
<div class="empty-text">暂无符合条件的意见</div>
|
||||
</div>
|
||||
|
||||
<!-- 意见列表 -->
|
||||
<div class="suggestion-card" v-for="(item, index) in suggestions" :key="item.id"
|
||||
@click="showDetail(item)">
|
||||
<div class="suggestion-header">
|
||||
<div class="suggestion-title">{{ item.title || '意见反馈' }}</div>
|
||||
<div class="suggestion-status" :class="getStatusClass(item.isReply)">
|
||||
{{ getStatusText(item.isReply) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="suggestion-submitter">
|
||||
<van-icon name="contact"/>
|
||||
<span>{{ item.submitterName }}</span>
|
||||
<span class="suggestion-unit">{{ item.submitterUnitName }}</span>
|
||||
</div>
|
||||
<div class="suggestion-content">{{ item.content }}</div>
|
||||
<div class="suggestion-footer">
|
||||
<div class="suggestion-time">
|
||||
<van-icon name="clock-o"/>
|
||||
<span>{{ formatDate(item.submitTime) }}</span>
|
||||
</div>
|
||||
<div class="suggestion-action">
|
||||
{{ item.reply ? '查看详情' : '去回复' }}
|
||||
<van-icon name="arrow"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</div>
|
||||
|
||||
<!-- 详情弹出层 -->
|
||||
<van-popup v-model="showDetailPopup" round closeable position="bottom">
|
||||
<div class="detail-popup" v-if="currentSuggestion">
|
||||
<div class="detail-header">
|
||||
<div class="detail-title">{{ currentSuggestion.title || '意见反馈' }}</div>
|
||||
<div class="detail-meta">
|
||||
<span>{{ formatDate(currentSuggestion.submitTime) }}</span>
|
||||
<span :class="getStatusClass(currentSuggestion.isReply)">{{ getStatusText(currentSuggestion.isReply) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">提交人信息</div>
|
||||
<div class="submitter-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">姓名:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">工号:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterLoginName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">单位:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterUnitName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">联系方式:</span>
|
||||
<span class="info-value">{{ currentSuggestion.concat || '未填写' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">意见内容</div>
|
||||
<div class="detail-content">{{ currentSuggestion.content }}</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section"
|
||||
v-if="currentSuggestion.attachments && currentSuggestion.attachments.length > 0">
|
||||
<div class="detail-label">附件</div>
|
||||
<div class="detail-attachments">
|
||||
<div class="attachment-item" v-for="(file, idx) in currentSuggestion.attachments" :key="idx"
|
||||
@click.stop="previewImage(file.url, idx)">
|
||||
<img :src="file.url" class="attachment-image">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentSuggestion.reply">
|
||||
<div class="detail-label">已回复内容</div>
|
||||
<div class="detail-reply detail-content">{{ currentSuggestion.reply }}</div>
|
||||
<div class="detail-meta" style="margin-top: 10px;">
|
||||
回复时间:{{ formatDate(currentSuggestion.replyTime) }}
|
||||
</div>
|
||||
<div v-if="currentSuggestion.replyAttachments && currentSuggestion.replyAttachments.length > 0"
|
||||
style="margin-top: 12px;">
|
||||
<div class="detail-label">回复附件</div>
|
||||
<div class="detail-attachments">
|
||||
<div class="attachment-item" v-for="(file, idx) in currentSuggestion.replyAttachments"
|
||||
:key="idx" @click.stop="previewImage(file.url, idx, 'reply')">
|
||||
<img :src="file.url" class="attachment-image">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">{{ currentSuggestion.isReply ? '修改回复' : '回复意见' }}</div>
|
||||
<div class="reply-form">
|
||||
<textarea class="reply-textarea" v-model="currentSuggestion.replyContent"
|
||||
placeholder="请输入回复内容..."></textarea>
|
||||
|
||||
<!-- <div class="reply-attachments">-->
|
||||
<!-- <vant-file-upload :files.sync="formData.replyAttachments" :max="15"></vant-file-upload>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="reply-actions">
|
||||
<van-button style="border-radius: 10px" block type="info" :color="themeColor"
|
||||
@click="submitReply">提交回复
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
return {
|
||||
refreshing: false,
|
||||
loading: false,
|
||||
finished: false,
|
||||
showDetailPopup: false,
|
||||
currentSuggestion: null,
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
suggestions: [],
|
||||
replyContent: '',
|
||||
replyAttachments: [],
|
||||
filters: {
|
||||
status: '', // 状态筛选
|
||||
time: '', // 时间筛选
|
||||
keyword: '', // 关键词搜索
|
||||
isCollapsed: true // 筛选区域是否折叠
|
||||
},
|
||||
stats: {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
replied: 0,
|
||||
today: 0
|
||||
},
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadStats();
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
// 加载统计数据
|
||||
loadStats() {
|
||||
$.post('/platform/suggestionBox/admin/getStats').done((res) => {
|
||||
if (res.code === 0 && res.data) {
|
||||
this.stats = {
|
||||
total: res.data.total || 0,
|
||||
pending: res.data.pending || 0,
|
||||
replied: res.data.replied || 0,
|
||||
today: res.data.today || 0
|
||||
};
|
||||
}
|
||||
}).fail(() => {
|
||||
this.$toast.fail('统计数据加载失败');
|
||||
});
|
||||
},
|
||||
|
||||
// 加载意见数据
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
$.post('/platform/suggestionBox/admin/pageData', {
|
||||
pageNumber: this.pageNumber,
|
||||
pageSize: this.pageSize,
|
||||
status: this.filters.status,
|
||||
timeRange: this.filters.time,
|
||||
keyword: this.filters.keyword
|
||||
}).done((res) => {
|
||||
if (res.code === 0 && res.data) {
|
||||
if (this.pageNumber === 1) {
|
||||
this.suggestions = res.data.list || [];
|
||||
} else {
|
||||
this.suggestions = this.suggestions.concat(res.data.list || []);
|
||||
}
|
||||
|
||||
this.finished = !res.data.list || res.data.list.length < this.pageSize;
|
||||
} else {
|
||||
this.finished = true;
|
||||
}
|
||||
this.loading = false;
|
||||
this.refreshing = false;
|
||||
}).fail(() => {
|
||||
this.loading = false;
|
||||
this.refreshing = false;
|
||||
this.finished = true;
|
||||
});
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onRefresh() {
|
||||
this.pageNumber = 1;
|
||||
this.finished = false;
|
||||
this.loadStats();
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
// 上拉加载更多
|
||||
loadMore() {
|
||||
this.pageNumber++;
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
// 设置筛选条件
|
||||
setFilter(type, value) {
|
||||
this.filters[type] = value;
|
||||
this.pageNumber = 1;
|
||||
this.finished = false;
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
// 搜索
|
||||
onSearch() {
|
||||
this.pageNumber = 1;
|
||||
this.finished = false;
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
// 查看详情
|
||||
showDetail(suggestion) {
|
||||
if (suggestion.attachments && typeof suggestion.attachments === 'string') {
|
||||
try{
|
||||
suggestion.attachments = JSON.parse(suggestion.attachments);
|
||||
}catch (e){
|
||||
suggestion.attachments = [];
|
||||
}
|
||||
}
|
||||
console.log(suggestion)
|
||||
this.currentSuggestion = suggestion;
|
||||
this.showDetailPopup = true;
|
||||
},
|
||||
|
||||
// 提交回复
|
||||
submitReply() {
|
||||
if (!this.currentSuggestion.replyContent || !this.currentSuggestion.replyContent.trim()) {
|
||||
this.$toast('请输入回复内容');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$dialog.confirm({
|
||||
title: '确认提交',
|
||||
message: '确定提交此回复内容吗?'
|
||||
}).then(() => {
|
||||
// 提交回复
|
||||
$.post('/platform/suggestionBox/admin/reply', {
|
||||
reply: JSON.stringify(this.currentSuggestion)
|
||||
}).done((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success('回复成功');
|
||||
this.showDetailPopup = false;
|
||||
this.loadStats();
|
||||
this.pageData();
|
||||
} else {
|
||||
this.$toast.fail(res.msg || '回复失败');
|
||||
}
|
||||
}).fail(() => {
|
||||
this.$toast.fail('网络错误,请重试');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 获取状态class
|
||||
getStatusClass(isReply) {
|
||||
if (!isReply || isReply === 0) return 'status-pending';
|
||||
if (isReply === 1) return 'status-processing';
|
||||
return 'status-completed';
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(isReply) {
|
||||
if (!isReply || isReply === 0) return '待回复';
|
||||
if (isReply === 1) return '已回复';
|
||||
return '已处理';
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
return date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0');
|
||||
},
|
||||
|
||||
// 预览图片
|
||||
previewImage(url, index, type = 'submission') {
|
||||
if (!url) return;
|
||||
|
||||
// 创建图片查看器
|
||||
const urls = type === 'reply'
|
||||
? this.currentSuggestion.replyAttachments.map(file => file.url)
|
||||
: this.currentSuggestion.attachments.map(file => file.url);
|
||||
|
||||
this.$imagePreview({
|
||||
images: urls,
|
||||
startPosition: index
|
||||
});
|
||||
},
|
||||
|
||||
// 切换筛选区域折叠状态
|
||||
toggleFilterCollapse() {
|
||||
this.filters.isCollapsed = !this.filters.isCollapsed;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!--#}#-->
|
||||
@@ -0,0 +1,538 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar title="意见箱" fixed placeholder></van-nav-bar>
|
||||
|
||||
<!-- 顶部Banner区域 -->
|
||||
<div class="banner-section">
|
||||
<div class="banner-content">
|
||||
<div class="banner-text">
|
||||
<h2>我们重视您的意见</h2>
|
||||
<p>每一条建议都将认真对待</p>
|
||||
</div>
|
||||
<div class="banner-image">
|
||||
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTI1LjYgMTAyLjdsLTE5LTE2LjhtLTQuNy0yMy43bDExLjUtNi44TTU3LjQgMTZsMTEuNSAxMS43TTMxLjIgNTMuOGwyMy42IDcuOSIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6I2ZmZjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtvcGFjaXR5Oi40Ii8+PHBhdGggZD0iTTY0LjEgMzEuN0w0NyA0OS45Yy0zIDMuMi0zLjEgOC4xLS4xIDExLjJsMjQuNCAyNWMzIDMuMSA3LjkgMy4yIDExIC4xTDk5IDY5YzMtMy4yIDMuMS04LjEuMS0xMS4yTDc0LjcgMzIuOGMtMyAzLjEtNy45IDMuMS0xMC42LTEuMXoiIHN0eWxlPSJmaWxsOiNmZmY7c3Ryb2tlOiNmZmY7c3Ryb2tlLW1pdGVybGltaXQ6MTAiLz48cGF0aCBkPSJNMTA4LjkgOTUuN2wtMTUuOC0xMi0xMi4yIDEzIDE0LjQgMTMuN2MuMyAyLjUgMi4zIDQuNSA0LjggNC41aDEzLjdjMi43IDAgNC45LTIuMiA0LjktNC45VjkzLjVjMC0yLjgtMi4xLTUtNC45LTV2OC40cy4xLTEuMi00LjkgMi44LjEtMyAuMS0zeiIgc3R5bGU9ImZpbGw6I2ZmZjtzdHJva2U6I2ZmZjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMCIvPjwvc3ZnPg==" alt="Feedback">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能卡片区域 -->
|
||||
<div class="cards-container">
|
||||
<!-- 提交意见 -->
|
||||
<div class="feature-card" @click="goToSubmitPage">
|
||||
<div class="card-icon submit-icon">
|
||||
<van-icon name="edit" />
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3>提交意见</h3>
|
||||
<p>分享您的想法和建议</p>
|
||||
</div>
|
||||
<div class="card-arrow">
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的意见 -->
|
||||
<div class="feature-card" @click="goToMyOpinionsPage">
|
||||
<div class="card-icon my-icon">
|
||||
<van-icon name="records" />
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3>我的意见</h3>
|
||||
<p>查看您提交的所有意见</p>
|
||||
</div>
|
||||
<div class="card-arrow">
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 意见管理(管理员) -->
|
||||
<div class="feature-card" v-if="isAdmin" @click="goToAllOpinionsPage">
|
||||
<div class="card-icon admin-icon">
|
||||
<van-icon name="manager" />
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3>意见管理</h3>
|
||||
<p>管理所有用户提交的意见</p>
|
||||
</div>
|
||||
<div class="card-arrow">
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 使用指南 -->
|
||||
<div class="guide-container" v-if="!isAdmin">
|
||||
<div class="guide-header">
|
||||
<h3>使用指南</h3>
|
||||
</div>
|
||||
<div class="guide-steps">
|
||||
<div class="guide-step">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-content">
|
||||
<h4>提交意见</h4>
|
||||
<p>点击"提交意见"按钮,填写您的意见和建议</p>
|
||||
</div>
|
||||
<div class="step-icon">
|
||||
<van-icon name="edit" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<div class="guide-step">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-content">
|
||||
<h4>等待处理</h4>
|
||||
<p>我们将在3个工作日内处理您的意见</p>
|
||||
</div>
|
||||
<div class="step-icon">
|
||||
<van-icon name="underway-o" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<div class="guide-step">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-content">
|
||||
<h4>查看回复</h4>
|
||||
<p>在"我的意见"中查看官方回复</p>
|
||||
</div>
|
||||
<div class="step-icon">
|
||||
<van-icon name="comment-o" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 常见问题 -->
|
||||
<div class="faq-container" v-if="!isAdmin">
|
||||
<div class="faq-header">
|
||||
<h3>常见问题</h3>
|
||||
<span class="faq-subtitle">解答您的疑惑</span>
|
||||
</div>
|
||||
<div class="faq-list">
|
||||
<div class="faq-item" :class="{'faq-active': activeFaq === 1}" @click="toggleFaq(1)">
|
||||
<div class="faq-question">
|
||||
<span class="faq-icon"><van-icon name="question-o" /></span>
|
||||
<span>如何提交带附件的意见?</span>
|
||||
<span class="faq-arrow"><van-icon name="arrow-down" /></span>
|
||||
</div>
|
||||
<div class="faq-answer" v-show="activeFaq === 1">
|
||||
<p>在提交意见表单中,您可以上传最多3个文件作为附件,支持图片格式。点击附件上传区域,选择您要上传的文件即可。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="faq-item" :class="{'faq-active': activeFaq === 2}" @click="toggleFaq(2)">
|
||||
<div class="faq-question">
|
||||
<span class="faq-icon"><van-icon name="question-o" /></span>
|
||||
<span>意见提交后多久能收到回复?</span>
|
||||
<span class="faq-arrow"><van-icon name="arrow-down" /></span>
|
||||
</div>
|
||||
<div class="faq-answer" v-show="activeFaq === 2">
|
||||
<p>我们会在3个工作日内处理您的意见,紧急问题会优先处理。您可以随时在"我的意见"中查看处理进度。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="faq-item" :class="{'faq-active': activeFaq === 3}" @click="toggleFaq(3)">
|
||||
<div class="faq-question">
|
||||
<span class="faq-icon"><van-icon name="question-o" /></span>
|
||||
<span>我可以修改已提交的意见吗?</span>
|
||||
<span class="faq-arrow"><van-icon name="arrow-down" /></span>
|
||||
</div>
|
||||
<div class="faq-answer" v-show="activeFaq === 3">
|
||||
<p>提交后的意见暂不支持修改,如有补充,请重新提交并说明这是对之前意见的补充。我们会将相关意见关联处理。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: rgb(0, 78, 100);
|
||||
--primary-light: rgba(0, 78, 100, 0.1);
|
||||
--secondary-color: #37A6BD;
|
||||
--text-color: #333333;
|
||||
--text-secondary: #666666;
|
||||
--text-light: #999999;
|
||||
--bg-color: #f6f6f6;
|
||||
--card-bg: #ffffff;
|
||||
--border-color: #eeeeee;
|
||||
--success-color: #07c160;
|
||||
--warning-color: #ff976a;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
/* 顶部Banner */
|
||||
.banner-section {
|
||||
padding: 0;
|
||||
height: 180px;
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.banner-curve {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24px 20px 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.banner-text {
|
||||
color: white;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.banner-text h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.banner-text p {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.banner-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* 功能卡片 */
|
||||
.cards-container {
|
||||
padding: 20px 16px 16px;
|
||||
margin-top: -20px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 20px 20px 0 0;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.card-icon .van-icon {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.submit-icon {
|
||||
background: linear-gradient(135deg, #1989fa 0%, #39b9f9 100%);
|
||||
}
|
||||
|
||||
.my-icon {
|
||||
background: linear-gradient(135deg, #07c160 0%, #10d878 100%);
|
||||
}
|
||||
|
||||
.admin-icon {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ffaa7f 100%);
|
||||
}
|
||||
|
||||
.card-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-info h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.card-info p {
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-arrow {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* 使用指南 */
|
||||
.guide-container {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.guide-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.guide-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.guide-steps {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.guide-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-content h4 {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
margin-left: 12px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.step-divider {
|
||||
height: 24px;
|
||||
width: 1px;
|
||||
background: #e8e8e8;
|
||||
margin: 8px 0 8px 15px;
|
||||
}
|
||||
|
||||
/* 常见问题 */
|
||||
.faq-container {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.faq-header {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.faq-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.faq-subtitle {
|
||||
font-size: 12px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.faq-list {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.faq-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.faq-active .faq-question {
|
||||
background-color: var(--primary-light);
|
||||
}
|
||||
|
||||
.faq-icon {
|
||||
color: var(--primary-color);
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.faq-arrow {
|
||||
margin-left: auto;
|
||||
color: var(--text-light);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.faq-active .faq-arrow .van-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
padding: 0 16px 16px 44px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
border-top: 1px dashed var(--border-color);
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.popup-title {
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #ebedf0;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
padding: 16px;
|
||||
max-height: calc(100% - 60px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.suggestion-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.suggestion-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.attachment-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data: function() {
|
||||
return {
|
||||
isAdmin: true, // 控制是否为管理员
|
||||
allSuggestions: [],
|
||||
user: 'user1',
|
||||
activeFaq: null,
|
||||
totalSuggestions: 0,
|
||||
respondedPercent: 0,
|
||||
recentSuggestions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userSuggestions: function() {
|
||||
return this.allSuggestions.filter(function(item) {
|
||||
return item.user === this.user;
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goToSubmitPage: function() {
|
||||
window.location.href = '/platform/suggestionBox/h5/write';
|
||||
},
|
||||
goToMyOpinionsPage: function() {
|
||||
window.location.href = '/platform/suggestionBox/h5/mine';
|
||||
},
|
||||
goToAllOpinionsPage: function() {
|
||||
window.location.href = '/platform/suggestionBox/admin/h5';
|
||||
},
|
||||
toggleFaq: function(id) {
|
||||
this.activeFaq = this.activeFaq === id ? null : id;
|
||||
},
|
||||
loadStats: function() {
|
||||
// 模拟加载统计数据
|
||||
this.totalSuggestions = 25;
|
||||
this.respondedPercent = 80;
|
||||
this.recentSuggestions = [1, 2, 3];
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
// 初始化数据
|
||||
if (this.isAdmin) {
|
||||
this.loadStats();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,542 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: rgb(0, 78, 100);
|
||||
--primary-light: rgba(0, 78, 100, 0.1);
|
||||
--secondary-color: #37a6bd;
|
||||
--text-color: #333333;
|
||||
--text-secondary: #666666;
|
||||
--text-light: #999999;
|
||||
--bg-color: #f5f7fa;
|
||||
--card-bg: #ffffff;
|
||||
--border-color: #eeeeee;
|
||||
--success-color: #07c160;
|
||||
--warning-color: #ff976a;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 页面样式 */
|
||||
.suggestion-list {
|
||||
padding: 16px;
|
||||
background-color: var(--bg-color);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.van-pull-refresh,
|
||||
.van-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.suggestion-card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.suggestion-card:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.suggestion-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.suggestion-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.suggestion-status {
|
||||
font-size: 12px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
margin-left: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background-color: #fff7e6;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
word-break: break-all;
|
||||
white-space: pre-line;
|
||||
max-height: 3.2em;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.suggestion-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--text-light);
|
||||
border-top: 1px solid #f5f5f5;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.suggestion-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.suggestion-time .van-icon {
|
||||
font-size: 14px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.suggestion-action {
|
||||
color: var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.suggestion-action .van-icon {
|
||||
font-size: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 64px 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
color: #ddd;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 15px;
|
||||
color: var(--text-light);
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 详情弹窗样式 */
|
||||
.detail-popup {
|
||||
padding: 24px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.detail-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--text-light);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
color: var(--text-color);
|
||||
line-height: 1.8;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.detail-reply {
|
||||
background-color: #f9f9f9;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.detail-attachments {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.attachment-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.loader {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.van-button--primary {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 下拉刷新和上拉加载样式 */
|
||||
.van-pull-refresh__track {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.van-list {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.no-reply {
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.no-reply-icon {
|
||||
font-size: 36px;
|
||||
color: #ccc;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-reply-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.submitter-info {
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: var(--text-secondary);
|
||||
width: 80px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: var(--text-color);
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar title="我的意见建议" left-arrow @click-left="history.go(-1)" fixed placeholder></van-nav-bar>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="suggestion-list">
|
||||
<!-- 下拉刷新和上拉加载更多 -->
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="loadMore">
|
||||
<!-- 空状态 -->
|
||||
<div class="empty-list" v-if="suggestions.length === 0 && !loading">
|
||||
<van-icon name="comment-circle-o" class="empty-icon" />
|
||||
<div class="empty-text">您还没有提交过意见</div>
|
||||
<van-button type="primary" size="normal" round @click="goToSubmitPage">去提交意见</van-button>
|
||||
</div>
|
||||
|
||||
<!-- 意见列表 -->
|
||||
<div class="suggestion-card" v-for="(item, index) in suggestions" :key="item.id" @click="showDetail(item)">
|
||||
<div class="suggestion-header">
|
||||
<div class="suggestion-title">{{ item.title || '意见反馈' }}</div>
|
||||
<div class="suggestion-status" :class="getStatusClass(item.isReply)">{{ item.node_name }}</div>
|
||||
</div>
|
||||
<div class="suggestion-content">{{ item.content }}</div>
|
||||
<div class="suggestion-footer">
|
||||
<div class="suggestion-time">
|
||||
<van-icon name="clock-o" />
|
||||
<span>{{ formatDate(item.submitTime) }}</span>
|
||||
</div>
|
||||
<div style="display: flex; column-gap: 10px">
|
||||
<div class="suggestion-action">
|
||||
查看详情
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
<div class="suggestion-action" v-if="item.flow_status==='1'" @click.stop="onRevoke(item)">
|
||||
撤销
|
||||
<van-icon name="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</div>
|
||||
|
||||
<!-- 详情弹出层 -->
|
||||
<van-popup v-model="showDetailPopup" round closeable position="bottom" :style="{ height: '80%' }">
|
||||
<div class="detail-popup" v-if="currentSuggestion">
|
||||
<div class="detail-header">
|
||||
<div class="detail-title">{{ currentSuggestion.title || '意见反馈' }}</div>
|
||||
<div class="detail-meta">
|
||||
<span>{{ formatDate(currentSuggestion.submitTime) }}</span>
|
||||
<span :class="getStatusClass(currentSuggestion.isReply)">{{ getStatusText(currentSuggestion.isReply) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">提交人信息</div>
|
||||
<div class="submitter-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">姓名:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">工号:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterLoginName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">单位:</span>
|
||||
<span class="info-value">{{ currentSuggestion.submitterUnitName || '未填写' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">联系方式:</span>
|
||||
<span class="info-value">{{ currentSuggestion.concat || '未填写' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">意见内容</div>
|
||||
<div class="detail-content">{{ currentSuggestion.content }}</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentSuggestion.attachments && currentSuggestion.attachments.length > 0">
|
||||
<div class="detail-label">附件</div>
|
||||
<div class="detail-attachments">
|
||||
<div
|
||||
class="attachment-item"
|
||||
v-for="(file, idx) in currentSuggestion.attachments"
|
||||
:key="idx"
|
||||
@click.stop="previewImage(file.url, idx)"
|
||||
>
|
||||
<img :src="file.url" class="attachment-image" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentSuggestion.isReply">
|
||||
<div class="detail-label">回复</div>
|
||||
<div class="detail-reply detail-content">{{ currentSuggestion.replyContent }}</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="detail-section">
|
||||
<div class="detail-label">回复</div>
|
||||
<div class="no-reply">
|
||||
<van-icon name="chat-o" class="no-reply-icon" />
|
||||
<div class="no-reply-text">暂无回复,请耐心等待</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
refreshing: false,
|
||||
loading: false,
|
||||
finished: false,
|
||||
showDetailPopup: false,
|
||||
currentSuggestion: null,
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
suggestions: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
this.loading = true
|
||||
$.post("/platform/suggestionBox/pageData", {
|
||||
pageNumber: this.pageNumber,
|
||||
pageSize: this.pageSize
|
||||
})
|
||||
.done((res) => {
|
||||
if (res.code === 0 && res.data) {
|
||||
if (this.pageNumber === 1) {
|
||||
this.suggestions = res.data.list || []
|
||||
} else {
|
||||
this.suggestions = this.suggestions.concat(res.data.list || [])
|
||||
}
|
||||
|
||||
this.finished = !res.data.list || res.data.list.length < this.pageSize
|
||||
} else {
|
||||
this.finished = true
|
||||
}
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
})
|
||||
.fail(() => {
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
this.finished = true
|
||||
})
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onRefresh() {
|
||||
this.pageNumber = 1
|
||||
this.finished = false
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 上拉加载更多
|
||||
loadMore() {
|
||||
this.pageNumber++
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 查看详情
|
||||
showDetail(suggestion) {
|
||||
if (suggestion.attachments && typeof suggestion.attachments === "string") {
|
||||
try {
|
||||
suggestion.attachments = JSON.parse(suggestion.attachments)
|
||||
} catch (e) {
|
||||
suggestion.attachments = []
|
||||
}
|
||||
}
|
||||
this.currentSuggestion = suggestion
|
||||
this.showDetailPopup = true
|
||||
},
|
||||
|
||||
// 获取状态class
|
||||
getStatusClass(isReply) {
|
||||
if (!isReply || isReply === 0) return "status-pending"
|
||||
if (isReply === 1) return "status-processing"
|
||||
return "status-completed"
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(isReply) {
|
||||
if (!isReply || isReply === 0) return "待处理"
|
||||
if (isReply === 1) return "处理中"
|
||||
return "已处理"
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ""
|
||||
const date = new Date(dateStr)
|
||||
return date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0") + "-" + String(date.getDate()).padStart(2, "0")
|
||||
},
|
||||
|
||||
// 前往提交意见页面
|
||||
goToSubmitPage() {
|
||||
window.location.href = "/platform/suggestionBox/h5/write"
|
||||
},
|
||||
|
||||
// 预览图片
|
||||
previewImage(url, index) {
|
||||
if (!url) return
|
||||
|
||||
// 创建图片查看器
|
||||
const urls = this.currentSuggestion.attachments.map((file) => file.url)
|
||||
this.$imagePreview({
|
||||
images: urls,
|
||||
startPosition: index
|
||||
})
|
||||
},
|
||||
|
||||
// 撤销
|
||||
onRevoke(item) {
|
||||
this.$dialog
|
||||
.confirm({
|
||||
title: "提示",
|
||||
message: "您确认要撤销吗?"
|
||||
})
|
||||
.then(() => {})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#}#-->
|
||||
@@ -0,0 +1,363 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.task-form-container {
|
||||
padding: 16px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-title::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background-color: rgb(0, 78, 100);
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
width: 80px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
background-color: #f9f9f9;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
margin-top: 8px;
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background-color: #fff7e6;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.reply-form {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.reply-textarea {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar title="意见箱处理" left-arrow @click-left="goBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="task-form-container">
|
||||
<!-- 加载状态 -->
|
||||
<van-empty v-if="loading" description="加载中...">
|
||||
<template #image>
|
||||
<van-loading type="spinner" size="36" />
|
||||
</template>
|
||||
</van-empty>
|
||||
|
||||
<template v-else>
|
||||
<!-- 业务表单数据 -->
|
||||
<div class="card">
|
||||
<div class="card-title">基本信息</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">提交人:</span>
|
||||
<span class="info-value">{{ suggestionData.submitterName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">工号:</span>
|
||||
<span class="info-value">{{ suggestionData.submitterLoginName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">单位:</span>
|
||||
<span class="info-value">{{ suggestionData.submitterUnitName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">联系方式:</span>
|
||||
<span class="info-value">{{ suggestionData.concat }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">提交时间:</span>
|
||||
<span class="info-value">{{ formatDate(suggestionData.submitTime) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">标题:</span>
|
||||
<span class="info-value">{{ suggestionData.title || '无标题' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">内容:</span>
|
||||
</div>
|
||||
<div class="content-box">{{ suggestionData.content }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 流程信息 -->
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
流程信息
|
||||
<span class="status-tag" :class="getStatusClass()">{{ getStatusText() }}</span>
|
||||
</div>
|
||||
<div class="timeline">
|
||||
<van-steps direction="vertical" :active="flowHistory.length - 1">
|
||||
<van-step v-for="(item, index) in flowHistory" :key="index">
|
||||
<h3>{{ item.taskName }}</h3>
|
||||
<p>处理人: {{ item.assigneeName || '系统' }}</p>
|
||||
<p>开始时间: {{ formatDate(item.startTime) }}</p>
|
||||
<p v-if="item.endTime">完成时间: {{ formatDate(item.endTime) }}</p>
|
||||
<p v-if="item.comment">处理意见: {{ item.comment }}</p>
|
||||
</van-step>
|
||||
</van-steps>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 回复表单 -->
|
||||
<div class="card" v-if="!taskCompleted && currentTask">
|
||||
<div class="card-title">处理意见</div>
|
||||
<div class="reply-form">
|
||||
<textarea v-model="replyForm.replyContent" class="reply-textarea"
|
||||
placeholder="请输入回复内容"></textarea>
|
||||
<div class="btn-container">
|
||||
<van-button type="primary" block round @click="submitReply">提交回复</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已回复信息 -->
|
||||
<div class="card" v-if="suggestionData.isReply">
|
||||
<div class="card-title">回复信息</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">回复人:</span>
|
||||
<span class="info-value">{{ suggestionData.replyUserName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">回复时间:</span>
|
||||
<span class="info-value">{{ formatDate(suggestionData.replyTime) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">回复内容:</span>
|
||||
</div>
|
||||
<div class="content-box">{{ suggestionData.replyContent }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
taskId: "",
|
||||
businessKey: "",
|
||||
processInstanceId: "",
|
||||
suggestionData: {},
|
||||
flowHistory: [],
|
||||
currentTask: null,
|
||||
taskCompleted: false,
|
||||
replyForm: {
|
||||
id: "",
|
||||
replyContent: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 获取URL参数
|
||||
this.taskId = GetQueryString("taskId")
|
||||
this.businessKey = GetQueryString("businessKey")
|
||||
this.processInstanceId = GetQueryString("processInstanceId")
|
||||
|
||||
// 加载数据
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
history.back()
|
||||
},
|
||||
|
||||
// 加载数据
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
try {
|
||||
// 1. 获取业务数据
|
||||
await this.loadSuggestionData()
|
||||
|
||||
// 2. 获取流程历史
|
||||
await this.loadFlowHistory()
|
||||
|
||||
// 3. 获取当前任务信息
|
||||
if (this.taskId) {
|
||||
await this.loadTaskInfo()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载数据失败:", error)
|
||||
this.$toast.fail("加载数据失败")
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 加载意见箱数据
|
||||
async loadSuggestionData() {
|
||||
if (!this.businessKey) return
|
||||
|
||||
const {
|
||||
code,
|
||||
data
|
||||
} = await $.post("/platform/suggestionBox/admin/getSuggestionById", { id: this.businessKey })
|
||||
if (code === 0 && data) {
|
||||
this.suggestionData = data
|
||||
this.replyForm.id = data.id
|
||||
}
|
||||
},
|
||||
|
||||
// 加载流程历史
|
||||
async loadFlowHistory() {
|
||||
if (!this.processInstanceId) return
|
||||
|
||||
const {
|
||||
code,
|
||||
data
|
||||
} = await $.post("/platform/workflow/task/getProcessHistory", { processInstanceId: this.processInstanceId })
|
||||
if (code === 0 && data) {
|
||||
this.flowHistory = data
|
||||
}
|
||||
},
|
||||
|
||||
// 加载任务信息
|
||||
async loadTaskInfo() {
|
||||
const { code, data } = await $.post("/platform/workflow/task/getTaskInfo", { taskId: this.taskId })
|
||||
if (code === 0 && data) {
|
||||
this.currentTask = data
|
||||
this.taskCompleted = data.endTime != null
|
||||
}
|
||||
},
|
||||
|
||||
// 提交回复
|
||||
async submitReply() {
|
||||
if (!this.replyForm.replyContent.trim()) {
|
||||
this.$toast("请输入回复内容")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 保存业务数据并完成任务
|
||||
const result = await $.post("/platform/suggestionBox/admin/reply", {
|
||||
reply: JSON.stringify(this.replyForm),
|
||||
taskId: this.taskId
|
||||
})
|
||||
|
||||
if (result.code === 0) {
|
||||
this.$toast.success("处理成功")
|
||||
// 重新加载数据
|
||||
setTimeout(() => {
|
||||
this.loadData()
|
||||
}, 1000)
|
||||
} else {
|
||||
this.$toast.fail(result.msg || "处理失败")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("提交回复失败:", error)
|
||||
this.$toast.fail("提交回复失败")
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态样式
|
||||
getStatusClass() {
|
||||
if (this.flowHistory.length === 0) return "status-pending"
|
||||
|
||||
const lastTask = this.flowHistory[this.flowHistory.length - 1]
|
||||
if (!lastTask.endTime) return "status-processing"
|
||||
return "status-completed"
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText() {
|
||||
if (this.flowHistory.length === 0) return "未开始"
|
||||
|
||||
const lastTask = this.flowHistory[this.flowHistory.length - 1]
|
||||
if (!lastTask.endTime) return "处理中"
|
||||
return "已完成"
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ""
|
||||
const date = new Date(dateStr)
|
||||
return date.getFullYear() + "-" +
|
||||
String(date.getMonth() + 1).padStart(2, "0") + "-" +
|
||||
String(date.getDate()).padStart(2, "0") + " " +
|
||||
String(date.getHours()).padStart(2, "0") + ":" +
|
||||
String(date.getMinutes()).padStart(2, "0")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,294 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
background-color: #f6f6f6;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: rgb(0, 78, 100);
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.section-title span {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
width: 80px;
|
||||
color: #333;
|
||||
padding: 8px 8px 8px 0;
|
||||
}
|
||||
|
||||
.input-control {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.input-control input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
text-align: right;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.textarea-container {
|
||||
padding: 10px 16px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.textarea-container textarea {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.word-count {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.upload-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.upload-item,
|
||||
.upload-btn {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.uploaded-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0 0 0 4px;
|
||||
}
|
||||
|
||||
.submit-area {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-sticky>
|
||||
<van-nav-bar title="我要提意见" left-arrow left-text="返回" @click-left="history.go(-1)"></van-nav-bar>
|
||||
</van-sticky>
|
||||
|
||||
<div class="form-container">
|
||||
<div class="form-section">
|
||||
<div class="section-title">
|
||||
<div class="dot"></div>
|
||||
<span>填写人信息</span>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<div class="input-label">姓名</div>
|
||||
<div class="input-control">
|
||||
<input type="text" v-model="formData.submitterName" placeholder="请输入姓名" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<div class="input-label">工号</div>
|
||||
<div class="input-control">
|
||||
<input type="text" v-model="formData.submitterLoginName" placeholder="请输入工号" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<div class="input-label">手机号码</div>
|
||||
<div class="input-control">
|
||||
<input type="tel" v-model="formData.concat" placeholder="请输入手机号码" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<div class="input-label">所在单位</div>
|
||||
<div class="input-control">
|
||||
<input type="text" v-model="formData.submitterUnitName" placeholder="请输入所在单位" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-title">
|
||||
<div class="dot"></div>
|
||||
<span>填写标题</span>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<div class="input-label">标题</div>
|
||||
<div class="input-control">
|
||||
<input type="text" v-model="formData.title" placeholder="请输入意见标题" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-title">
|
||||
<div class="dot"></div>
|
||||
<span>填写意见建议内容</span>
|
||||
</div>
|
||||
<div class="textarea-container">
|
||||
<textarea v-model="formData.content" placeholder="请描述您要填写的意见建议内容..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-title">
|
||||
<div class="dot"></div>
|
||||
<span>照片上传</span>
|
||||
</div>
|
||||
|
||||
<div class="upload-area">
|
||||
<!-- <vant-file-upload :files.sync="formData.attachments" :max="15"></vant-file-upload>-->
|
||||
<!-- <div class="upload-grid">-->
|
||||
<!-- <div class="upload-item" v-for="(item, index) in formData.fileList" :key="index">-->
|
||||
<!-- <img :src="item.content || item.url" class="uploaded-image">-->
|
||||
<!-- <div class="delete-btn" @click="deleteImage(index)">-->
|
||||
<!-- <van-icon name="cross" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="upload-btn" v-if="formData.fileList.length < 3" @click="triggerUpload">-->
|
||||
<!-- <input type="file" ref="fileInput" style="display:none" accept="image/*" @change="onFileChange" multiple>-->
|
||||
<!-- <van-icon name="plus" size="24" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="submit-area">
|
||||
<van-button @click="submitForm" style="border-radius: 10px" block type="info">提 交</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
name: "",
|
||||
mobile: "",
|
||||
idCard: "",
|
||||
department: "",
|
||||
content: "",
|
||||
address: "",
|
||||
fileList: []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
history.back()
|
||||
},
|
||||
|
||||
async submitForm() {
|
||||
if (!this.formData.concat.trim()) {
|
||||
this.$toast("请输入手机号码")
|
||||
return
|
||||
}
|
||||
//正则验证
|
||||
if (!/^1[3-9]\d{9}$/.test(this.formData.concat)) {
|
||||
this.$toast("请输入正确的手机号码")
|
||||
return
|
||||
}
|
||||
if (!this.formData.content.trim()) {
|
||||
this.$toast("请输入投诉内容")
|
||||
return
|
||||
}
|
||||
this.$dialog
|
||||
.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要提交吗?"
|
||||
})
|
||||
.then(async () => {
|
||||
const { code, data, msg } = await $.post("/platform/suggestionBox/submit", { suggestion: JSON.stringify(this.formData) })
|
||||
if (code === 0) {
|
||||
this.$toast.success("提交成功")
|
||||
setTimeout(() => {
|
||||
// history.back()
|
||||
}, 200)
|
||||
} else {
|
||||
this.$toast(msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const id = GetQueryString("id")
|
||||
if (!id) {
|
||||
this.$set(this.formData, "submitterId", "${@auth.getPrincipalProperty('id')}")
|
||||
this.$set(this.formData, "submitterName", "${@auth.getPrincipalProperty('username')}")
|
||||
this.$set(this.formData, "submitterLoginName", "${@auth.getPrincipalProperty('loginname')}")
|
||||
this.$set(this.formData, "submitterUnitId", "${@auth.getPrincipalProperty('unitid')}")
|
||||
this.$set(this.formData, "submitterUnitName", "${@auth.getPrincipalProperty('unit').getName()}")
|
||||
this.$set(this.formData, "submitterUnionId", "${@auth.getPrincipalProperty('union').getId()}")
|
||||
this.$set(this.formData, "submitterUnionName", "${@auth.getPrincipalProperty('union').getName()}")
|
||||
this.$set(this.formData, "concat", "${@auth.getPrincipalProperty('mobile')}")
|
||||
console.log(this.formData)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#}#-->
|
||||
Reference in New Issue
Block a user