<template>
|
<view class="content">
|
<!-- 项目概况 -->
|
<view class="header-card">
|
<view class="project-header">
|
<view class="project-icon-box">
|
<uni-icons type="paperplane-filled" size="24" color="#fff"></uni-icons>
|
</view>
|
<view class="project-title-wrapper">
|
<text class="title">{{ detailData.projectName || '-' }}</text>
|
<view class="project-tag">{{ detailData.projectCode || '-' }}</view>
|
</view>
|
</view>
|
<view class="project-divider"></view>
|
<view class="project-info-grid">
|
<view class="info-item">
|
<text class="label">当前流程</text>
|
<text class="value">{{ queryParams.processName || '-' }}</text>
|
</view>
|
<view class="info-item">
|
<text class="label">流程状态</text>
|
<text class="value status-text">{{ detailData.status || '进行中' }}</text>
|
</view>
|
</view>
|
</view>
|
|
<!-- 状态切换栏 -->
|
<view class="tab-wrapper">
|
<scroll-view scroll-x class="tab-scroll" :show-scrollbar="false">
|
<view class="tab-container">
|
<view
|
v-for="tab in tabs"
|
:key="tab.id"
|
class="tab-item"
|
:class="[{ active: tab.id === selectTabId }]"
|
@click="changeTab(tab.id, tab.type)"
|
>
|
<text class="tab-label">{{ tab.label }}</text>
|
<text class="tab-count" v-if="detailData.statistics">
|
{{ getTabCount(tab.type) }}
|
</text>
|
</view>
|
</view>
|
</scroll-view>
|
</view>
|
|
<!-- 搜索与操作 -->
|
<view class="action-bar">
|
<view class="search-box">
|
<uni-icons type="search" size="18" color="#999"></uni-icons>
|
<input
|
class="search-input"
|
v-model="queryParams.taskName"
|
placeholder="搜索任务名称..."
|
confirm-type="search"
|
@confirm="handleSearch"
|
/>
|
</view>
|
<view class="btn-group">
|
<!-- <view class="icon-btn" @click="openProcessImg">
|
<uni-icons type="image" size="20" color="#1E88E5"></uni-icons>
|
<text>流程图</text>
|
</view>
|
<view class="icon-btn" @click="openRecord">
|
<uni-icons type="list" size="20" color="#1E88E5"></uni-icons>
|
<text>日志</text>
|
</view> -->
|
</view>
|
</view>
|
|
<!-- 任务列表 -->
|
<view class="task-list">
|
<view v-if="tableLoading && (!taskList || taskList.length === 0)" class="loading-state">
|
<uni-icons type="spinner-cycle" size="28" color="#1E88E5" class="rotate"></uni-icons>
|
<text>加载任务中...</text>
|
</view>
|
<view v-else-if="!taskList || taskList.length === 0" class="empty-state">
|
<image src="/static/images/empty.png" mode="aspectFit" class="empty-img" v-if="false"></image>
|
<text>暂无相关任务数据</text>
|
</view>
|
|
<view v-for="(item, index) in taskList" :key="index" class="task-card" :class="getStatusClass(item.taskStatus)">
|
<view class="card-status-bar"></view>
|
<view class="card-content">
|
<view class="card-main">
|
<view class="card-header-row">
|
<text class="task-name">{{ item.taskName }}</text>
|
<text class="status-tag" :class="getStatusClass(item.taskStatus)">{{ item.taskStatus }}</text>
|
</view>
|
<view class="card-detail-row">
|
<view class="detail-item">
|
<uni-icons type="staff" size="14" color="#999"></uni-icons>
|
<text>{{ formatUnitName(item.promoterUnitName) }}</text>
|
</view>
|
</view>
|
</view>
|
<view class="card-actions">
|
<view class="action-btn-outline" v-if="item.taskStatus !== '未开始'" @click="goToProcessDetail(item)">
|
<text>详情</text>
|
</view>
|
<view class="action-btn-primary" v-if="showHandle(item)" @click="goToDo(item)">
|
<text>办理</text>
|
</view>
|
<view class="action-btn-warn" v-if="item.taskStatus === '待办'" @click="openSupervise(item)">
|
<text>督办</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<!-- 督办弹窗 -->
|
<view v-if="showSupervisePopup" class="custom-popup-mask" @click="showSupervisePopup = false">
|
<view class="popup-content" @click.stop>
|
<view class="popup-header">
|
<text class="popup-title">任务督办</text>
|
<view class="close-btn" @click="showSupervisePopup = false">
|
<uni-icons type="closeempty" size="20" color="#999"></uni-icons>
|
</view>
|
</view>
|
<view class="popup-body">
|
<view class="form-label">督办意见</view>
|
<textarea
|
v-model="superviseForm.content"
|
placeholder="请详细输入您的督办意见或要求..."
|
class="popup-textarea"
|
maxlength="200"
|
/>
|
<view class="word-count">{{ (superviseForm.content || '').length }}/200</view>
|
</view>
|
<view class="popup-footer">
|
<button class="btn-cancel" @click="showSupervisePopup = false">取消</button>
|
<button class="btn-confirm" @click="submitSupervise">发送督办</button>
|
</view>
|
</view>
|
</view>
|
|
<BottomTabBar active="progress" />
|
</view>
|
</template>
|
|
<script>
|
import {
|
getProjectProcessDetail,
|
checkTaskAuditStatus,
|
taskSupervise,
|
getProjectProcessDetailTaskList
|
} from "@/api/projectProcess/projectProcess.js";
|
import BottomTabBar from '@/components/BottomTabBar.vue';
|
|
export default {
|
name: "ProcessDetail",
|
components: {
|
BottomTabBar
|
},
|
data() {
|
return {
|
detailData: {},
|
taskList: [],
|
total: 0,
|
selectTabId: 2,
|
tableLoading: false,
|
queryParams: {
|
taskName: '',
|
taskType: 'todo',
|
pageSize: 10,
|
currentPage: 1,
|
projectId: null,
|
processDefId: null,
|
processInsId: null,
|
deployId: null,
|
processName: ''
|
},
|
tabs: [
|
{ id: 1, label: '全部', type: 'all', colorClass: 'all-color' },
|
{ id: 2, label: '待办', type: 'todo', colorClass: 'todo-color' },
|
{ id: 4, label: '剩余', type: 'remaining', colorClass: 'remaining-color' },
|
{ id: 5, label: '按时', type: 'timely', colorClass: 'timely-color' },
|
{ id: 6, label: '超时', type: 'overtime', colorClass: 'overtime-color' },
|
{ id: 3, label: '容缺', type: 'wait', colorClass: 'wait-color' },
|
{ id: 7, label: '跳过', type: 'jump', colorClass: 'jump-color' },
|
{ id: 8, label: '督办', type: 'urge', colorClass: 'urge-color' }
|
],
|
superviseForm: {
|
taskId: null,
|
projectId: null,
|
processInsId: null,
|
receiverIds: null,
|
receiverType: null,
|
superviseType: 'SUPERVISE',
|
content: '',
|
},
|
showSupervisePopup: false
|
}
|
},
|
onShow() {
|
console.log("执行onshow")
|
this.getDetail();
|
this.getList();
|
},
|
onLoad(query) {
|
console.log("执行onload")
|
const decodeValue = (value) => {
|
if (typeof value !== 'string' || value === '') return '';
|
try {
|
return decodeURIComponent(value);
|
} catch (e) {
|
console.error('参数解码失败:', e, '原始值:', value);
|
return value;
|
}
|
};
|
if (decodeValue(query.projectId)) {
|
this.queryParams.projectId = decodeValue(query.projectId);
|
this.queryParams.processDefId = decodeValue(query.processDefId);
|
this.queryParams.processInsId = decodeValue(query.processInsId);
|
this.queryParams.deployId = decodeValue(query.deployId);
|
this.queryParams.processName = decodeValue(query.processName);
|
|
this.getDetail();
|
this.getList();
|
}
|
},
|
methods: {
|
getTabCount(type) {
|
if (!this.detailData.statistics) return 0;
|
const mapping = {
|
all: 'totalTaskNum',
|
todo: 'todoTaskNum',
|
remaining: 'remainingTaskNum',
|
timely: 'timelyFinishedTaskNum',
|
overtime: 'overtimeTaskNum',
|
wait: 'waitTaskNum',
|
jump: 'jumpTaskNum',
|
urge: 'urgeTaskNum'
|
};
|
return this.detailData.statistics[mapping[type]] || 0;
|
},
|
formatUnitName(unitName) {
|
if (!unitName) return '未指定责任单位';
|
if (Array.isArray(unitName)) {
|
return unitName.filter(item => item && item.trim()).join(', ') || '未指定责任单位';
|
}
|
return unitName;
|
},
|
getStatusClass(status) {
|
const mapping = {
|
'待办': 'todo',
|
'已完成': 'finished',
|
'进行中': 'processing',
|
'未开始': 'pending',
|
'容缺': 'wait',
|
'挂起': 'suspend'
|
};
|
return mapping[status] || '';
|
},
|
getDetail() {
|
const projectType = /^\d+(\.\d+)?$/.test(this.queryParams.projectId) ? "PROJECT" : "ENGINEERING";
|
getProjectProcessDetail({
|
projectId: this.queryParams.projectId,
|
processDefId: this.queryParams.processDefId,
|
projectType
|
}).then(res => {
|
if(res.statusCode === 200){
|
|
this.detailData = res.data.data;
|
console.log(this.detailData)
|
}
|
|
});
|
},
|
getList() {
|
this.tableLoading = true;
|
getProjectProcessDetailTaskList(this.queryParams).then(res => {
|
this.taskList = res.data.data || [];
|
this.total = res.data.total || 0;
|
this.tableLoading = false;
|
}).catch(() => {
|
this.tableLoading = false;
|
});
|
},
|
changeTab(id, type) {
|
this.selectTabId = id;
|
this.queryParams.taskType = type;
|
this.queryParams.currentPage = 1;
|
this.getList();
|
},
|
handleSearch() {
|
this.queryParams.currentPage = 1;
|
this.getList();
|
},
|
showHandle(row) {
|
const userInfo = uni.getStorageSync('userInfo') || {};
|
if (['待办', '挂起', '容缺'].includes(row.taskStatus)) {
|
if (row.handlerType === "USER") {
|
return (row.handlerId || []).indexOf(userInfo.user.userId) !== -1;
|
} else if (row.handlerType === "DEPT") {
|
return (row.handlerUnitId || []).indexOf(userInfo.user.deptId) !== -1 ||
|
(row.handlerUnitId || []).some(id => (userInfo.childDeptList || []).indexOf(id) !== -1);
|
} else if (row.handlerType === "ROLE") {
|
return (row.handlerUnitId || []).some(roleId => (userInfo.user.roleIds || []).indexOf(roleId) !== -1);
|
}
|
}
|
return false;
|
},
|
goToDo(row) {
|
const params = {
|
deployId: row.deployId,
|
procDefId: row.processDefId,
|
procInsId: row.processInsId,
|
processName: row.taskName,
|
flowName: this.queryParams.processName,
|
projectName: this.detailData.projectName,
|
taskId: row.taskId,
|
projectId: this.queryParams.projectId,
|
isWait: row.taskStatus === '容缺'
|
};
|
|
if (row.taskStatus === '容缺') {
|
this.navigateToTask(params, false);
|
} else {
|
checkTaskAuditStatus({
|
processDefId: row.processDefId,
|
taskId: row.taskId
|
}).then(res => {
|
this.navigateToTask(params, res.data);
|
});
|
}
|
},
|
navigateToTask(params, showAuditing) {
|
params.showAuditing = showAuditing;
|
uni.navigateTo({
|
url: `/subpackage/flowable/task-process?${this.buildQuery(params)}`
|
});
|
},
|
goToProcessDetail(row) {
|
const params = {
|
projectName: this.detailData.projectName,
|
flowName: this.queryParams.processName,
|
procInsId: row.processInsId,
|
deployId: row.deployId,
|
taskId: row.taskId,
|
projectId: this.queryParams.projectId
|
};
|
uni.navigateTo({
|
url: `/subpackage/flowable/task-process?${this.buildQuery(params)}&isView=true`
|
});
|
},
|
buildQuery(obj) {
|
return Object.keys(obj).map(key => `${key}=${encodeURIComponent(obj[key])}`).join('&');
|
},
|
openSupervise(row) {
|
this.superviseForm.content = '';
|
this.superviseForm.taskId = row.taskId;
|
this.superviseForm.projectId = this.queryParams.projectId;
|
this.superviseForm.processInsId = row.processInsId;
|
this.superviseForm.receiverType = row.handlerType;
|
|
if (row.handlerType === 'USER') {
|
this.superviseForm.receiverIds = row.handlerId;
|
} else {
|
this.superviseForm.receiverIds = row.handlerUnitId;
|
}
|
this.showSupervisePopup = true;
|
},
|
submitSupervise() {
|
if (!this.superviseForm.content) {
|
uni.showToast({ title: '请输入督办内容', icon: 'none' });
|
return;
|
}
|
taskSupervise(this.superviseForm).then(() => {
|
this.showSupervisePopup = false;
|
uni.showToast({ title: '督办成功' });
|
});
|
},
|
openProcessImg() {
|
// Uni-app 暂不支持复杂的 BPMN Viewer,通常跳转到 H5 页面或显示图片
|
uni.showToast({ title: '请在电脑端查看流程图', icon: 'none' });
|
},
|
openRecord() {
|
// 跳转到日志页面或在本页展开
|
uni.showToast({ title: '日志功能开发中', icon: 'none' });
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.content {
|
padding: 24rpx;
|
background-color: #f8f9fb;
|
min-height: 100vh;
|
padding-bottom: 140rpx;
|
}
|
|
/* 项目概况卡片优化 */
|
.header-card {
|
background: linear-gradient(135deg, #ffffff 0%, #fefefe 100%);
|
padding: 32rpx;
|
border-radius: 24rpx;
|
margin-bottom: 24rpx;
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
}
|
|
.project-header {
|
display: flex;
|
align-items: center;
|
margin-bottom: 24rpx;
|
}
|
|
.project-icon-box {
|
width: 80rpx;
|
height: 80rpx;
|
background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
|
border-radius: 20rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-right: 24rpx;
|
box-shadow: 0 4rpx 12rpx rgba(30, 136, 229, 0.3);
|
}
|
|
.project-title-wrapper {
|
flex: 1;
|
}
|
|
.project-title-wrapper .title {
|
font-size: 34rpx;
|
font-weight: 600;
|
color: #2c3e50;
|
display: block;
|
margin-bottom: 8rpx;
|
}
|
|
.project-tag {
|
display: inline-block;
|
font-size: 22rpx;
|
color: #1e88e5;
|
background: rgba(30, 136, 229, 0.1);
|
padding: 4rpx 16rpx;
|
border-radius: 8rpx;
|
}
|
|
.project-divider {
|
height: 1px;
|
background-color: #f0f0f0;
|
margin: 24rpx 0;
|
}
|
|
.project-info-grid {
|
display: flex;
|
justify-content: space-between;
|
}
|
|
.project-info-grid .info-item {
|
flex: 1;
|
}
|
|
.project-info-grid .info-item .label {
|
display: block;
|
font-size: 24rpx;
|
color: #94a3b8;
|
margin-bottom: 8rpx;
|
}
|
|
.project-info-grid .info-item .value {
|
font-size: 28rpx;
|
color: #475569;
|
font-weight: 500;
|
}
|
|
.status-text {
|
color: #10b981 !important;
|
}
|
|
/* 状态切换栏优化 */
|
.tab-wrapper {
|
margin: 0 -24rpx 24rpx;
|
}
|
|
.tab-scroll {
|
white-space: nowrap;
|
}
|
|
.tab-container {
|
display: inline-flex;
|
padding: 8rpx 24rpx;
|
}
|
|
.tab-item {
|
padding: 16rpx 32rpx;
|
margin-right: 20rpx;
|
border-radius: 100rpx;
|
background: #fff;
|
font-size: 26rpx;
|
color: #64748b;
|
display: flex;
|
align-items: center;
|
border: 1px solid #e2e8f0;
|
transition: all 0.3s;
|
}
|
|
.tab-item.active {
|
background: #1e88e5;
|
color: #fff;
|
border-color: #1e88e5;
|
box-shadow: 0 4rpx 12rpx rgba(30, 136, 229, 0.2);
|
}
|
|
.tab-count {
|
margin-left: 8rpx;
|
font-size: 22rpx;
|
opacity: 0.8;
|
}
|
|
/* 搜索与操作优化 */
|
.action-bar {
|
display: flex;
|
align-items: center;
|
margin-bottom: 24rpx;
|
gap: 20rpx;
|
}
|
|
.search-box {
|
flex: 1;
|
height: 80rpx;
|
background: #fff;
|
border-radius: 100rpx;
|
padding: 0 32rpx;
|
display: flex;
|
align-items: center;
|
border: 1px solid #e2e8f0;
|
}
|
|
.search-input {
|
flex: 1;
|
margin-left: 12rpx;
|
font-size: 28rpx;
|
color: #334155;
|
}
|
|
.btn-group {
|
display: flex;
|
gap: 16rpx;
|
}
|
|
.icon-btn {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
min-width: 80rpx;
|
}
|
|
.icon-btn text {
|
font-size: 20rpx;
|
color: #64748b;
|
margin-top: 4rpx;
|
}
|
|
/* 任务列表优化 */
|
.task-list {
|
display: flex;
|
flex-direction: column;
|
gap: 24rpx;
|
}
|
|
.task-card {
|
background: #fff;
|
border-radius: 20rpx;
|
display: flex;
|
overflow: hidden;
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03);
|
position: relative;
|
}
|
|
.card-status-bar {
|
width: 8rpx;
|
background-color: #cbd5e1;
|
}
|
|
.task-card.todo .card-status-bar { background-color: #f59e0b; }
|
.task-card.finished .card-status-bar { background-color: #10b981; }
|
.task-card.wait .card-status-bar { background-color: #6366f1; }
|
.task-card.pending .card-status-bar { background-color: #94a3b8; }
|
|
.card-content {
|
flex: 1;
|
padding: 28rpx;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.card-main {
|
margin-bottom: 24rpx;
|
}
|
|
.card-header-row {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
margin-bottom: 16rpx;
|
}
|
|
.task-name {
|
font-size: 30rpx;
|
font-weight: 600;
|
color: #1e293b;
|
flex: 1;
|
margin-right: 16rpx;
|
}
|
|
.status-tag {
|
font-size: 22rpx;
|
padding: 4rpx 16rpx;
|
border-radius: 6rpx;
|
}
|
|
.status-tag.todo { color: #f59e0b; background: rgba(245, 158, 11, 0.1); }
|
.status-tag.finished { color: #10b981; background: rgba(16, 185, 129, 0.1); }
|
.status-tag.wait { color: #6366f1; background: rgba(99, 102, 241, 0.1); }
|
.status-tag.pending { color: #64748b; background: rgba(100, 116, 139, 0.1); }
|
|
.card-detail-row {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 24rpx;
|
}
|
|
.detail-item {
|
display: flex;
|
align-items: center;
|
gap: 8rpx;
|
font-size: 24rpx;
|
color: #64748b;
|
}
|
|
.card-actions {
|
display: flex;
|
justify-content: flex-end;
|
gap: 16rpx;
|
padding-top: 24rpx;
|
border-top: 1px solid #f1f5f9;
|
}
|
|
.action-btn-outline, .action-btn-primary, .action-btn-warn {
|
height: 60rpx;
|
padding: 0 32rpx;
|
border-radius: 30rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 24rpx;
|
font-weight: 500;
|
}
|
|
.action-btn-outline {
|
border: 1px solid #e2e8f0;
|
color: #64748b;
|
}
|
|
.action-btn-primary {
|
background-color: #1e88e5;
|
color: #fff;
|
}
|
|
.action-btn-warn {
|
background-color: #f59e0b;
|
color: #fff;
|
}
|
|
/* 弹窗美化 */
|
.custom-popup-mask {
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: rgba(0, 0, 0, 0.6);
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
z-index: 999;
|
}
|
|
.popup-content {
|
background: #fff;
|
width: 640rpx;
|
border-radius: 32rpx;
|
overflow: hidden;
|
}
|
|
.popup-header {
|
padding: 32rpx;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
border-bottom: 1px solid #f1f5f9;
|
}
|
|
.popup-title {
|
font-size: 32rpx;
|
font-weight: 600;
|
color: #1e293b;
|
}
|
|
.popup-body {
|
padding: 32rpx;
|
}
|
|
.form-label {
|
font-size: 26rpx;
|
font-weight: 500;
|
color: #64748b;
|
margin-bottom: 16rpx;
|
}
|
|
.popup-textarea {
|
width: 100%;
|
height: 240rpx;
|
background: #f8fafc;
|
padding: 24rpx;
|
border-radius: 16rpx;
|
font-size: 28rpx;
|
color: #1e293b;
|
box-sizing: border-box;
|
border: 1px solid #e2e8f0;
|
}
|
|
.word-count {
|
font-size: 22rpx;
|
color: #94a3b8;
|
text-align: right;
|
margin-top: 12rpx;
|
}
|
|
.popup-footer {
|
padding: 24rpx 32rpx 40rpx;
|
display: flex;
|
gap: 24rpx;
|
}
|
|
.btn-cancel, .btn-confirm {
|
flex: 1;
|
height: 88rpx;
|
border-radius: 44rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 30rpx;
|
font-weight: 500;
|
}
|
|
.btn-cancel {
|
background: #f1f5f9;
|
color: #64748b;
|
}
|
|
.btn-confirm {
|
background: #1e88e5;
|
color: #fff;
|
}
|
|
.loading-state, .empty-state {
|
padding: 100rpx 0;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
gap: 24rpx;
|
color: #94a3b8;
|
font-size: 26rpx;
|
}
|
|
.rotate {
|
animation: rotate 1.5s linear infinite;
|
}
|
|
@keyframes rotate {
|
from { transform: rotate(0deg); }
|
to { transform: rotate(360deg); }
|
}
|
</style>
|