feat: 优化流程详情审批记录显示效果

This commit is contained in:
chenli
2026-01-28 17:32:24 +08:00
parent b38961ac53
commit 82ea0103b6

View File

@@ -1,62 +1,94 @@
<template>
<view class="dynamicModel-form-v jnpf-wrap jnpf-wrap-form" v-if="showPage">
<JnpfParser :formConf="formConf" ref="dynamicForm" @submit="sumbitForm" :key="key" v-if="!loading" />
<!-- 审批记录区域 -->
<view v-if="approvalData.length && isApproval">
<view class="approval-title">审批记录</view>
<view class="approval-list">
<view
class="approval-item"
v-for="(item, index) in approvalData"
:key="item.id"
>
<!-- 左侧状态线 + 状态点 -->
<view class="approval-line-container">
<!-- 状态点 -->
<view class="status-icon" :class="getStatusClass(item.result)">
<!-- <u-icon
:name="item.result === 1 ? 'clock' : (item.result === 2 ? 'checkmark-circle' : 'close-circle')"
size="20"
:color="item.result === 1 ? '#409EFF' : (item.result === 2 ? '#67C23A' : '#F56C6C')"
/> -->
</view>
<!-- 竖线最后一条隐藏 -->
<view class="approval-line" v-if="index < approvalData.length - 1"></view>
</view>
<!-- 审批内容 -->
<view class="approval-content">
<!-- 任务名称 + 状态标签 -->
<view class="task-header">
<text class="task-name">任务{{ item.name }}</text>
<!-- 状态标签参考图的右上角样式 -->
<view class="status-tag" :class="getStatusTagClass(item.result)">
{{ item.result === 1 ? '审批中' : (item.result === 2 ? '已提交' : '不通过') }}
<!-- 时间显示在标签右侧 -->
<text class="status-time">{{ formatTime(item.createTime) }}</text>
</view>
</view>
<!-- 固定的底部审批记录只显示第一条 -->
<view
v-if="hasFixedApproval && showFixedApproval"
class="fixed-approval-bar"
:style="{ bottom: bottomOffset + 'rpx' }"
>
<view class="fixed-approval-content" @tap="scrollToApproval">
<!-- 左侧状态点 -->
<view class="fixed-status-icon" :class="getStatusClass(firstApproval?.result)"></view>
<!-- 中间审批信息 -->
<view class="fixed-approval-info">
<view class="fixed-approval-header">
<text class="fixed-task-name">任务{{ firstApproval?.name || '' }}</text>
<text class="status-tag" :class="getStatusTagClass(firstApproval.result)">
{{ getStatusText(firstApproval.result) }}
<text class="status-time">{{ formatTime(firstApproval.createTime) }}</text>
</text>
</view>
<view class="fixed-approval-user">
<text class="fixed-info-label">审批人</text>
<text class="fixed-user-name">{{ firstApproval?.assigneeUser?.nickname || '' }}</text>
<text class="fixed-dept-tag">{{ firstApproval?.assigneeUser?.deptName || '' }}</text>
</view>
<view class="reason-box" v-if="firstApproval.reason">
{{ firstApproval.reason }}
</view>
</view>
<!-- 右侧向下箭头按钮 -->
<view class="fixed-scroll-btn" @tap.stop="scrollToApproval">
<text class="fixed-btn-text">更多</text>
</view>
</view>
</view>
<!-- 完整的审批记录区域 -->
<view
v-if="approvalData.length && isApproval"
id="approval-record-all"
class="full-approval-section"
:class="{ 'has-fixed-bar': hasFixedApproval && showFixedApproval }"
>
<view class="approval-title">审批记录</view>
<view class="approval-list">
<view
class="approval-item"
v-for="(item, index) in approvalData"
:key="item.id"
>
<!-- 左侧状态线 + 状态点 -->
<view class="approval-line-container">
<!-- 状态点 -->
<view class="status-icon" :class="getStatusClass(item.result)"></view>
<!-- 竖线最后一条隐藏 -->
<view class="approval-line" v-if="index < approvalData.length - 1"></view>
</view>
<!-- 审批人信息 -->
<view class="info-row">
<text class="info-label">审批人</text>
<text class="info-value">{{ item.assigneeUser.nickname }}</text>
<text class="info-value-tag">{{ item.assigneeUser.deptName }}</text>
<view class="pending-tag" v-if="item.result === 1">待审批</view>
</view>
<!-- 审批意见有则显示 -->
<view class="reason-box" v-if="item.reason">
{{ item.reason }}
</view>
</view>
</view>
</view>
<!-- 审批内容 -->
<view class="approval-content">
<!-- 任务名称 + 状态标签 -->
<view class="task-header">
<text class="task-name">任务{{ item.name }}</text>
<view class="status-tag" :class="getStatusTagClass(item.result)">
{{ getStatusText(item.result) }}
<text class="status-time">{{ formatTime(item.createTime) }}</text>
</view>
</view>
<!-- 审批人信息 -->
<view class="info-row">
<text class="info-label">审批人</text>
<text class="info-value">{{ item.assigneeUser?.nickname || '' }}</text>
<text class="info-value-tag">{{ item.assigneeUser?.deptName || '' }}</text>
<view class="pending-tag" v-if="item.result === 1">待审批</view>
</view>
<!-- 审批意见有则显示 -->
<view class="reason-box" v-if="item.reason">
{{ item.reason }}
</view>
</view>
</view>
</view>
</view>
<!-- 底部操作按钮区域 -->
<view v-if="!loading && isShowBtn" class="buttom-actions">
<view v-if="!loading && isShowBtn" class="buttom-actions" id="bottom-actions">
<u-button v-if="(!dataForm.approveStatus && dataForm.id) || dataForm.approveStatus == 3" class="buttom-btn launch-flow-btn" type="primary" @click.stop="handleLaunchFlow" :disabled="idDsabled" :loading="btnLoading">
发起流程
</u-button>
@@ -87,7 +119,6 @@
:style="{color: item.color || '#333'}"
>
<view @click.stop="handleMoreMenuClick(item)">
<!-- <text class="menu-icon" :class="item.icon"></text> -->
<text class="menu-label">{{item.label}}</text>
</view>
</view>
@@ -237,29 +268,6 @@
</view>
<view class="dialog-content">
<!-- <view class="form-item">
<text class="label">任务名称</text>
<text class="value">{{ copyForm.taskName || '安全监护人' }}</text>
</view>
<view class="form-item">
<text class="label">任务标识</text>
<text class="value">{{ copyForm.taskId || '' }}</text>
</view>
<view class="form-item">
<text class="label">流程名称</text>
<text class="value">{{ copyForm.flowName || '动土作业审批' }}</text>
</view>
<view class="form-item">
<text class="label">流程标识</text>
<text class="value">{{ copyForm.flowId || '' }}</text>
</view>
<view class="form-item required">
<text class="label">规则类型</text>
<u-select v-model="copyForm.ruleType" placeholder="请选择"> -->
<!-- <u-select-item label="用户" value="user"></u-select-item> -->
<!-- 如果有其他类型可以在这里添加 -->
<!-- </u-select> -->
<!-- </view> -->
<view class="form-item-user required">
<view>
<text class="label">指定用户</text>
@@ -336,6 +344,7 @@
} from '@/api/apply/visualDev'
import UserSelect from '@/components/Jnpf/CandidateSelect'
import {getOrganizeSelector} from '@/api/common.js'
export default {
components: {
CustomButton,
@@ -369,7 +378,7 @@
isAdd: false,
showMoreMenu: false,
moreMenuList: [],
createData: {}, // 初始值
createData: {},
deptList: [],
showProcessDialog: false,
processList: [],
@@ -381,15 +390,12 @@
loadingApprover: false,
currentProcessId: '',
selectedCandidates: {},
approvalData: [], // 存储审批记录
isProcess: false, // 点击查看流程
//审批意见弹窗相关数据
showApprovalReasonDialog: false, // 弹窗显示状态
approvalType: '', // 审批类型through(通过) / fail(不通过)
approvalReason: '', // 审批意见内容
approvalSubmitting: false, // 提交加载状态
// 抄送
approvalData: [],
isProcess: false,
showApprovalReasonDialog: false,
approvalType: '',
approvalReason: '',
approvalSubmitting: false,
showCopyDialog: false,
copySubmitting: false,
copyForm: {
@@ -398,10 +404,16 @@
options: {},
reason: ''
},
// 取消
showCacleDialog: false,
cacleReason: '',
cacleSubmitting: false,
// 新增:固定审批记录相关
showFixedApproval: true,
isScrollingToApproval: false,
scrollTimer: null,
// bottomOffset: 100,
scrollHandler: null, // 用于存储滚动监听函数
}
},
computed: {
@@ -436,11 +448,52 @@
if(!isProcess) return true
if(isProcess) return false
return false
}
},
bottomOffset(){
if(!this.loading && this.isShowBtn) return 85
return 0
},
hasFixedApproval() {
console.log(this.approvalData.length > 0 && this.isApproval,'判断---')
return this.approvalData.length > 0 && this.isApproval
},
firstApproval() {
console.log(this.approvalData[0],'数据--')
return this.approvalData.length > 0 ? this.approvalData[0] : null
},
},
onLoad(option) {
this.init(option)
},
onReady() {
this.calculateBottomOffset()
this.bindPageScroll()
},
onUnload() {
// 清除滚动监听
if (this.scrollHandler) {
uni.$off('scroll', this.scrollHandler)
}
if (this.scrollTimer) {
clearTimeout(this.scrollTimer)
}
},
onPageScroll(e) {
console.log('页面滚动事件触发:', e.scrollTop)
// 如果正在滚动到审批区域,不处理
if (this.isScrollingToApproval) return
// 防抖处理
if (this.scrollTimer) {
clearTimeout(this.scrollTimer)
}
this.scrollTimer = setTimeout(() => {
this.checkApprovalVisibility()
}, 50)
},
methods: {
init(option) {
const parseConfig = (rawConfig) => {
@@ -493,9 +546,121 @@
})
this.getDesForm()
},
handleUserChange(node, value, data,index){
// 计算底部按钮高度
calculateBottomOffset() {
setTimeout(() => {
const query = uni.createSelectorQuery().in(this)
query.select('#bottom-actions').boundingClientRect(data => {
if (data) {
this.bottomOffset = data.height + 20
}
}).exec()
}, 300)
},
// 监听页面滚动
bindPageScroll() {
// 初始检查一次
this.checkApprovalVisibility()
},
// 检查审批记录是否可见
checkApprovalVisibility() {
if (!this.hasFixedApproval || this.isScrollingToApproval) return
const query = uni.createSelectorQuery().in(this)
query.select('#approval-record-all').boundingClientRect(data => {
if (!data) {
this.showFixedApproval = true
return
}
// 获取窗口高度
const windowHeight = uni.getSystemInfoSync().windowHeight || 667
// 计算审批记录区域底部距离屏幕顶部的距离
const approvalBottom = data.top + data.height
console.log(approvalBottom,'approvalBottom--')
// 判断:如果审批记录区域底部在屏幕内(能看到最下方的审批记录),则隐藏固定底栏
// 这里使用一个阈值比如底部距离屏幕顶部小于窗口高度的90%(即能看到大部分)
const isBottomVisible = approvalBottom <= windowHeight * 0.9
// 或者判断整个区域是否在视口内
const isFullyVisible = data.top >= 0 && data.bottom <= windowHeight
// 只要能看到最下方的审批记录(底部在视口内),就隐藏固定底栏
// 看不到最下方的审批记录时,显示固定底栏
this.showFixedApproval = !isBottomVisible && !isFullyVisible
console.log('审批记录可见性检查:', {
top: data.top,
bottom: data.bottom,
approvalBottom,
windowHeight,
isBottomVisible,
isFullyVisible,
showFixedApproval: this.showFixedApproval
})
}).exec()
},
// 滚动到审批记录区域
scrollToApproval() {
this.isScrollingToApproval = true
this.showFixedApproval = false
// 先滚动到审批记录区域
uni.pageScrollTo({
selector: '#approval-record-all',
duration: 300,
success: () => {
// 滚动完成后,延迟检查可见性
setTimeout(() => {
this.isScrollingToApproval = false
this.checkApprovalVisibility()
}, 500)
},
fail: () => {
this.isScrollingToApproval = false
this.scrollToApprovalByPosition()
}
})
},
// 备选滚动方法
scrollToApprovalByPosition() {
const query = uni.createSelectorQuery().in(this)
query.select('#approval-record-all').boundingClientRect(data => {
if (data) {
uni.pageScrollTo({
scrollTop: data.top - 50,
duration: 300,
success: () => {
setTimeout(() => {
this.isScrollingToApproval = false
this.checkApprovalVisibility()
}, 500)
}
})
}
}).exec()
},
// 获取状态文本
getStatusText(result) {
switch(result) {
case 1: return '审批中'
case 2: return '已提交'
case 3: return '不通过'
default: return '未知'
}
},
handleUserChange(node, value, data,index) {
const name = this.approverList[index]['taskDefinitionKey']
this.approverList[index] ={
this.approverList[index] = {
...this.approverList[index],
[name] : value
}
@@ -537,7 +702,6 @@
getListCreateData(params){
getListCreate(params,this.modelId).then(res=>{
if(res.code == 0) {
// const name = this.approvalType == 'through' ? '申请通过成功' '发起成功'
let name = '发起成功'
switch(this.approvalType){
case 'through':
@@ -563,14 +727,7 @@
})
},
initMoreMenuList() {
let menuList = [
// {
// label: '删除',
// value: 'delete',
// icon: 'icon-ym icon-ym-app-delete',
// color: '#dd524d'
// }
]
let menuList = []
console.log(this.dataForm.approveStatus,'approveStatus--')
if((!this.dataForm.approveStatus && this.dataForm.id) || this.dataForm.approveStatus == 3){
// 未审核 | 驳回
@@ -700,12 +857,10 @@
}
this.getListCreateData(params)
},
// 回退
handleReject() {
const {id,processInstance} = this.approvalData[0]
const params = {
processType: '3', // 回退code todo
// processDefinitionId:Object.values(this.copyForm.options),
processType: '3',
processInstanceId: this.formData.processInstanceId,
id: this.dataForm.id,
data: {
@@ -837,7 +992,6 @@
}
try {
this.cacleSubmitting = true
//参数
const params = {
processType: 5,
processInstanceId: this.formData.processInstanceId, // 流程实例ID
@@ -881,7 +1035,6 @@
}
try {
this.approvalSubmitting = true
// 构造审批参数(
const params = {
processType: this.approvalType === 'through' ? 1 : 2, // 1=通过2=不通过
processInstanceId: this.formData.processInstanceId, // 流程实例ID
@@ -1131,9 +1284,6 @@
item.options.unshift(matchedOption);
}
// this.$set(item, 'modelValue', applyDepId);
// this.$set(item, 'value', applyDepId); // 兼容部分组件的value字段
// this.$set(item, '__value__', matchedOption ? matchedOption.orgNameTree : applyDepName);
config.defaultValue = applyDepId;
item.modelValue = applyDepId;
item.value = applyDepId;
@@ -1366,7 +1516,6 @@
const res = await getByProcess(this.formData.processInstanceId);
const result = []
if (res.code === 0) {
// this.approvalData = res.data
res.data.forEach((task) => {
if (task.result !== 4) {
result.push(task)
@@ -1387,6 +1536,13 @@
})
}
this.approvalData = result
// 审批记录加载完成后,检查一次可见性
this.$nextTick(() => {
setTimeout(() => {
this.checkApprovalVisibility()
}, 300)
})
} catch (err) {
console.error('获取审批记录失败:', err);
}
@@ -1438,11 +1594,283 @@
background-color: #f0f2f6;
}
/* 固定的审批记录底栏 */
.fixed-approval-bar {
position: fixed;
left: 0;
right: 0;
z-index: 998;
background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%);
border-top: 1rpx solid #e8e8e8;
box-shadow: 0 -2rpx 20rpx rgba(0, 0, 0, 0.08);
padding: 20rpx 30rpx;
transition: transform 0.3s ease;
}
.fixed-approval-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.fixed-status-icon {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
margin-right: 20rpx;
flex-shrink: 0;
&.status-processing {
background-color: #409EFF;
border: 3rpx solid rgba(64, 158, 255, 0.3);
}
&.status-pass {
background-color: #67C23A;
border: 3rpx solid rgba(103, 194, 58, 0.3);
}
&.status-reject {
background-color: #F56C6C;
border: 3rpx solid rgba(245, 108, 108, 0.3);
}
}
.fixed-approval-info {
flex: 1;
overflow: hidden;
}
.fixed-approval-header {
display: flex;
justify-content: space-between;
}
.fixed-task-name {
display: block;
font-size: 28rpx;
font-weight: 500;
color: #333;
margin-bottom: 8rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.fixed-approval-user {
display: flex;
align-items: center;
font-size: 24rpx;
}
.fixed-user-name {
color: #666;
margin-right: 12rpx;
}
.fixed-dept-tag {
background: #f4f4f5;
color: #909399;
padding: 2rpx 10rpx;
border-radius: 4rpx;
font-size: 22rpx;
}
.fixed-scroll-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #409EFF;
color: white;
padding: 12rpx 20rpx;
border-radius: 30rpx;
margin-left: 20rpx;
flex-shrink: 0;
transition: all 0.2s;
&:active {
background: #3a8ee6;
transform: scale(0.95);
}
}
.fixed-scroll-btn .icon-ym {
font-size: 28rpx;
margin-bottom: 4rpx;
}
.fixed-btn-text {
font-size: 22rpx;
}
/* 完整的审批记录区域 */
.full-approval-section {
margin-top: 20rpx;
background: white;
border-radius: 12rpx;
overflow: hidden;
&.has-fixed-bar {
margin-bottom: 120rpx;
}
}
.approval-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
background: #f9fafc;
}
.approval-list {
padding: 0 30rpx;
}
.approval-item {
display: flex;
position: relative;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
}
.approval-line-container {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 24rpx;
}
.status-icon {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
&.status-processing {
background-color: #409EFF;
border: 4rpx solid rgba(64, 158, 255, 0.2);
box-shadow: 0 0 0 4rpx rgba(64, 158, 255, 0.1);
}
&.status-pass {
background-color: #67C23A;
border: 4rpx solid rgba(103, 194, 58, 0.2);
box-shadow: 0 0 0 4rpx rgba(103, 194, 58, 0.1);
}
&.status-reject {
background-color: #F56C6C;
border: 4rpx solid rgba(245, 108, 108, 0.2);
box-shadow: 0 0 0 4rpx rgba(245, 108, 108, 0.1);
}
}
.approval-line {
width: 2rpx;
background-color: #e8e8e8;
flex: 1;
margin-top: 10rpx;
}
.approval-content {
flex: 1;
}
.task-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
}
.task-name {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.status-tag {
display: flex;
align-items: center;
font-size: 24rpx;
padding: 6rpx 16rpx;
border-radius: 30rpx;
&.tag-processing {
background-color: rgba(64, 158, 255, 0.1);
color: #409EFF;
}
&.tag-pass {
background-color: rgba(103, 194, 58, 0.1);
color: #67C23A;
}
&.tag-reject {
background-color: rgba(245, 108, 108, 0.1);
color: #F56C6C;
}
.status-time {
font-size: 22rpx;
margin-left: 12rpx;
opacity: 0.8;
}
}
.info-row {
display: flex;
align-items: center;
font-size: 26rpx;
margin-bottom: 12rpx;
flex-wrap: wrap;
.info-label {
color: #999;
margin-right: 8rpx;
}
.info-value {
color: #666;
margin-right: 12rpx;
}
.info-value-tag {
background: #f4f4f5;
color: #909399;
padding: 4rpx 12rpx;
border-radius: 20rpx;
font-size: 24rpx;
}
.pending-tag {
background-color: #fef0f0;
color: #f56c6c;
padding: 4rpx 12rpx;
border-radius: 20rpx;
font-size: 24rpx;
margin-left: 12rpx;
}
}
.reason-box {
background: #f9f9f9;
padding: 20rpx;
border-radius: 8rpx;
font-size: 26rpx;
color: #333;
line-height: 1.5;
margin-top: 16rpx;
border-left: 4rpx solid #e8e8e8;
}
/* 底部按钮区域 */
.buttom-actions {
display: flex;
align-items: center;
// justify-content: flex-end;
// padding: 20rpx 30rpx;
background: #fff;
position: fixed;
bottom: 0;
@@ -1450,23 +1878,26 @@
right: 0;
z-index: 999 !important;
padding: 10rpx 0rpx;
// gap: 20rpx;
.buttom-btn {
width: 300rpx;
min-width: 300rpx;
padding: 12rpx 15rpx;
flex: 1;
}
// border-top: 1rpx solid #f0f0f0;
// box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.buttom-btn {
min-width: 200rpx;
padding: 12rpx 15rpx;
flex: 1;
// margin: 0 10rpx;
}
.launch-flow-btn {
background: #19be6b !important;
}
.launch-flow-btn[disabled] {
background: #19be6b !important;
opacity: 0.6;
background: #19be6b !important;
opacity: 0.6;
}
.more-btn {
position: relative;
display: flex;
@@ -1481,7 +1912,7 @@
color: #606266;
margin-top: 4rpx;
}
.more-menu {
position: absolute;
bottom: 100%;
@@ -1499,14 +1930,14 @@
padding: 16rpx 20rpx;
font-size: 28rpx;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: #f5f7fa;
}
.menu-icon {
font-size: 32rpx;
margin-right: 12rpx;
&:active {
background-color: #ebeef5;
}
.menu-label {