Files

1645 lines
43 KiB
Vue
Raw Permalink Normal View History

2026-01-04 11:09:06 +08:00
<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" />
2026-01-20 18:07:35 +08:00
<!-- 审批记录区域 -->
<view v-if="false&&(dataForm.approveStatus == 1 || dataForm.approveStatus == 2)">
<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-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 ? '通过' : '不通过') }}
</view>
</view>
<view class="info-row">
<text class="info-label">审批人</text>
<text class="info-value">{{ item.assigneeUser.nickname }}</text>
<view class="dept-tag">{{ item.assigneeUser.deptName }}</view>
<text class="info-label ml-20">创建时间</text>
<text class="info-value">{{ formatTime(item.createTime) }}</text>
<text class="info-label ml-20" v-if="item.endTime">审批时间</text>
<text class="info-value" v-if="item.endTime">{{ formatTime(item.endTime) }}</text>
<text class="info-label ml-20" v-if="item.durationInMillis">耗时</text>
<text class="info-value" v-if="item.durationInMillis">{{ formatDuration(item.durationInMillis) }}</text>
</view>
<view class="reason-box" v-if="item.reason">
{{ item.reason }}
</view>
</view>
</view>
</view>
</view>
<!-- 底部操作按钮区域 -->
2026-01-04 11:09:06 +08:00
<view class="buttom-actions" v-if="btnType === 'btn_edit' || btnType === 'btn_add'">
2026-01-20 18:07:35 +08:00
<CustomButton
class="u-flex buttom-btn-left-inner"
:btnText="getCancelText"
btnIcon="icon-ym icon-ym-add-cancel"
customIcon
:btnLoading="btnLoading"
/>
<u-button class="buttom-btn" type="primary" @click.stop="submit" :disabled="idDsabled" :loading="btnLoading">
2026-01-04 11:09:06 +08:00
{{getOkText}}
</u-button>
2026-01-20 18:07:35 +08:00
<view class="more-btn" @click.stop="toggleMoreMenu" v-if="dataForm.id && moreMenuList.length && !idDsabled">
<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>
2026-01-04 11:09:06 +08:00
</view>
2026-01-20 18:07:35 +08:00
<!-- 流程选择弹框 -->
<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 @change="selectProcess">
<view
class="process-item"
v-for="(item, index) in processList"
:key="item.id"
:class="{'selected-item': selectedProcess && selectedProcess.id === 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>
2026-01-04 11:09:06 +08:00
</view>
</template>
2026-01-20 18:07:35 +08:00
2026-01-04 11:09:06 +08:00
<script>
import CustomButton from '@/components/CustomButton'
import {
2026-01-20 18:07:35 +08:00
getDesForm,
2026-01-04 11:09:06 +08:00
createModel,
updateModel,
2026-01-20 18:07:35 +08:00
getModelInfo,
getdbformlist,
getAssignList,
getListCreate,
deteleModel,
launchFlow,
getByProcess
2026-01-04 11:09:06 +08:00
} from '@/api/apply/visualDev'
2026-01-20 18:07:35 +08:00
import UserSelect from '@/components/Jnpf/CandidateSelect'
import {getOrganizeSelector} from '@/api/common.js'
2026-01-04 11:09:06 +08:00
export default {
components: {
2026-01-20 18:07:35 +08:00
CustomButton,
UserSelect
2026-01-04 11:09:06 +08:00
},
data() {
return {
webType: '',
2026-01-20 18:07:35 +08:00
type: 'all',
2026-01-04 11:09:06 +08:00
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: {},
2026-01-20 18:07:35 +08:00
isAdd: false,
showMoreMenu: false,
moreMenuList: [],
lc_fire_operation_detail: [],
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 // 提交加载状态
2026-01-04 11:09:06 +08:00
}
},
computed: {
getOkText() {
const text = this.formConf.confirmButtonTextI18nCode ?
this.$t(this.formConf.confirmButtonTextI18nCode, this.formConf.confirmButtonText) :
this.formConf.confirmButtonText;
return text || this.$t('common.okText');
},
getCancelText() {
const text = this.formConf.cancelButtonTextI18nCode ?
this.$t(this.formConf.cancelButtonTextI18nCode, this.formConf.cancelButtonText) :
this.formConf.cancelButtonText;
return text || this.$t('common.cancelText');
2026-01-20 18:07:35 +08:00
},
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
2026-01-04 11:09:06 +08:00
}
},
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
2026-01-20 18:07:35 +08:00
const formPermissionList =[]
2026-01-04 11:09:06 +08:00
Object.assign(this, {
userInfo: uni.getStorageSync('userInfo') || {},
config,
index,
formPermissionList,
2026-01-20 18:07:35 +08:00
formList: formPermissionList?.formList || [],
2026-01-04 11:09:06 +08:00
btnType,
modelId,
isPreview,
dataForm: {
id
}
})
2026-01-20 18:07:35 +08:00
if (id) {
this.initMoreMenuList()
}
2026-01-04 11:09:06 +08:00
const getNavigationTitle = () => this.dataForm.id ? this.$t('common.editText') : this.$t('common.addText')
uni.setNavigationBarTitle({
2026-01-20 18:07:35 +08:00
title: getNavigationTitle() + this.config.name
2026-01-04 11:09:06 +08:00
})
2026-01-20 18:07:35 +08:00
this.getDesForm()
},
handleUserChange(node, value, data,index){
const name = this.approverList[index]['taskDefinitionKey']
this.approverList[index] ={
...this.approverList[index],
[name] : value
}
2026-01-04 11:09:06 +08:00
},
2026-01-20 18:07:35 +08:00
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
}
}
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: 'through',
// icon: 'icon-ym icon-ym-flow-launch',
// color: '#67c23a'
// },
// {
// label: '不通过',
// value: 'fail',
// icon: 'icon-ym icon-ym-flow-launch',
// color: '#f56c6c'
// },
// {
// label: '发起流程',
// value: 'launchFlow',
// icon: 'icon-ym icon-ym-flow-launch',
// color: '#0293fc'
// },
{
label: '删除',
value: 'delete',
icon: 'icon-ym icon-ym-app-delete',
color: '#dd524d'
}
]
if(this.dataForm.approveStatus == 0 || this.dataForm.approveStatus == 3 || !this.dataForm.approveStatus){
menuList.unshift({
label: '发起流程',
value: 'launchFlow',
icon: 'icon-ym icon-ym-flow-launch',
color: '#0293fc'
},)
}
if(this.dataForm.approveStatus == 1){
menuList.unshift({
label: '通过',
value: 'through',
icon: 'icon-ym icon-ym-flow-launch',
color: '#67c23a'
},
{
label: '不通过',
value: 'fail',
icon: 'icon-ym icon-ym-flow-launch',
color: '#f56c6c'
})
}
// if(this.dataForm.approveStatus == 1){
// menuList.unshift({
// label: '通过',
// value: 'through',
// icon: 'icon-ym icon-ym-flow-launch',
// color: '#67c23a'
// },
// {
// label: '不通过',
// value: 'fail',
// icon: 'icon-ym icon-ym-flow-launch',
// color: '#f56c6c'
// })
// }
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 'launchFlow':
this.handleLaunchFlow()
break
case 'delete':
this.handleDelete()
break
case 'through':
this.handleThrough()
break
case 'fail':
this.handleFail()
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 === 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
}
},
selectProcess(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
}
}
const res = await getListCreate(params,this.modelId)
if(res.code == 0) {
uni.showToast({
title: '发起成功',
icon: 'success'
})
setTimeout(() => {
uni.$emit('refresh')
uni.navigateBack()
}, 1500)
}
}
} 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
console.log(this.formData,'formData----')
// 构造审批参数(
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 || '', // 审批意见
}
}
//调用审批接口(替换为你的实际审批接口)
const res = await getListCreate(params,this.modelId)
if (res.code !== 0) {
uni.showToast({
title: res.msg || '审批失败',
icon: 'none'
})
return
}
// 关闭弹窗,刷新审批记录
this.showApprovalReasonDialog = false
uni.showToast({
title: '发起成功',
icon: 'success'
})
setTimeout(() => {
// 返回上一页或刷新列表
uni.$emit('refresh')
uni.navigateBack()
}, 1500)
} 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--')
this.formConf.labelWidth = 160;
this.lc_fire_operation_detail = data?.lc_fire_operation_detail || [];
await this.getDeptList();
await this.fillFormData(this.formConf, this.formData);
this.showPage = true;
await this.initData();
this.loading = false;
} else {
2026-01-04 11:09:06 +08:00
uni.showToast({
title: '暂无此页面',
icon: 'none',
complete: () => {
setTimeout(() => {
2026-01-20 18:07:35 +08:00
uni.navigateBack();
2026-01-04 11:09:06 +08:00
}, 1500)
}
})
}
2026-01-20 18:07:35 +08:00
} 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();
})
2026-01-04 11:09:06 +08:00
})
},
2026-01-20 18:07:35 +08:00
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();
2026-01-04 11:09:06 +08:00
if (this.dataForm.id) {
2026-01-20 18:07:35 +08:00
const extra = {
2026-01-04 11:09:06 +08:00
modelId: this.modelId,
id: this.dataForm.id,
type: 1
2026-01-20 18:07:35 +08:00
};
uni.setStorageSync('dynamicModelExtra', extra);
const res = await getModelInfo(this.modelId, this.dataForm.id);
console.log(res.data,'res.data111')
console.log(res.data.jeelowcode_subtable_data,'res.data222')
if(!!res.data.approveStatus&&(res.data.approveStatus !== 0 || res.data.approveStatus == 3)){
// 不是未发起和驳回的都不可编辑
this.disableAllFormFields();
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
this.dataForm = res.data;
if (!res.data) return;
this.formData = {
...res.data,
id: this.dataForm.id,
lc_fire_operation_detail: res.data.jeelowcode_subtable_data?.lc_fire_operation_detail || res.data?.lc_fire_operation_detail || [],
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,
};
this.$nextTick(()=>{
if (this.$refs.dynamicForm && this.$refs.dynamicForm.setFormData) {
this.$refs.dynamicForm.setFormData('applyDepId', this.formData.applyDepId);
}
2026-01-04 11:09:06 +08:00
})
2026-01-20 18:07:35 +08:00
// 获取审批记录
await this.getApprovalData();
this.initMoreMenuList()
2026-01-04 11:09:06 +08:00
} else {
2026-01-20 18:07:35 +08:00
const {user,deptInfoList} = this.userInfo
this.isAdd = true;
this.formData = {
...this.formData,
lc_fire_operation_detail: this.lc_fire_operation_detail,
applyDepId: deptInfoList ? String(deptInfoList[0].deptId) : '' ,
applyDepName: deptInfoList && deptInfoList[0].deptName,
applyUser: user.nickname
};
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
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;
}
2026-01-04 11:09:06 +08:00
},
2026-01-20 18:07:35 +08:00
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);
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
// 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'] = '';
2026-01-04 11:09:06 +08:00
}
}
2026-01-20 18:07:35 +08:00
}
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
}
}
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
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
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
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)
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
} else {
let noShow = config.noShow ? config.noShow : false,
isVisibility = false
if (!config.visibility || (Array.isArray(config.visibility) && config.visibility.includes('app'))) {
isVisibility = true
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
this.$set(config, 'isVisibility', isVisibility)
this.$set(config, 'noShow', noShow)
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
if (config && config.children && Array.isArray(config.children)) {
loop(config.children, item);
2026-01-04 11:09:06 +08:00
}
}
2026-01-20 18:07:35 +08:00
};
loop(form.fields);
if (!form.formData) {
form.formData = {};
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
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();
});
});
2026-01-04 11:09:06 +08:00
},
sumbitForm(data, callback) {
2026-01-20 18:07:35 +08:00
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
2026-01-04 11:09:06 +08:00
const formData = {
...this.formData,
2026-01-20 18:07:35 +08:00
...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
}
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
const formMethod = createModel;
formMethod(this.modelId, params).then(res => {
2026-01-04 11:09:06 +08:00
uni.showToast({
2026-01-20 18:07:35 +08:00
title:'保存成功',
2026-01-04 11:09:06 +08:00
complete: () => {
setTimeout(() => {
if (this.clickType == 'save_add') {
2026-01-20 18:07:35 +08:00
this.key = +new Date();
2026-01-04 11:09:06 +08:00
this.$nextTick(() => {
2026-01-20 18:07:35 +08:00
this.$refs.dynamicForm && this.$refs.dynamicForm.resetForm();
});
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
this.btnLoading = false;
this.initData();
if (this.clickType != 'save_proceed' && this.clickType != 'save_add') {
uni.$emit('refresh');
uni.navigateBack();
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
}, 1500);
2026-01-04 11:09:06 +08:00
}
2026-01-20 18:07:35 +08:00
});
2026-01-04 11:09:06 +08:00
}).catch(() => {
2026-01-20 18:07:35 +08:00
this.btnLoading = false;
});
if (callback && typeof callback === "function") callback();
2026-01-04 11:09:06 +08:00
},
commonSubmit(type) {
2026-01-20 18:07:35 +08:00
this.clickType = type;
this.submit(type);
2026-01-04 11:09:06 +08:00
},
submit(type) {
2026-01-20 18:07:35 +08:00
this.clickType = type;
if (this.isPreview == '1') return this.$u.toast('功能预览不支持数据保存');
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} 分钟`;
2026-01-04 11:09:06 +08:00
}
}
}
</script>
<style lang="scss">
page {
background-color: #f0f2f6;
}
2026-01-20 18:07:35 +08:00
.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: 99;
gap: 20rpx;
.buttom-btn {
min-width: 180rpx;
}
}
.more-btn {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 80rpx;
height: 80rpx;
.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: 20rpx 30rpx;
}
.approval-item {
display: flex;
position: relative;
margin-bottom: 30rpx;
}
.approval-line-container {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 20rpx;
}
.approval-line {
width: 2rpx;
background-color: #e5e5e5;
flex: 1;
margin-top: 8rpx;
}
.status-icon {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&.status-processing {
background-color: #E6F7FF;
}
&.status-pass {
background-color: #F0F9EB;
}
&.status-reject {
background-color: #FFF2F0;
}
}
.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;
margin-bottom: 16rpx;
.task-name {
font-size: 28rpx;
color: #333;
}
.status-tag {
font-size: 24rpx;
padding: 4rpx 12rpx;
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;
}
}
}
.info-row {
font-size: 24rpx;
color: #666;
line-height: 1.5;
margin-bottom: 12rpx;
.info-label {
color: #999;
}
.info-value {
color: #666;
}
.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;
}
2026-01-04 11:09:06 +08:00
</style>