This commit is contained in:
那些花儿
2025-07-30 15:19:03 +08:00
parent 65b7960783
commit 73974dbdd3
32 changed files with 1403 additions and 388 deletions
@@ -664,7 +664,6 @@ td.no-b {
}
.flow-task-form .el-descriptions-item__label.is-bordered-label {
color: rgb(102, 102, 102) !important;
background: rgb(248, 249, 250) !important;
padding: 8px 12px !important;
border: 1px solid rgb(221, 221, 221) !important;
@@ -36,7 +36,7 @@
<div class="panel-content">
<div class="info-table">
<!-- 新申请场景 -->
<template v-if="isNewApplication">
<template v-if="isNewApplication || (processInstance && processInstance.state === 30)">
<div class="info-row">
<div class="info-label">流程名称</div>
<div class="info-value">{{ processDefinition.displayName || processDefinition.name }}</div>
@@ -82,13 +82,20 @@
</div>
<!-- 表单处理区域 -->
<div class="form-panel" v-if="isNewApplication || todoTasks.map((v) => v.id).includes(taskId)">
<div
class="form-panel"
v-if="isNewApplication || (processInstance && processInstance.state === 30) || todoTasks.map((v) => v.id).includes(taskId)"
>
<div class="panel-header">
<h3 class="panel-title">{{ formPanelTitle }}</h3>
</div>
<div class="panel-content">
<!-- 新申请表单 -->
<div v-if="isNewApplication" id="application-form-container" class="form-content">
<div
v-if="isNewApplication || (processInstance && processInstance.state === 30)"
id="application-form-container"
class="form-content"
>
<div v-if="pjaxLoading.apply" class="loading-state">
<i class="el-icon-loading"></i>
<span>正在加载申请表单...</span>
@@ -121,7 +128,72 @@
</div>
</template>
<script>
<script type="text/javascript">
class PjaxSync {
constructor() {
this.isLoading = false
this.queue = []
}
async request(url, options = {}) {
return new Promise((resolve, reject) => {
const requestData = { url, options, resolve, reject }
if (this.isLoading) {
this.queue.push(requestData)
return
}
this._executeRequest(requestData)
})
}
_executeRequest({ url, options, resolve, reject }) {
this.isLoading = true
const defaultOptions = {
timeout: 10000,
push: false,
replace: false,
...options
}
const successHandler = (event, data, status, xhr) => {
this._cleanup()
resolve({ data, status, xhr })
this._processQueue()
}
const errorHandler = (event, xhr, textStatus, errorThrown) => {
this._cleanup()
reject(new Error(textStatus || "PJAX request failed"))
this._processQueue()
}
$(document).one("pjax:success", successHandler)
$(document).one("pjax:error", errorHandler)
$.pjax({
url: url,
...defaultOptions
})
}
_cleanup() {
this.isLoading = false
$(document).off("pjax:success pjax:error")
}
_processQueue() {
if (this.queue.length > 0) {
const nextRequest = this.queue.shift()
this._executeRequest(nextRequest)
}
}
}
// 创建全局实例
const pjaxSync = new PjaxSync()
module.exports = {
name: "SnakerFlow",
data() {
@@ -221,7 +293,7 @@ module.exports = {
} else if (this.shouldShowTaskForm) {
return this.currentTask.displayName || "任务表单"
} else if (this.shouldShowHistoryProcess) {
return "办理过程"
return "申请表单"
} else {
return "表单信息"
}
@@ -236,7 +308,6 @@ module.exports = {
// 是否显示任务表单
shouldShowTaskForm() {
debugger
return this.taskId != null
},
@@ -277,20 +348,20 @@ module.exports = {
},
created() {
this.initComponent()
// 监听
window.addEventListener("message", (event) => {
// 监听消息
new BroadcastChannel("zhgh-global-channel").addEventListener("message", (event) => {
if (event.data.type === "task-complete") {
this.initComponent()
}
})
},
beforeDestroy() {
console.log("beforeDestroy")
},
methods: {
// 初始化组件
async initComponent() {
this.loading = true
debugger
if (this.isNewApplication) {
// 新申请:加载流程定义信息
await this.loadProcessDefinition()
@@ -299,6 +370,12 @@ module.exports = {
} else if (this.isExistingProcess) {
// 已有流程:加载流程实例信息
await this.loadProcessInstance()
if (this.processInstance && this.processInstance.state === 30) {
// 流程被撤回了 此时加载申请表单
// 加载流程申请表单
await this.loadApplicationForm()
}
// 加载任务信息
await this.loadCurrentTask()
// 如果有businessId且不是第一个任务节点,加载历史办理过程
@@ -317,9 +394,11 @@ module.exports = {
// 加载流程定义信息
async loadProcessDefinition() {
if (!this.defineKey) return
try {
const response = await $.get("/flow/common/defineInfo", { defineKey: this.defineKey })
const response = await $.get("/flow/common/defineInfo", {
defineKey: this.defineKey,
instanceId: this.instanceId
})
if (response.code === 0) {
this.processDefinition = response.data
}
@@ -334,20 +413,11 @@ module.exports = {
this.pjaxLoading.apply = true
$.pjax({
url: this.processDefinition.instanceUrl,
container: "#application-form-container",
push: false,
replace: false,
timeout: 10000
await pjaxSync.request(this.processDefinition.instanceUrl, {
container: "#application-form-container"
})
.done(() => {
this.pjaxLoading.apply = false
})
.fail(() => {
this.pjaxLoading.apply = false
console.log(this.processDefinition.instanceUrl, "申请表单加载失败")
})
this.pjaxLoading.apply = false
},
// 加载流程实例信息
@@ -356,6 +426,7 @@ module.exports = {
const { code, data, msg } = await $.get("/flow/common/instanceInfo", { instanceId: this.instanceId })
if (code === 0) {
this.processInstance = data.processInstance
this.processDefinition = data.processInstance?.jsonObject
this.todoTasks = data.todoTasks
}
},
@@ -381,43 +452,23 @@ module.exports = {
this.pjaxLoading.taskForm = true
$.pjax({
url: this.currentTask.taskModel.form,
container: "#task-form-container",
push: false,
replace: false,
timeout: 10000
await pjaxSync.request(this.currentTask.taskModel.form, {
container: "#task-form-container"
})
.done(() => {
this.pjaxLoading.taskForm = false
})
.fail(() => {
this.pjaxLoading.taskForm = false
console.log(this.currentTask.taskModel.form, "任务表单加载失败")
})
},
// 加载历史办理过程
async loadHistoryProcess() {
if (!this.businessId) return
const { instanceViewUrl } = this.processInstance.jsonObject
this.pjaxLoading.historyProcess = true
if (instanceViewUrl) {
$.pjax({
url: `${instanceViewUrl}?businessId=${this.businessId}&instanceId=${this.instanceId}`,
container: "#history-process-container",
push: false,
replace: false,
timeout: 10000
this.pjaxLoading.historyProcess = true
await pjaxSync.request(`${instanceViewUrl}?businessId=${this.businessId}&instanceId=${this.instanceId}`, {
container: "#history-process-container"
})
.done(() => {
this.pjaxLoading.historyProcess = false
})
.fail(() => {
this.pjaxLoading.historyProcess = false
console.log("历史办理过程加载失败")
})
this.pjaxLoading.historyProcess = false
}
},
@@ -149,8 +149,7 @@ module.exports = {
// 处理提交操作
handleSubmit() {
// 根据是否有流程实例来确定操作类型
// const submitAction = this.instanceId ? this.actionEnum.RE_APPLY : this.actionEnum.APPLY
const submitAction = this.actionEnum.APPLY
const submitAction = this.instanceId ? this.actionEnum.RE_APPLY : this.actionEnum.APPLY
// 触发父组件事件,传递提交操作
this.$emit("task-action", {
@@ -160,7 +159,6 @@ module.exports = {
defineId: this.defineId,
defineKey: this.defineKey,
businessId: this.businessId
// currentTask: this.taskInfo
})
},
@@ -201,7 +199,11 @@ module.exports = {
// 取消操作
handleCancel() {
this.$emit("cancel")
const func = () => window.close()
this.$emit("cancel", func)
if (!this.$listeners.cancel) {
func()
}
},
// 刷新任务信息
@@ -71,7 +71,7 @@
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
<!-- <script src="${base!}/assets/platform/plugins/snaker/SnakerflowDesigner.umd.min.js"></script>-->
<!-- <script src="${base!}/assets/platform/plugins/snaker/SnakerflowDesigner.umd.min.js"></script>-->
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
@@ -101,6 +101,11 @@
Vue.use(FcDesigner.formCreate)
</script>
<!--广播频道-->
<script type="text/javascript">
window.GlobalBroadcastChannel = new BroadcastChannel("zhgh-global-channel")
</script>
<!--ws-->
<script type="text/javascript">
class WebSocketPubSub {
@@ -334,12 +339,17 @@
Vue.component("excel-import", httpVueLoader("/components/plugins/sysImport/excelImport.vue?v=" + new Date().getTime()))
Vue.component("flow-form-button", httpVueLoader("/components/plugins/flowable/formButton.vue?v=" + new Date().getTime()))
Vue.component("snaker-flow", httpVueLoader("/components/plugins/snaker/snakerFlow.vue?v=" + new Date().getTime()))
Vue.component("snaker-flow-task-form-action", httpVueLoader("/components/plugins/snaker/snakerFlowTaskFormAction.vue?v=" + new Date().getTime()))
Vue.component("snaker-flow-history-approval", httpVueLoader("/components/plugins/snaker/snakerFlowHisApproval.vue?v=" + new Date().getTime()))
Vue.component(
"snaker-flow-task-form-action",
httpVueLoader("/components/plugins/snaker/snakerFlowTaskFormAction.vue?v=" + new Date().getTime())
)
Vue.component(
"snaker-flow-history-approval",
httpVueLoader("/components/plugins/snaker/snakerFlowHisApproval.vue?v=" + new Date().getTime())
)
</script>
<style>
.v4-header {
background-color: rgb(0, 109, 185);
box-shadow: 0 2px 10px rgba(0, 109, 185, 0.3);
@@ -555,6 +565,10 @@
<i class="fa fa-th-large"></i>
应用中心
</a>
<a href="/platform/v4/serv" data-pjax class="v4-nav-item">
<i class="fa fa-th-large"></i>
服务中心
</a>
<a href="/flow/todoCenter" data-pjax class="v4-nav-item">
<i class="fa fa-th-large"></i>
待办中心
@@ -0,0 +1,597 @@
<!--#
layout("/layouts/v4/baseLayout.html"){
#-->
<style>
.apps-hero {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
/*border-radius: 8px 8px 0 0;*/
margin-bottom: 0;
}
.apps-hero img {
width: 100%;
height: 100%;
}
.apps-container {
display: flex;
/*height: calc(100vh - 64px - 48px - 180px);*/
background-color: #fff;
/*border-radius: 0 0 8px 8px;*/
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
flex: 1;
}
.apps-sidebar {
width: 220px;
background-color: #f7f7f7;
border-right: 1px solid #e8e8e8;
overflow-y: auto;
}
.apps-sidebar-item {
padding: 16px 20px;
cursor: pointer;
transition: all 0.3s;
font-size: 16px;
display: flex;
align-items: center;
gap: 10px;
}
.apps-sidebar-item.active {
background-color: #e6f7ff;
color: var(--color-primary);
border-right: 2px solid #1890ff;
}
.apps-sidebar-item:hover:not(.active) {
background-color: #f0f0f0;
}
.apps-content {
flex: 1;
padding: 20px;
overflow: auto;
display: flex;
flex-direction: column;
}
.apps-header {
padding: 0 0 20px 0;
border-bottom: 1px solid #eee;
margin-bottom: 20px;
}
.apps-search-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.apps-search {
position: relative;
width: 400px;
}
.apps-search input {
width: 100%;
height: 36px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 15px;
font-size: 14px;
box-sizing: border-box;
}
.apps-search .search-icon {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: #999;
cursor: pointer;
}
.apps-filter {
margin-top: 20px;
}
.apps-filter-title {
color: #666;
font-size: 14px;
margin-bottom: 5px;
}
.apps-filter-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.filter-tag {
padding: 5px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.filter-tag.active {
background-color: var(--color-primary);
color: #fff;
}
.filter-tag:not(.active) {
background-color: #f0f0f0;
color: #333;
}
.filter-tag:not(.active):hover {
background-color: #e0e0e0;
}
.apps-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
flex: 1;
overflow-y: auto;
grid-auto-rows: min-content;
padding-top: 5px;
}
.app-card {
border: 1px solid #e8e8e8;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s;
display: flex;
align-items: center;
padding: 15px;
position: relative;
}
.app-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
cursor: pointer;
}
.app-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
color: #fff;
font-size: 24px;
}
.app-icon img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 8px;
}
.app-icon i {
color: var(--color-primary);
font-size: 38px;
}
.favorite-icon {
position: absolute;
bottom: 10px;
right: 10px;
cursor: pointer;
transition: all 0.3s;
font-size: 18px;
color: #c0c4cc;
}
.favorite-icon.active {
color: #ff9800;
}
.favorite-icon:hover {
transform: scale(1.2);
}
.app-info {
flex: 1;
}
.app-title {
font-weight: 500;
margin-bottom: 5px;
font-size: 14px;
}
.app-desc {
color: #999;
font-size: 12px;
}
.alphabet-filter {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.alphabet-item {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
font-size: 14px;
}
.alphabet-item.active {
background-color: var(--color-primary);
color: #fff;
}
.alphabet-item:not(.active) {
background-color: #f0f0f0;
}
.alphabet-item:hover:not(.active) {
background-color: #e0e0e0;
}
.app-status {
position: absolute;
top: 0;
right: 0;
background-color: #ff9800;
color: #fff;
padding: 2px 10px;
font-size: 12px;
border-bottom-left-radius: 8px;
}
.apps-wrapper {
/*margin: 0 auto;*/
/*margin: 24px;*/
/*padding: 24px;*/
display: flex;
flex-direction: column;
height: calc(100vh - 64px);
}
.no-results {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
padding: 40px 20px;
}
.no-results-content {
text-align: center;
max-width: 300px;
}
.no-results i {
color: #c0c4cc;
margin-bottom: 16px;
}
.no-results .title {
font-size: 16px;
color: #606266;
font-weight: 500;
margin-bottom: 8px;
}
.no-results .hint {
font-size: 14px;
color: #909399;
line-height: 1.5;
}
.page-item.disabled {
color: #c0c4cc;
cursor: not-allowed;
}
.page-item.disabled:hover {
border-color: #d9d9d9;
color: #c0c4cc;
}
.loading-container {
text-align: center;
padding: 40px 0;
color: #909399;
font-size: 14px;
}
</style>
<div class="apps-wrapper" id="app">
<div class="apps-hero">
<img src="https://i.cug.edu.cn/data/sys-attach/download/1i8hmpaqdwmvwajjvw2c8isi61jj0gkmosw0" alt="应用中心" />
</div>
<div class="apps-container">
<!-- Left Sidebar -->
<div class="apps-sidebar">
<div
v-for="category in allCategories"
:key="category.id"
:class="['apps-sidebar-item', activeCategory === category.id ? 'active' : '']"
@click="changeCategory(category.id)"
>
<i :class="category.icon"></i>
{{ category.name }}
</div>
</div>
<!-- Main Content -->
<div class="apps-content">
<div class="apps-header">
<div class="apps-search-container">
<div class="apps-search">
<input type="text" v-model="searchKeyword" @keyup.enter="search" placeholder="请输入内容" />
<i class="el-icon-search search-icon" @click="search"></i>
</div>
</div>
<!-- 字母过滤 -->
<div class="apps-filter">
<div class="apps-filter-title">首字母:</div>
<div class="apps-filter-tags alphabet-filter">
<div :class="['filter-tag', activeAlphabet === '' ? 'active' : '']" @click="changeAlphabet('')">全部</div>
<div
v-for="letter in alphabets"
:key="letter"
:class="['alphabet-item', activeAlphabet === letter ? 'active' : '']"
@click="changeAlphabet(letter)"
>
{{ letter }}
</div>
</div>
</div>
</div>
<!-- 应用网格 -->
<div class="apps-grid" v-if="applications.length > 0">
<div v-for="app in applications" :key="app.id" class="app-card" @click.stop="openApp(app)">
<div :class="['app-icon', app.iconType]">
<i v-if="app.icon" :class="app.icon"></i>
<i v-else class="fa fa-skype"></i>
</div>
<div class="app-info">
<div class="app-title">{{ app.displayName }}</div>
</div>
</div>
</div>
<!-- 加载状态 -->
<div class="loading-container" v-if="loading">
<i class="el-icon-loading"></i>
<p>加载中...</p>
</div>
<!-- 无搜索结果 -->
<div class="no-results" v-else-if="applications.length === 0">
<div class="no-results-content">
<i class="el-icon-document" style="font-size: 48px"></i>
<p class="title">没有找到匹配的服务</p>
<p class="hint">请尝试不同的搜索词或筛选条件</p>
</div>
</div>
</div>
</div>
</div>
<script>
new Vue({
el: "#app",
data: {
// 应用列表
applications: [],
// 分类列表
categories: [{ id: "all", name: "全部服务", icon: "fa fa-th-large" }],
// 动态加载的分类
dynamicCategories: [],
// 当前选中的分类
activeCategory: "all",
// 字母表筛选
alphabets: [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z"
],
activeAlphabet: "",
// 搜索关键词
searchKeyword: "",
// 分页相关
currentPage: 1,
pageSize: 15,
totalPage: 1,
total: 0,
// 加载状态
loading: true
},
computed: {
// 所有分类(固定分类 + 动态分类)
allCategories() {
return [...this.categories, ...this.dynamicCategories]
}
},
mounted() {
// 页面加载时获取分类和应用数据
this.loadCategories()
this.loadApps()
},
methods: {
// 加载分类数据
loadCategories() {
$.get("/platform/v4/serv/categories")
.then((result) => {
if (result.code === 0) {
this.dynamicCategories = result.data || []
} else {
console.error("获取应用分类失败:", result.msg)
}
})
.fail((error) => {
console.error("获取应用分类异常:", error)
})
},
// 加载应用数据
loadApps() {
this.loading = true
// 构建请求参数
const params = {
categoryId:
this.activeCategory === "all"
? ""
: this.activeCategory === "favorites" || this.activeCategory === "recommended"
? this.activeCategory
: this.activeCategory,
letter: this.activeAlphabet,
keyword: this.searchKeyword
}
// 将参数转换为URL查询参数
const queryString = Object.keys(params)
.filter((key) => params[key] !== null && params[key] !== undefined && params[key] !== "")
.map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(params[key]))
.join("&")
// 如果是"我的收藏"分类
if (this.activeCategory === "favorites") {
this.loadFavorites()
return
}
// 发送请求获取普通应用列表
$.get("/platform/v4/serv/list?" + queryString)
.then((result) => {
if (result.code === 0) {
this.applications = result.data || []
} else {
console.error("获取应用列表失败:", result.msg)
}
this.loading = false
})
.fail((error) => {
console.error("获取应用列表异常:", error)
this.loading = false
})
},
// 加载收藏的应用
loadFavorites() {
$.get("/platform/v4/serv/favorite")
.then((result) => {
if (result.code === 0) {
this.applications = result.data || []
} else {
console.error("获取收藏应用失败:", result.msg)
}
})
.fail((error) => {
console.error("获取收藏应用异常:", error)
})
},
// 切换应用收藏状态
toggleFavorite(appId) {
const app = this.applications.find((a) => a.id === appId)
if (!app) return
const url = app.isFavorite ? "/platform/v4/serv/removeFavorite" : "/platform/v4/serv/addFavorite"
const params = { appId: appId }
$.post(url, params)
.then((result) => {
if (result.code === 0) {
app.isFavorite = !app.isFavorite
this.$message.success(app.isFavorite ? "收藏成功" : "取消收藏成功")
} else {
console.error(app.isFavorite ? "取消收藏失败:" : "收藏失败:", result.msg)
}
})
.fail((error) => {
console.error(app.isFavorite ? "取消收藏异常:" : "收藏异常:", error)
})
},
// 切换应用分类
changeCategory(categoryId) {
this.activeCategory = categoryId
this.currentPage = 1
// 如果是"我的收藏"分类
if (categoryId === "favorites") {
this.loadFavorites()
} else {
this.loadApps()
}
},
// 切换应用字母
changeAlphabet(letter) {
this.activeAlphabet = letter
this.currentPage = 1
this.loadApps()
},
// 搜索
search() {
this.loadApps()
},
// 打开应用
openApp(app) {
console.log(app)
// 新标签页打开
window.open("/flow/common/approval/form?defineKey=" + app.name, "_blank")
}
}
})
</script>
<!--#
}
#-->
@@ -84,7 +84,8 @@ layout("/layouts/platform.html"){
<el-input v-model="formData.h5InstanceViewUrl" placeholder="请输入手机端发起地址"></el-input>
</el-form-item>
<el-form-item label="图标" prop="icon">
<el-input v-model="formData.icon" placeholder="请输入图标"></el-input>
<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>
<template #footer>
@@ -201,11 +202,11 @@ layout("/layouts/platform.html"){
},
listCategory() {
// this.$axios.get("/platform/warmFlow/category/list").then((res) => {
// if (res.code === 0) {
// this.categoryOptions = res.data
// }
// })
this.$axios.post("/flow/category/list").then((res) => {
if (res.code === 0) {
this.categoryOptions = res.data
}
})
}
},
created() {
@@ -2,172 +2,6 @@
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;
@@ -369,20 +203,138 @@ layout("/layouts/v4/baseLayout.html"){
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>
<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 categoryOptions" :key="type.id" :label="type.name" :value="type.id"></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'}">
<el-table-column prop="processInstanceName" label="流程名称" min-width="180" show-overflow-tooltip>
<template slot-scope="scope">
<span class="task-title">{{scope.row.variable?.instanceName}}</span>
</template>
</el-table-column>
<el-table-column prop="taskName" label="任务节点" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="categoryName" label="流程分类" min-width="150" show-overflow-tooltip>
<template slot-scope="{row}">{{categoryOptions.find(item => item.id === row.category)?.name}}</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="100px" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="openView(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>
</div>
</div>
<script>
new Vue({
el: "#app",
@@ -400,7 +352,7 @@ layout("/layouts/v4/baseLayout.html"){
},
// 流程类型选项
processTypes: [],
categoryOptions: [],
// 任务列表
tasks: [],
@@ -436,9 +388,7 @@ layout("/layouts/v4/baseLayout.html"){
INTERRUPT: 40,
PENDING: 50,
ABANDON: 99
},
}
}
},
@@ -450,12 +400,18 @@ layout("/layouts/v4/baseLayout.html"){
created() {
this.initData()
// 监听
window.GlobalBroadcastChannel.addEventListener("message", (event) => {
if (event.data.type === "task-complete") {
this.initData()
}
})
},
methods: {
// 初始化数据
async initData() {
await Promise.all([this.getStatistics(), this.getProcessTypes(), this.getTasks()])
await Promise.all([this.getStatistics(), this.listCategory(), this.getTasks()])
},
// 获取统计数据
@@ -475,16 +431,12 @@ layout("/layouts/v4/baseLayout.html"){
},
// 获取流程类型
async getProcessTypes() {
try {
const res = await $.post("/platform/warmFlow/todoCenter/category")
async listCategory() {
this.$axios.post("/flow/category/list").then((res) => {
if (res.code === 0) {
this.processTypes = res.data
this.categoryOptions = res.data
}
} catch (error) {
this.$message.error("获取流程类型失败")
console.error("获取流程类型失败:", error)
}
})
},
// 获取任务列表
@@ -549,31 +501,12 @@ layout("/layouts/v4/baseLayout.html"){
},
// 处理任务
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"
async openView(task) {
const { taskId, taskState, instanceId, businessNo } = task
if (taskState === this.taskStateEnum.DOING) {
window.open("/flow/common/approval/form?instanceId=" + instanceId + "&taskId=" + taskId + "&businessId=" + businessNo)
} else {
window.open("/flow/common/approval/form?instanceId=" + instanceId + "&businessId=" + businessNo)
}
},
@@ -589,11 +522,6 @@ layout("/layouts/v4/baseLayout.html"){
default:
return "暂无数据"
}
},
// 表格行类名
tableRowClassName({ row, rowIndex }) {
return ""
}
}
})
@@ -17,7 +17,7 @@
</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>
<snaker-flow-task-form-action @task-action="handleTaskAction" @save-draft="handleSaveDraft"></snaker-flow-task-form-action>
</div>
<script>
@@ -27,6 +27,7 @@
data() {
return {
businessId: GetQueryString("businessId"),
instanceId: GetQueryString("instanceId"),
formData: {},
formRules: {
title: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
@@ -39,20 +40,54 @@
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" }, "*")
}
})
if (!this.instanceId) {
this.handleApply(val)
} else {
this.handleReApply(val)
}
}
})
},
// 提交申请
handleApply(val) {
this.$axios
.post("/flow/common/startInstanceAndExecute", {
...val,
bizData: JSON.stringify(this.formData)
})
.then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
window.GlobalBroadcastChannel.postMessage({
type: "task-complete"
})
}
})
},
// 重新提交申请
handleReApply(val) {
this.$axios
.post("/flow/common/executeTask", {
data: JSON.stringify({
processTaskId: val.taskId,
processInstanceId: val.instanceId,
submitType: val.submitType,
f_data: JSON.stringify(this.formData)
})
})
.then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
window.GlobalBroadcastChannel.postMessage({
type: "task-complete"
})
}
})
},
// 保存申请
handleSaveDraft(val) {
this.$refs.formRef.validateField(["title"], (errMsg) => {
if (errMsg) {
@@ -78,12 +113,15 @@
})
})
},
handleCancel() {
// 关闭浏览器窗口
window.close()
},
// 初始化
init() {
if (this.businessId) {
this.$axios.post("/platform/suggestionBox/view/info", { id: this.businessId }).then((res) => {
if (res.code === 0) {
this.formData = res.data
}
})
} else {
const { username, loginname, id, unit, union, mobile } = this.$store.state.user
this.formData = {
@@ -33,12 +33,12 @@ layout("/layouts/platform.html"){
</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="row.taskKey === 'apply' || !row.instanceId" @click="onEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button v-if="['waiting'].includes(row.flow_status)" size="mini" type="danger" @click="onRevoke(row.id)"></el-button>
<el-button v-if="row.taskKey === 'apply' || !row.instanceId" @click="onDelete(row.id)" size="mini" type="danger">
删除
</el-button>
<el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskKey === 'startTask'" @click="onEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button v-if="row.canRevoke" @click="onRevoke(row)" size="mini" type="danger"></el-button>
<!-- <el-button v-if="row.instanceState === 10" size="mini" type="danger" @click="onWithDraw(row)">撤销</el-button>-->
<!-- v-if="row.instanceState === 30"-->
<el-button @click="onDelete(row.id)" size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -58,7 +58,9 @@ layout("/layouts/platform.html"){
}
},
methods: {
onOpen(id) {},
openView(row) {
window.open("/flow/common/approval/form?" + "instanceId=" + (row.instanceId || "") + "&businessId=" + row.id + "&defineKey=XWTG")
},
onEdit(row) {
window.open(
"/flow/common/approval/form?taskId=" +
@@ -70,8 +72,50 @@ layout("/layouts/platform.html"){
"&defineKey=XWTG"
)
},
onRevoke(id) {},
onDelete(id) {}
onRevoke(row) {
this.$confirm("您确定要撤回吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", { taskId: row.startTaskId }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
})
},
onWithDraw({ instanceId }) {
this.$confirm("您确定要撤销申请吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/flow/common/withdrawInstance", { instanceId }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
})
},
onDelete(id) {
this.$confirm("您确定要删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/suggestionBox2/apply/delete", { id }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
})
}
},
created() {
this.pageData()
@@ -5,7 +5,7 @@
<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="标题" :span="2">{{viewData.viewData}}</el-descriptions-item>
<el-descriptions-item label="标题" :span="2">{{viewData.title}}</el-descriptions-item>
<el-descriptions-item label="填写意见建议内容" :span="2">{{viewData.content}}</el-descriptions-item>
</el-descriptions>
@@ -21,6 +21,7 @@
methods: {
handleTaskAction(val) {
console.log(val)
this.$axios
.post("/flow/common/executeTask", {
data: JSON.stringify({
@@ -32,12 +33,10 @@
if (res.code === 0) {
this.$message.success("操作成功")
// 发送完成消息
window.parent.postMessage(
{
type: "task-complete"
},
"*"
)
window.GlobalBroadcastChannel.postMessage({
type: "task-complete",
payload: val
})
}
})
},
@@ -30,16 +30,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 prop="taskName" label="当前节点"></el-table-column>
<el-table-column prop="curTaskName" 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">
<el-table-column label="操作" fixed="right" width="200px">
<template slot-scope="{row}">
<el-button @click="onOpen(row.id)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskState === 10" @click="openApproval(row)" size="mini" type="primary">审核</el-button>
<el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.canRevoke" @click="onRevoke(row)" size="mini" type="danger">撤回</el-button>
<!-- <el-button v-if="row.taskState === 10" @click="openView(row)" size="mini" type="primary">审核</el-button>-->
</template>
</el-table-column>
</el-table>
@@ -62,9 +63,11 @@ layout("/layouts/platform.html"){
}
},
methods: {
onOpen() {},
openApproval(row) {
openView(row) {
window.open("/flow/common/approval/form?taskId=" + row.taskId + "&instanceId=" + row.instanceId + "&businessId=" + row.businessNo)
},
onRevoke(row) {
console.log(row)
}
},
created() {