Files
jnpf_app/pages/apply/dynamicModelList/form.vue
2026-01-29 14:28:28 +08:00

2393 lines
61 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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="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="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" 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>
<u-button v-if="!config.current && !dataForm.approveStatus" class="buttom-btn" type="primary" @click.stop="submit" :disabled="idDsabled" :loading="btnLoading">
保存
</u-button>
<u-button v-if="!config.current && (dataForm.approveStatus == 1 || dataForm.approveStatus == 2)" class="buttom-btn" type="primary" @click.stop="goProcess">
查看流程
</u-button>
<u-button v-if="config.current == 1 && dataForm.approveStatus && dataForm.approveStatus !==0" class="buttom-btn" type="error" @click.stop="handleCancle">
取消
</u-button>
<u-button v-if="config.current == 2" class="buttom-btn" type="error" @click.stop="handleFail" :disabled="idDsabled" :loading="btnLoading">
不通过
</u-button>
<u-button v-if="config.current == 2" class="buttom-btn launch-flow-btn" type="primary" @click.stop="handleThrough" :disabled="idDsabled" :loading="btnLoading">
通过
</u-button>
<view class="more-btn" @click.stop="toggleMoreMenu" v-if="dataForm.id && moreMenuList.length && !idDsabled || config.current == 2">
<u-icon name="more-dot-fill" size="34"></u-icon>
<text class="more-text">{{$t('common.moreText')}}</text>
<view class="more-menu" v-show="showMoreMenu" @click.stop>
<view
class="menu-item"
v-for="(item, index) in moreMenuList"
:key="index"
:style="{color: item.color || '#333'}"
>
<view @click.stop="handleMoreMenuClick(item)">
<text class="menu-label">{{item.label}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 流程选择弹框 -->
<u-popup v-model="showProcessDialog" mode="center" :round="10" >
<view class="process-dialog">
<view class="dialog-header">
<text class="dialog-title">选择流程表单</text>
<u-icon name="close" size="24" color="#999" @click="closeProcessDialog" class="close-icon"></u-icon>
</view>
<view class="process-list" v-if="!loadingProcess">
<u-radio-group v-model="selectedProcess" @change="selectProcess">
<view
class="process-item"
v-for="(item, index) in processList"
:key="item.id"
:class="{'selected-item': selectedProcess && selectedProcess === item.id}"
>
<view class="process-item-left">
<u-radio :name="item.id">{{ item.name }}</u-radio>
</view>
</view>
</u-radio-group>
<view class="empty-process" v-if="processList.length === 0">
<u-icon name="list" size="60" color="#c0c4cc"></u-icon>
<text class="empty-text">暂无可用流程</text>
</view>
</view>
<view class="loading-process" v-else>
<u-loading mode="circle" size="40"></u-loading>
<text class="loading-text">加载中...</text>
</view>
<view class="dialog-footer" v-if="processList.length > 0">
<u-button class="footer-btn cancel-btn" @click="closeProcessDialog">取消</u-button>
<u-button class="footer-btn submit-btn" type="primary" :disabled="!selectedProcess" @click="launchSelectedProcess">提交</u-button>
</view>
</view>
</u-popup>
<!-- 审批人弹框 -->
<u-popup v-model="showApproverDialog" mode="center" :round="10" @close="closeApproverDialog">
<view class="approver-dialog">
<view class="dialog-header">
<text class="dialog-title">发起人选择审批人</text>
<u-icon name="close" size="24" color="#999" @click="closeApproverDialog" class="close-icon"></u-icon>
</view>
<view class="approver-content" v-if="!loadingApprover && approverList.length > 0">
<view class="approver-node" v-for="(node, index) in approverList" :key="node.id">
<view class="node-title">
<text class="node-label">审批节点</text>
<text class="node-name">{{ node.taskDefinitionName || '未命名节点' }}</text>
</view>
<view class="candidate-section">
<view class="section-title">
<text class="section-label">候选人<text style="color:red">*</text></text>
<view>
<user-select
v-model="selectedCandidates"
:placeholder="'请选择审批人'"
:multiple="false"
:selectType="type"
@change="(value, data) => handleUserChange(node, value, data,index)"
style="margin-top: 10rpx;"
/>
</view>
</view>
</view>
</view>
</view>
<view class="loading-approver" v-else-if="loadingApprover">
<u-loading mode="circle" size="40"></u-loading>
<text class="loading-text">加载中...</text>
</view>
<view class="empty-approver" v-else>
<u-icon name="user" size="60" color="#c0c4cc"></u-icon>
<text class="empty-text">暂无审批人</text>
</view>
<view class="dialog-footer" v-if="approverList.length > 0">
<u-button class="footer-btn confirm-btn" type="primary" :disabled="!isAllCandidatesSelected" @click="confirmLaunchFlow">确定</u-button>
</view>
</view>
</u-popup>
<!-- 审批意见输入弹窗 -->
<u-popup
v-model="showApprovalReasonDialog"
mode="center"
:round="10"
:closeOnClickOverlay="false"
>
<view class="approval-reason-dialog">
<!-- 弹窗标题 -->
<view class="dialog-header">
<text class="dialog-title">{{ approvalType === 'through' ? '审批通过' : '审批不通过' }}</text>
<u-icon name="close" size="24" color="#999" @click="showApprovalReasonDialog = false" class="close-icon"></u-icon>
</view>
<!-- 输入框区域 -->
<view class="dialog-content">
<view class="input-label">
审批意见
<text style="color: red;">*</text>
</view>
<u-input
v-model="approvalReason"
type="textarea"
placeholder="请输入审批意见(选填)"
:rows="4"
border="surround"
class="reason-input"
/>
</view>
<!-- 按钮区域 -->
<view class="dialog-footer">
<u-button class="footer-btn cancel-btn" @click="showApprovalReasonDialog = false">取消</u-button>
<u-button
class="footer-btn submit-btn"
type="primary"
@click="submitApprovalResult"
:loading="approvalSubmitting"
>
确认提交
</u-button>
</view>
</view>
</u-popup>
<!-- 抄送规则弹窗 -->
<u-popup v-model="showCopyDialog" mode="center" :round="10">
<view class="copy-dialog">
<view class="dialog-header">
<text class="dialog-title">修改任务规则</text>
<u-icon name="close" size="24" color="#999" @click="showCopyDialog = false" class="close-icon"></u-icon>
</view>
<view class="dialog-content">
<view class="form-item-user required">
<view>
<text class="label">指定用户</text>
<text style="color: red;">*</text>
</view>
<view>
<user-select
v-model="copyForm.options"
placeholder="请选择用户"
:multiple="true"
:selectType="type"
/>
</view>
</view>
<view class="form-item required">
<text class="label">抄送原因</text>
<u-input
v-model="copyForm.reason"
type="textarea"
placeholder="请输入抄送原因"
:rows="3"
border="surround"
/>
</view>
</view>
<view class="dialog-footer">
<u-button class="footer-btn cancel-btn" @click="showCopyDialog = false">取消</u-button>
<u-button class="footer-btn submit-btn" type="primary" @click="submitCopyRule" :loading="copySubmitting">确定</u-button>
</view>
</view>
</u-popup>
<!-- 取消原因弹框 -->
<u-popup v-model="showCacleDialog" mode="center" :round="10">
<view class="copy-dialog">
<view class="dialog-header">
<text class="dialog-title">取消流程</text>
<u-icon name="close" size="24" color="#999" @click="showCacleDialog = false" class="close-icon"></u-icon>
</view>
<view class="dialog-content">
<view class="form-item required">
<text class="label">取消原因</text>
<u-input
v-model="cacleReason"
type="textarea"
placeholder="请输入取消原因"
:rows="3"
border="surround"
/>
</view>
</view>
<view class="dialog-footer">
<u-button class="footer-btn cancel-btn" @click="showCacleDialog = false">取消</u-button>
<u-button class="footer-btn submit-btn" type="primary" @click="submitCancel" :loading="cacleSubmitting">确定</u-button>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import CustomButton from '@/components/CustomButton'
import {
getDesForm,
createModel,
updateModel,
getModelInfo,
getdbformlist,
getAssignList,
getListCreate,
deteleModel,
launchFlow,
getByProcess
} from '@/api/apply/visualDev'
import UserSelect from '@/components/Jnpf/CandidateSelect'
import {getOrganizeSelector} from '@/api/common.js'
export default {
components: {
CustomButton,
UserSelect
},
data() {
return {
webType: '',
type: 'all',
showPage: false,
btnLoading: false,
loading: true,
isPreview: '0',
modelId: '',
formConf: {},
formData: {},
dataForm: {
id: '',
data: ''
},
btnType: '',
formPermissionList: {},
formList: [],
key: +new Date(),
config: {},
clickType: 'submit',
prevDis: false,
nextDis: false,
index: 0,
userInfo: {},
isAdd: false,
showMoreMenu: false,
moreMenuList: [],
createData: {},
deptList: [],
showProcessDialog: false,
processList: [],
selectedProcess: '',
loadingProcess: false,
showApproverDialog: false,
approverList: [],
selectedApprover: null,
loadingApprover: false,
currentProcessId: '',
selectedCandidates: {},
approvalData: [],
isProcess: false,
showApprovalReasonDialog: false,
approvalType: '',
approvalReason: '',
approvalSubmitting: false,
showCopyDialog: false,
copySubmitting: false,
copyForm: {
processInstanceKey: '',
processInstanceName: '',
options: {},
reason: ''
},
showCacleDialog: false,
cacleReason: '',
cacleSubmitting: false,
// 新增:固定审批记录相关
showFixedApproval: true,
isScrollingToApproval: false,
scrollTimer: null,
// bottomOffset: 100,
scrollHandler: null, // 用于存储滚动监听函数
}
},
computed: {
getOkText() {
return this.$t('common.saveText');
},
getProcessText() {
return '发起流程'
},
isAllCandidatesSelected() {
const result = this.approverList.every(item=>{
const {taskDefinitionKey} = item
return !!item[taskDefinitionKey]
})
return result
},
idDsabled() {
const {approveStatus} = this.dataForm
return approveStatus == 2 || approveStatus == 4
},
isApproval(){
const {current} = this.config
return !!current || this.isProcess
},
// 是否显示底部按钮
isShowBtn(){
const {approveStatus} = this.dataForm
const {config,isProcess} = this
if(config.current == 1 && approveStatus == 0) return false
if(config.current == 2) return true
if(config.current == 3 || config.current == 4) return false
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) => {
try {
return JSON.parse(rawConfig) || {}
} catch (error) {
return {}
}
}
const config = parseConfig(option.config)
const {
index,
currentMenu,
btnType = '',
modelId,
isPreview = '0',
id = '',
} = config
const formPermissionList =[]
Object.assign(this, {
userInfo: uni.getStorageSync('userInfo') || {},
config,
index,
formPermissionList,
formList: formPermissionList?.formList || [],
btnType,
modelId,
isPreview,
dataForm: {
id
}
})
if (id) {
this.initMoreMenuList()
}
const getNavigationTitle = () =>{
let titleName = ''
if(this.config.current || this.isProcess){
titleName = this.config.name + '详情'
}else {
const text = this.dataForm.id ? this.$t('common.editText') : this.$t('common.addText')
titleName = text + this.config.name
}
return titleName
}
uni.setNavigationBarTitle({
title: getNavigationTitle()
})
this.getDesForm()
},
// 计算底部按钮高度
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],
[name] : value
}
},
handleRuleUserChange(node, value, data){
},
confirmLaunchFlow() {
const result = this.approverList.every(item=>{
const {taskDefinitionKey} = item
return !!item[taskDefinitionKey]
})
if(!result){
uni.showToast({
title: '请选择候选人',
icon: 'error'
})
return
}
const assignee = {}
this.approverList.forEach(item=>{
const key = item.taskDefinitionKey;
assignee[key] = item[key];
})
const params = {
processType: 0,
processDefinitionId: this.selectedProcess,
id: this.dataForm.id,
data: {
assignee,
dbFormId : this.modelId,
dataId: this.dataForm.id,
processDefinitionId: this.selectedProcess
}
}
this.getListCreateData(params)
},
// 请求日志
getListCreateData(params){
getListCreate(params,this.modelId).then(res=>{
if(res.code == 0) {
let name = '发起成功'
switch(this.approvalType){
case 'through':
name = '审批通过成功'
break;
case 'fail':
name = '审批不通过成功'
break;
case 'cancel':
name = '取消成功'
break;
}
uni.showToast({
title: name,
icon: 'success'
})
setTimeout(() => {
uni.$emit('refresh')
uni.navigateBack()
}, 1500)
}
})
},
initMoreMenuList() {
let menuList = []
console.log(this.dataForm.approveStatus,'approveStatus--')
if((!this.dataForm.approveStatus && this.dataForm.id) || this.dataForm.approveStatus == 3){
// 未审核 | 驳回
menuList.push({
label: '删除',
value: 'delete',
icon: 'icon-ym icon-ym-app-delete',
color: '#dd524d'
})
}
if(this.config.current == 2){
menuList.unshift({
label: '抄送',
value: 'send',
icon: 'icon-ym icon-ym-flow-launch',
color: '#409eff'
},
{
label: '回退',
value: 'reject',
icon: 'icon-ym icon-ym-flow-launch',
color: '#e6a23c'
})
}
this.moreMenuList = menuList
},
toggleMoreMenu() {
this.showMoreMenu = !this.showMoreMenu
if (this.showMoreMenu) {
setTimeout(() => {
uni.$once('clickOutside', () => {
this.showMoreMenu = false
})
}, 100)
}
},
handleMoreMenuClick(item) {
this.showMoreMenu = false
switch (item.value) {
case 'edit':
this.handleEdit()
break
case 'send':
this.handleSend()
break
case 'reject':
this.handleReject()
break
case 'delete':
this.handleDelete()
break
}
},
handleEdit() {
this.btnType = 'btn_edit'
this.initData()
uni.showToast({
title: '进入编辑状态',
icon: 'none'
})
},
handleLaunchFlow() {
this.getProcessList()
},
async getProcessList() {
try {
this.loadingProcess = true
const res = await getdbformlist(this.modelId)
if (res.code === 0 || res.code === 200) {
this.processList = res.data || []
if(this.processList.length === 1){
this.selectProcess(res.data[0].id)
}
if (this.processList.length === 0) {
uni.showToast({
title: '暂无可用流程',
icon: 'none'
})
return
}
this.showProcessDialog = true
} else {
uni.showToast({
title: res.msg || '获取流程列表失败',
icon: 'none'
})
}
} catch (error) {
console.error('获取流程列表失败:', error)
uni.showToast({
title: '获取流程列表失败',
icon: 'none'
})
} finally {
this.loadingProcess = false
}
},
// 抄送
handleSend() {
this.showCopyDialog = true
},
// 抄送-提交
submitCopyRule(){
const {options,reason} = this.copyForm
if(!options.length || !reason){
const name = !options.length ? '指定用户不能为空' : '抄送原因不能为空'
uni.showToast({
title: name,
icon: 'none'
})
return
}
const {id,processInstance} = this.approvalData[0]
const params = {
processType: '4',
processDefinitionId:Object.values(this.copyForm.options),
processInstanceId: this.formData.processInstanceId,
id: this.dataForm.id,
data: {
startUserId: processInstance.startUserId,
taskId: id,
...this.copyForm,
options: Object.values(this.copyForm.options),
processInstanceKey:processInstance.id,
processInstanceName:processInstance.name
}
}
this.getListCreateData(params)
},
handleReject() {
const {id,processInstance} = this.approvalData[0]
const params = {
processType: '3',
processInstanceId: this.formData.processInstanceId,
id: this.dataForm.id,
data: {
taskId: id
}
}
this.getListCreateData(params)
},
selectProcess(item) {
console.log(item,'item----')
this.selectedProcess = item
},
async launchSelectedProcess() {
if (!this.selectedProcess) {
uni.showToast({
title: '请选择一个流程',
icon: 'none'
})
return
}
this.showProcessDialog = false
await this.getApproverList()
},
async getApproverList() {
try {
const res = await getAssignList(this.selectedProcess)
if (res.code === 0 || res.code === 200) {
this.approverList = res.data || []
let userSelectRules = res.data?.filter(rule => rule.type === 35)
if(userSelectRules?.length > 0){
this.loadingApprover = true
this.showApproverDialog = true
this.selectedCandidates = {}
}else{
const params = {
processType: 0,
processDefinitionId: this.selectedProcess,
id: this.dataForm.id,
data: {
dbFormId : this.modelId,
dataId: this.dataForm.id,
processDefinitionId: this.selectedProcess
}
}
this.getListCreateData(params)
}
} else {
uni.showToast({
title: res.msg || '获取审批人失败',
icon: 'none'
})
}
} catch (error) {
console.error('获取审批人失败:', error)
uni.showToast({
title: '获取审批人失败',
icon: 'none'
})
} finally {
this.loadingApprover = false
}
},
closeProcessDialog() {
this.showProcessDialog = false
this.selectedProcess = ''
},
closeApproverDialog() {
this.showApproverDialog = false
},
handleDelete() {
uni.showModal({
title: '警告',
content: '删除后数据无法恢复,确认删除吗?',
success: (res) => {
if (res.confirm) {
createModel(this.modelId,{
operateType: 3,
id: this.dataForm.id,
data: {}
}, this.modelId).then(res => {
uni.showToast({
title: res.msg || '删除成功',
icon: 'success',
complete: () => {
setTimeout(() => {
uni.$emit('refresh')
uni.navigateBack()
}, 1500)
}
})
}).catch(err => {
uni.showToast({
title: err.msg || '删除失败',
icon: 'none'
})
})
}
}
})
},
// 点击通过 - 打开审批意见弹窗
handleThrough() {
this.approvalType = 'through'
this.approvalReason = '' // 清空之前的意见
this.showApprovalReasonDialog = true
},
// 点击不通过 - 打开审批意见弹窗
handleFail() {
this.approvalType = 'fail'
this.approvalReason = '' // 清空之前的意见
this.showApprovalReasonDialog = true
},
// 取消
handleCancle() {
this.approvalType = 'cancel'
this.cacleReason = ''
this.showCacleDialog = true
},
// 取消弹框确认
submitCancel() {
if(!this.cacleReason){
uni.showToast({
title: '请输入取消原因',
icon: 'none'
})
return
}
try {
this.cacleSubmitting = true
const params = {
processType: 5,
processInstanceId: this.formData.processInstanceId, // 流程实例ID
id: this.formData.id,
data: {
dbFormId : this.modelId,
id: this.approvalData && this.approvalData[0].id, // 审批记录第一条id
reason: this.cacleReason || '',
}
}
//调用
this.getListCreateData(params)
} catch (err) {
console.error('取消失败:', err)
uni.showToast({
title: '取消失败,请重试',
icon: 'none'
})
} finally {
this.cacleSubmitting = false
}
},
// 查看流程
goProcess(){
this.isProcess = true
console.log(this.config,'config-')
uni.setNavigationBarTitle({
title: `${this.config.name}流程详情`
})
},
// 提交审批结果(通过/不通过)
async submitApprovalResult() {
if(!this.approvalReason){
uni.showToast({
title: '请填写审批意见',
icon: 'none'
})
return
}
try {
this.approvalSubmitting = true
const params = {
processType: this.approvalType === 'through' ? 1 : 2, // 1=通过2=不通过
processInstanceId: this.formData.processInstanceId, // 流程实例ID
id: this.formData.id,
data: {
dbFormId : this.modelId,
id: this.approvalData && this.approvalData[0].id, // 审批记录第一条id
reason: this.approvalReason || '', // 审批意见
}
}
//调用审批接口
this.getListCreateData(params)
} catch (err) {
console.error('提交审批结果失败:', err)
uni.showToast({
title: '提交失败,请重试',
icon: 'none'
})
} finally {
this.approvalSubmitting = false
}
},
// 获取布局和初始值
async getDesForm() {
try {
const res = await getDesForm(this.config.modelId);
if((res.code == 0 || res.code == 200) && res.data !== null){
const data = res.data ? JSON.parse(res.data) : {};
this.formConf = JSON.parse(data.formData);
console.log(this.formConf,'formConf--')
console.log(data,'data--111')
this.formConf.labelWidth = 160;
this.createData = data
await this.getDeptList();
await this.fillFormData(this.formConf, this.formData);
this.showPage = true;
await this.initData();
this.loading = false;
} else {
uni.showToast({
title: '暂无此页面',
icon: 'none',
complete: () => {
setTimeout(() => {
uni.navigateBack();
}, 1500)
}
})
}
} catch (error) {
console.error('获取表单配置失败:', error);
this.loading = false;
}
},
getDeptList() {
return new Promise((resolve) => {
getOrganizeSelector({type:'all'}).then(res => {
if (res.code == 0 && res.data) {
this.deptList = this.formatDeptData(res.data);
}
resolve();
}).catch((error) => {
console.error('获取部门列表失败:', error);
resolve();
})
})
},
formatDeptData(data) {
const result = [];
const formatItem = (item) => {
let deptId = item.depthd || item.id || item.deptId;
let deptName = item.depthName || item.deptName || item.fullName || item.name || item.orgNameTree;
deptId = String(deptId || '');
return {
...item,
deptId: deptId,
deptName: deptName ,
depthd: item.depthd,
depthName: item.depthName,
id: item.id
};
};
if (Array.isArray(data)) {
data.forEach(item => {
const formatted = formatItem(item);
if (formatted.deptId) {
result.push(formatted);
}
if (item.children && Array.isArray(item.children)) {
const children = this.formatDeptData(item.children);
result.push(...children);
}
});
}
return result;
},
async initData() {
try {
await this.$nextTick();
if (this.dataForm.id) {
const extra = {
modelId: this.modelId,
id: this.dataForm.id,
type: 1
};
uni.setStorageSync('dynamicModelExtra', extra);
const res = await getModelInfo(this.modelId, this.dataForm.id);
if(!!res.data.approveStatus&&(res.data.approveStatus !== 0 || res.data.approveStatus == 3)){
// 不是未发起和驳回的都不可编辑
this.disableAllFormFields();
}
this.dataForm = res.data;
if (!res.data) return;
const {fields = []} = this.formConf
const result = fields.reduce((acc,current)=>{
const key = current.__vModel__;
if(res.data.hasOwnProperty(key)){
acc[key] = res.data[key];
}else if(res.data.jeelowcode_subtable_data && res.data.jeelowcode_subtable_data.hasOwnProperty(key)){
acc[key] = res.data.jeelowcode_subtable_data[key];
}
return acc;
},{})
this.formData = {
...res.data,
id: this.dataForm.id,
sDate: res.data.sDate,
eDate: res.data.eDate ? new Date(res.data.eDate).getTime() : null,
applyDepId: res.data.applyDepId ? String(res.data.applyDepId) : '',
applyDepName: res.data.applyDepName || '',
applyDepData: res.data.applyDepData || null,
...result
};
console.log(this.formData,'formData123')
this.$nextTick(()=>{
if (this.$refs.dynamicForm && this.$refs.dynamicForm.setFormData) {
this.$refs.dynamicForm.setFormData('applyDepId', this.formData.applyDepId);
}
})
// 获取审批记录
await this.getApprovalData();
this.initMoreMenuList()
} else {
console.log(this.formData,'formData111')
const {user,deptInfoList} = this.userInfo
this.isAdd = true;
this.formData = {
...this.formData,
...this.createData,
applyDepId: deptInfoList ? String(deptInfoList[0].deptId) : '' ,
applyDepName: deptInfoList && deptInfoList[0].deptName,
applyUser: user.nickname
};
}
if (this.deptList && this.deptList.length > 0) {
await this.fillFormData(this.formConf, this.formData);
if (this.$refs.dynamicForm && this.$refs.dynamicForm.setFormData) {
this.$refs.dynamicForm.setFormData(this.formConf.formData);
}
setTimeout(() => {
this.key = +new Date();
}, 200);
} else {
console.warn('部门列表未加载完成,等待重试');
setTimeout(() => {
this.initData();
}, 300);
}
this.loading = false;
} catch (error) {
console.error('初始化数据失败:', error);
this.loading = false;
}
},
disableAllFormFields() {
const disableField = (field) => {
// 禁用当前字段
if (field.__config__) {
this.$set(field.__config__, 'disabled', true);
this.$set(field, 'disabled', true);
}
// 禁用子表字段
if (field.children && Array.isArray(field.children)) {
field.children.forEach(child => disableField(child));
}
// 禁用嵌套字段
if (field.__config__ && field.__config__.children && Array.isArray(field.__config__.children)) {
field.__config__.children.forEach(child => disableField(child));
}
// 特殊处理组件值
if (field.modelValue !== undefined) {
this.$set(field, 'modelValue', field.modelValue);
}
if (field.value !== undefined) {
this.$set(field, 'value', field.value);
}
};
// 遍历所有顶级字段
if (this.formConf && this.formConf.fields && Array.isArray(this.formConf.fields)) {
this.formConf.fields.forEach(field => {
disableField(field);
});
}
// 禁用整个表单组件
if (this.$refs.dynamicForm) {
this.$refs.dynamicForm.disabled = true;
}
},
async fillFormData(form, data) {
return new Promise((resolve) => {
this.$nextTick(() => {
const loop = (list, parent) => {
for (let i = 0; i < list.length; i++) {
let item = list[i];
let vModel = item.__vModel__;
let config = item.__config__;
if (vModel === 'applyDepId' && config.jnpfKey === 'organizeSelect') {
this.type = item.type = config.type
item.options = [...(this.deptList || [])].map(dept => ({
id: String(dept.deptId || dept.id),
orgNameTree: dept.deptName || dept.depthName || dept.orgNameTree ,
deptId: dept.deptId,
deptName: dept.deptName,
...dept
}));
const applyDepId = data.applyDepId ? String(data.applyDepId) : '';
console.log(applyDepId,'applyDepId---')
const applyDepName = data.applyDepName || '';
if (applyDepId && applyDepId !== '') {
let matchedOption = item.options.find(opt =>
String(opt.id) === applyDepId
);
if (!matchedOption && applyDepName) {
matchedOption = {
id: applyDepId,
orgNameTree: applyDepName,
deptId: applyDepId,
deptName: applyDepName
};
item.options.unshift(matchedOption);
}
config.defaultValue = applyDepId;
item.modelValue = applyDepId;
item.value = applyDepId;
item.__value__ = matchedOption ? matchedOption.orgNameTree : applyDepName;
const innerSelectedData = matchedOption ? [
{
id: matchedOption.id,
orgNameTree: matchedOption.orgNameTree,
deptId: matchedOption.deptId,
deptName: matchedOption.deptName
}
] : [];
item.props = {
value: 'id',
label: 'orgNameTree',
innerSelectedData: innerSelectedData
};
item.selectedData = matchedOption ? [
{
id: matchedOption.id,
orgNameTree: matchedOption.orgNameTree,
deptId: matchedOption.deptId,
deptName: matchedOption.deptName
}
] : [];
item.innerSelectedData = item.selectedData;
if (!form.formData) form.formData = {};
form.formData[vModel] = applyDepId;
form.formData['applyDepName'] = item.__value__;
console.log(form.formData,'form.formData---')
console.log(item,'item---')
} else {
item.selectedData = [];
item.modelValue = '';
item.__value__ = '';
config.defaultValue = '';
if (form.formData) {
form.formData[vModel] = '';
form.formData['applyDepName'] = '';
}
}
}
if(vModel == 'applyUser') {
this.$set(item, 'disabled', true);
}
if (vModel) {
let val = data.hasOwnProperty(vModel) ? data[vModel] : config.defaultValue;
if (!config.isSubTable) config.defaultValue = val;
if (this.isAdd || config.isSubTable) {
if (config.defaultCurrent) {
if (config.jnpfKey === 'datePicker') {
if (!data.hasOwnProperty(vModel)) {
let format = this.jnpf.handelFormat(item.format)
let dateStr = this.jnpf.toDate(new Date().getTime(), format)
let time = format === 'yyyy' ? '-01-01 00:00:00' : format === 'yyyy-MM' ?
'-01 00:00:00' : format === 'yyyy-MM-dd' ?
' 00:00:00' : ''
val = new Date(dateStr + time).getTime()
config.defaultValue = val
}
}
if (config.jnpfKey === 'timePicker') {
if (!data.hasOwnProperty(vModel)) {
config.defaultValue = this.jnpf.toDate(new Date(), item.format)
}
}
if (config.jnpfKey === 'organizeSelect' && this.userInfo.organizeIds?.length) {
config.defaultValue = item.multiple ? this.userInfo.organizeIds :
this.userInfo.organizeId
}
if (config.jnpfKey === 'posSelect' && this.userInfo.positionIds?.length) {
config.defaultValue = item.multiple ? this.userInfo.positionIds :
this.userInfo.positionId
}
const userId = this.userInfo.userId
if (config.jnpfKey === 'userSelect' && userId) {
config.defaultValue = item.multiple ? [userId] : userId;
}
if (config.jnpfKey === 'usersSelect' && userId) {
config.defaultValue = [userId + '--user'];
}
if (config.jnpfKey === 'sign' && this.userInfo.signImg) {
config.defaultValue = this.userInfo.signImg
}
}
}
const btn_detail = this.$permission.hasBtnP('btn_detail', this.formPermissionList?.menuId)
const btn_edit = this.$permission.hasBtnP('btn_edit', this.formPermissionList?.menuId)
if (!!this.dataForm.id && !btn_edit && btn_detail) item.disabled = btn_detail
let noShow = !config.noShow ? false : config.noShow
let isVisibility = false
if (!config.visibility || (Array.isArray(config.visibility) && config.visibility.includes('app'))) {
isVisibility = true
}
this.$set(config, 'isVisibility', isVisibility)
if (this.formPermissionList.useFormPermission) {
let id = config.isSubTable ? parent.__vModel__ + '-' + vModel : vModel
noShow = true
if (this.formList && this.formList.length) {
noShow = !this.formList.some(o => o.enCode === id)
}
noShow = config.noShow ? config.noShow : noShow
this.$set(config, 'noShow', noShow)
}
} else {
let noShow = config.noShow ? config.noShow : false,
isVisibility = false
if (!config.visibility || (Array.isArray(config.visibility) && config.visibility.includes('app'))) {
isVisibility = true
}
this.$set(config, 'isVisibility', isVisibility)
this.$set(config, 'noShow', noShow)
}
if (config && config.children && Array.isArray(config.children)) {
loop(config.children, item);
}
}
};
loop(form.fields);
if (!form.formData) {
form.formData = {};
}
const formDataToMerge = {
...data,
applyDepId: data.applyDepId || '',
applyDepName: data.applyDepName || ''
};
form.formData = { ...form.formData, ...formDataToMerge };
setTimeout(() => {
const applyField = form.fields.find(f => f.__vModel__ === 'applyDepId');
if (!applyField || !applyField.selectedData || applyField.selectedData.length === 0) {
console.warn('申请单位无选中数据');
return;
}
const dynamicForm = this.$refs.dynamicForm;
if (!dynamicForm) return;
const findOrganizeSelect = (component) => {
if (component.$options.name === 'jnpf-organize-select') {
return component;
}
const children = component.$children || [];
for (let child of children) {
const res = findOrganizeSelect(child);
if (res) return res;
}
return null;
};
const organizeSelect = findOrganizeSelect(dynamicForm);
if (organizeSelect) {
organizeSelect.selectedData = applyField.selectedData;
} else {
console.warn('未找到jnpf-organize-select组件');
}
}, 1000);
this.key = +new Date();
setTimeout(() => {
const finalApplyField = form.fields.find(f => f.__vModel__ === 'applyDepId');
if (finalApplyField) {
}
}, 100);
resolve();
});
});
},
sumbitForm(data, callback) {
const {billNoPrefix} = this.config
if (!data) return;
const filePathArr = [...data.attachment]
const attachment = filePathArr.map(item => item.fileUrl)
this.btnLoading = true;
const applyDepName = this.deptList?.filter(item=>item.deptId === data.applyDepId)[0]?.deptName
const formData = {
...this.formData,
...data,
id: data?.id || '',
applyDepName,
attachment: attachment.join(',')
};
if(!this.dataForm.id){
formData.billNo = billNoPrefix + new Date().getTime()
}
this.dataForm.data = JSON.stringify(formData);
const params = {
id: this.dataForm.id,
operateType: this.dataForm.id ? 1 : 0,
attachments: data.attachment,
data: {
...formData,
eDate: this.formatTime(formData.eDate),
sDate: this.formatTime(formData.sDate),
}
}
const formMethod = createModel;
console.log(params,'params---')
formMethod(this.modelId, params).then(res => {
uni.showToast({
title:'保存成功',
complete: () => {
setTimeout(() => {
if (this.clickType == 'save_add') {
this.key = +new Date();
this.$nextTick(() => {
this.$refs.dynamicForm && this.$refs.dynamicForm.resetForm();
});
}
this.btnLoading = false;
this.initData();
if (this.clickType != 'save_proceed' && this.clickType != 'save_add') {
uni.$emit('refresh');
uni.navigateBack();
}
}, 1500);
}
});
}).catch(() => {
this.btnLoading = false;
});
if (callback && typeof callback === "function") callback();
},
commonSubmit(type) {
this.clickType = type;
this.submit(type);
},
submit(type) {
console.log(type,'type333')
this.clickType = type;
this.$refs.dynamicForm && this.$refs.dynamicForm.submitForm();
},
// 获取审批记录
async getApprovalData() {
try {
const res = await getByProcess(this.formData.processInstanceId);
const result = []
if (res.code === 0) {
res.data.forEach((task) => {
if (task.result !== 4) {
result.push(task)
}
})
result.sort((a, b) => {
// 有已完成的情况,按照完成时间倒序
if (a.endTime && b.endTime) {
return b.endTime - a.endTime
} else if (a.endTime) {
return 1
} else if (b.endTime) {
return -1
// 都是未完成,按照创建时间倒序
} else {
return b.createTime - a.createTime
}
})
}
this.approvalData = result
// 审批记录加载完成后,检查一次可见性
this.$nextTick(() => {
setTimeout(() => {
this.checkApprovalVisibility()
}, 300)
})
} catch (err) {
console.error('获取审批记录失败:', err);
}
},
// 状态样式
getStatusClass(result) {
return {
'status-processing': result === 1,
'status-pass': result === 2,
'status-reject': result === 3
};
},
getStatusTagClass(result) {
return {
'tag-processing': result === 1,
'tag-pass': result === 2,
'tag-reject': result === 3
};
},
// 时间格式化
formatTime(timestamp) {
if (!timestamp) return '-';
const date = new Date(timestamp);
return `${date.getFullYear()}-${this.padZero(date.getMonth() + 1)}-${this.padZero(date.getDate())} ${this.padZero(date.getHours())}:${this.padZero(date.getMinutes())}:${this.padZero(date.getSeconds())}`;
},
padZero(num) {
return num.toString().padStart(2, '0');
},
// 耗时格式化
formatDuration(millis) {
if (!millis) return '-';
const seconds = Math.floor(millis / 1000);
if (seconds < 60) {
return `${seconds}`;
}
const minutes = Math.floor(seconds / 60);
if (minutes < 60) {
return `${minutes} 分钟`;
}
const hours = Math.floor(minutes / 60);
return `${hours} 小时 ${minutes % 60} 分钟`;
}
}
}
</script>
<style lang="scss">
page {
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;
background: #fff;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 999 !important;
padding: 10rpx 0rpx;
// 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;
}
.more-btn {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 80rpx;
height: 80rpx;
flex: 1;
.more-text {
font-size: 22rpx;
color: #606266;
margin-top: 4rpx;
}
.more-menu {
position: absolute;
bottom: 100%;
right: 0;
background: #fff;
border-radius: 8rpx;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.1);
width: 200rpx;
z-index: 100;
padding: 10rpx 0;
.menu-item {
display: flex;
align-items: center;
padding: 16rpx 20rpx;
font-size: 28rpx;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: #f5f7fa;
}
&:active {
background-color: #ebeef5;
}
.menu-label {
flex: 1;
}
}
}
}
/* 流程选择弹框样式 */
.process-dialog {
width: 90vw;
max-height: 70vh;
background: #fff;
border-radius: 12rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.dialog-header {
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.dialog-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.close-icon {
padding: 10rpx;
}
.process-list {
flex: 1;
overflow-y: auto;
max-height: 50vh;
padding: 20rpx 0;
}
.process-item {
display: flex;
align-items: flex-start;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f8f8f8;
transition: background-color 0.3s;
cursor: pointer;
&.selected-item {
background-color: #f5f7fa;
border-left: 4rpx solid #2979ff;
}
}
.process-item:active {
background-color: #f0f2f5;
}
.process-item-left {
margin-right: 20rpx;
padding-top: 6rpx;
}
.empty-process {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
}
.loading-process {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
}
.loading-text {
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
}
.dialog-footer {
display: flex;
padding: 30rpx;
border-top: 1rpx solid #f0f0f0;
gap: 20rpx;
}
.footer-btn {
flex: 1;
}
.cancel-btn {
background: #f5f5f5;
color: #666;
}
.submit-btn {
background: linear-gradient(135deg, #3d6dcc, #2a52b5);
color: #fff;
}
.submit-btn[disabled] {
opacity: 0.6;
}
/* 审批人选择弹框样式 */
.approver-dialog {
width: 90vw;
max-height: 80vh;
background: #fff;
border-radius: 12rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.approver-content {
flex: 1;
overflow-y: auto;
max-height: 60vh;
padding: 30rpx;
}
.approver-node {
margin-bottom: 40rpx;
padding: 20rpx;
background: #f9fafc;
border-radius: 8rpx;
}
.node-title {
margin-bottom: 20rpx;
padding-bottom: 10rpx;
border-bottom: 1rpx solid #ebeef5;
display: flex;
justify-content: space-between;
}
.node-label {
font-size: 28rpx;
color: #606266;
}
.node-name {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.candidate-section {
margin-top: 20rpx;
}
.section-title {
margin-bottom: 16rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.section-label {
font-size: 26rpx;
color: #606266;
font-weight: 500;
}
.loading-approver {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
}
.empty-approver {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
}
.confirm-btn {
background: linear-gradient(135deg, #3d6dcc, #2a52b5);
color: #fff;
margin: 0 30rpx 30rpx;
}
.confirm-btn[disabled] {
opacity: 0.6;
}
/* 审批记录样式 */
.approval-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #eee;
}
.approval-list {
padding: 10rpx 30rpx;
}
.approval-item {
display: flex;
position: relative;
margin-bottom: 20rpx;
padding-top: 10rpx;
}
.approval-line-container {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 16rpx;
}
.approval-line {
width: 2rpx;
background-color: #8e8585;
flex: 1;
margin-top: 8rpx;
}
.status-icon {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
// background-color: #fff;
border: 2rpx solid;
/* 状态点边框色 */
&.status-processing {
background-color: #409EFF;
border-color: #409EFF;
}
&.status-pass {
background-color: #67C23A;
border-color: #67C23A;
}
&.status-reject {
background-color: #F56C6C;
border-color: #F56C6C;
}
}
.approval-content {
flex: 1;
background-color: #fff;
border-radius: 8rpx;
padding: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
}
.task-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
.task-name {
font-size: 28rpx;
color: #333;
}
}
.status-tag {
display: flex;
align-items: center;
font-size: 22rpx;
padding: 2rpx 8rpx;
border-radius: 4rpx;
margin-left: 12rpx;
/* 状态对应的样式 */
&.tag-processing {
background-color: #E6F7FF;
color: #409EFF;
}
&.tag-pass {
background-color: #F0F9EB;
color: #67C23A;
}
&.tag-reject {
background-color: #FFF2F0;
color: #F56C6C;
}
/* 状态标签内的时间 */
.status-time {
font-size: 20rpx;
margin-left: 8rpx;
}
}
.info-row {
font-size: 24rpx;
color: #666;
line-height: 1.5;
margin-bottom: 12rpx;
.info-label {
color: #999;
}
.info-value {
color: #666;
}
.info-value-tag {
background: #f4f4f5;
color: #a0a4a7;
padding: 2rpx 10rpx;
margin-left: 10rpx;
border: 10rpx;
}
.pending-tag {
display: inline-block;
background-color: #FAFAFA;
border: 1rpx solid #E5E5E5;
color: #666;
padding: 2rpx 8rpx;
border-radius: 4rpx;
margin-left: 10rpx;
font-size: 20rpx;
}
.dept-tag {
display: inline-block;
background-color: #F5F7FA;
color: #666;
padding: 2rpx 8rpx;
border-radius: 4rpx;
margin-left: 8rpx;
font-size: 22rpx;
}
.ml-20 {
margin-left: 20rpx;
}
}
.reason-box {
font-size: 24rpx;
color: #333;
background-color: #F9F9F9;
padding: 12rpx;
border-radius: 4rpx;
margin-top: 8rpx;
}
/* 新增:审批意见弹窗样式 */
.approval-reason-dialog {
width: 80vw;
max-width: 600rpx;
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.approval-reason-dialog .dialog-header {
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.approval-reason-dialog .dialog-content {
padding: 30rpx;
}
.approval-reason-dialog .input-label {
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}
.approval-reason-dialog .reason-input {
font-size: 26rpx;
}
.approval-reason-dialog .dialog-footer {
padding: 20rpx 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.copy-dialog {
width: 90vw;
max-height: 80vh;
background: #fff;
border-radius: 12rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.dialog-content {
flex: 1;
overflow-y: auto;
padding: 30rpx;
}
.form-item-user {
display: flex;
justify-content: space-between;
align-items: center;
}
.form-item {
margin-bottom: 24rpx;
display: flex;
flex-direction: column;
}
.form-item.required .label::after {
content: '*';
color: #F56C6C;
margin-left: 4rpx;
}
.label {
font-size: 28rpx;
color: #333;
margin-bottom: 12rpx;
}
.value {
font-size: 26rpx;
color: #666;
padding: 12rpx;
background: #f9fafc;
border-radius: 6rpx;
}
</style>