Files
jnpf_app/pages/apply/dynamicModelList/form.vue
2026-01-23 15:16:31 +08:00

1825 lines
48 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="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 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 && (!(dataForm.approveStatus == 1 || dataForm.approveStatus == 2 || dataForm.approveStatus == 4) || !dataForm.id || config.current && config.current == 2)" class="buttom-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" class="buttom-btn" type="primary" @click.stop="submit" :disabled="idDsabled" :loading="btnLoading">
保存
</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-icon" :class="item.icon"></text> -->
<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">审批意见</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">
<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">
<text class="label">指定用户</text>
<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>
</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: [], // 存储审批记录
//审批意见弹窗相关数据
showApprovalReasonDialog: false, // 弹窗显示状态
approvalType: '', // 审批类型through(通过) / fail(不通过)
approvalReason: '', // 审批意见内容
approvalSubmitting: false, // 提交加载状态
// 抄送
showCopyDialog: false,
copySubmitting: false,
copyForm: {
processInstanceKey: '',
processInstanceName: '',
options: {},
reason: ''
}
}
},
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
}
},
onLoad(option) {
this.init(option)
},
methods: {
init(option) {
const parseConfig = (rawConfig) => {
try {
return JSON.parse(this.jnpf.base64.decode(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){
titleName = this.config.name + '详情'
}else {
const name = this.dataForm.id ? this.$t('common.editText') : this.$t('common.addText')
titleName = name + this.config.name
}
return titleName
}
uni.setNavigationBarTitle({
title: getNavigationTitle()
})
this.getDesForm()
},
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) {
uni.showToast({
title: '发起成功',
icon: 'success'
})
setTimeout(() => {
uni.$emit('refresh')
uni.navigateBack()
}, 1500)
}
})
},
initMoreMenuList() {
let menuList = [
// {
// label: '删除',
// value: 'delete',
// icon: 'icon-ym icon-ym-app-delete',
// color: '#dd524d'
// }
]
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])
}
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 {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', // 回退code todo
// processDefinitionId:Object.values(this.copyForm.options),
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
},
// 提交审批结果(通过/不通过)
async submitApprovalResult() {
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);
}
// 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;
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;
}
for (let child of component.$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
}
}
const formMethod = createModel;
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) {
// this.approvalData = res.data
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
} 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;
}
.buttom-actions {
display: flex;
align-items: center;
// justify-content: flex-end;
// padding: 20rpx 30rpx;
background: #fff;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 999 !important;
padding: 10rpx 0rpx;
// gap: 20rpx;
.buttom-btn {
width: 300rpx;
min-width: 300rpx;
padding: 12rpx 15rpx;
flex: 1;
}
}
.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;
&:hover {
background-color: #f5f7fa;
}
.menu-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.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>