提交 c2023d3b authored 作者: liuluyu's avatar liuluyu

更新流程记录表单

上级 5cf77944
......@@ -58,7 +58,7 @@
<!-- <template #footer>
<div class="drawer-footer">
<a-button @click="handleClose">取消123</a-button>
<a-button @click="handleClose">取消</a-button>
<a-button type="primary" :loading="submitLoading" @click="handleSubmit"> 提交 </a-button>
</div>
</template> -->
......
......@@ -89,12 +89,10 @@
<script lang="ts" setup>
import { ref, reactive, nextTick } from 'vue';
import { FileTextOutlined, UserOutlined, CalendarOutlined, ClockCircleOutlined, FormOutlined } from '@ant-design/icons-vue';
import { flowRecord } from '/@/components/Process/api/finished';
import { flowXmlAndNode } from '/@/components/Process/api/definition';
import { findFlowFormVal } from '/@/components/Process/api/todo';
import { flowFormData } from '/@/components/Process/api/process';
import BpmnViewer from '/@/components/Process/viewer/index.vue';
import FlowInnerForm from '/@/views/flowable/task/components/FlowInnerForm.vue';
......@@ -133,7 +131,6 @@
const refInnerForm = ref();
// 遮罩层
const loading = ref(true);
const flowRecordList = ref<FlowRecordItem[]>([]);
const taskForm = reactive<TaskForm>({
returnTaskShow: false,
......
......@@ -90,7 +90,7 @@
</a-tab-pane>
</a-tabs>
</a-card>
<ShowFormModal @register="registerShowFormModal" @submit="handleShowFormSubmit" />
<ShowFormModal @register="registerShowFormModal" />
</div>
</template>
......@@ -229,36 +229,55 @@
};
const showNodeFormData = (data) => {
// 调试:打印传入的数据结构
console.log('showNodeFormData 传入数据:', data);
console.log('showNodeFormData taskDataObj:', taskDataObj.value);
// Build workflowNodes list for the drawer. Adjust component paths as needed.
const nodes = [
{ id: 'stplanman', name: '主表单', formUrl: '/views/project/plan/components/StPlanManForm.vue' },
{ id: 'stplanexcute', name: '执行表单', formUrl: '/views/project/plan/components/StPlanExcuteForm.vue' },
{ id: 'stplanman', name: '主表单', formUrl: '/project/plan/components/StPlanManForm.vue' },
{ id: 'stplanexcute', name: '执行表单', formUrl: '/project/plan/components/StPlanExcuteForm.vue' },
];
// pick which node to show — customize the rule as necessary
const currentIndex = (data?.taskName || '').includes('执行') ? 1 : 0;
// externalFormData: map node id -> form data object
const externalFormData = {
[nodes[currentIndex].id]: data?.data || {},
// 获取当前节点
const currentNode = nodes[currentIndex];
// 获取业务数据ID:优先级为 id -> dataId -> procInsId
let businessDataId = taskDataObj.value?.id || taskDataObj.value?.dataId || taskDataObj.value?.procInsId;
console.log('获取到的业务数据ID:', businessDataId);
// 准备表单数据
const formData = {
id: businessDataId || '',
// 将taskDataObj中的数据合并到formData中
...taskDataObj.value,
};
console.log('准备的表单数据:', formData);
// externalFormData: map node id -> form data object
const externalFormData = {};
// 所有节点都传递相同的formData,包括可编辑节点和只读节点
nodes.forEach((node) => {
externalFormData[node.id] = formData;
});
console.log('传递给ShowFormModal的externalFormData:', externalFormData);
showFormModalApi.openModal(true, {
workflowNodes: nodes,
currentNodeIndex: currentIndex,
externalFormData,
title: data?.taskName || '表单',
showFooter: true,
showFooter: false,
});
};
// Handle submit event emitted from ShowFormModal
const handleShowFormSubmit = (payload) => {
console.log('ShowFormModal submit payload:', payload);
// payload contains: { nodeId, nodeName, formData, procDefId, formComponent }
// Add your handling logic here (e.g., call API, close modal, refresh list)
};
defineExpose({
iniData,
});
......
......@@ -8,10 +8,10 @@
<!-- 表单主体 -->
<div class="form-body">
<a-form ref="formRef" :model="formData" layout="vertical" class="styled-form">
<a-form ref="formRef" :model="formFields" layout="vertical" class="styled-form">
<!-- 执行状态 -->
<a-form-item label="执行状态" name="executeStatus">
<a-select v-model:value="formData.executeStatus" placeholder="请选择执行状态">
<a-select v-model:value="formFields.executeStatus" placeholder="请选择执行状态">
<a-select-option value="0">
<span class="status-option">
<span class="status-indicator pending"></span>
......@@ -42,22 +42,22 @@
<!-- 时间选择区域 -->
<div class="form-row">
<a-form-item label="实际开始时间" name="actualStartTime" class="form-item-half">
<a-date-picker v-model:value="formData.actualStartTime" placeholder="选择时间" format="YYYY-MM-DD"></a-date-picker>
<a-date-picker v-model:value="formFields.actualStartTime" placeholder="选择时间" format="YYYY-MM-DD"></a-date-picker>
</a-form-item>
<a-form-item label="实际结束时间" name="actualEndTime" class="form-item-half" :rules="endDateRules">
<a-date-picker v-model:value="formData.actualEndTime" placeholder="选择时间" format="YYYY-MM-DD"></a-date-picker>
<a-date-picker v-model:value="formFields.actualEndTime" placeholder="选择时间" format="YYYY-MM-DD"></a-date-picker>
</a-form-item>
</div>
<!-- 执行记录 -->
<a-form-item label="执行记录" name="executeEcord">
<a-textarea v-model:value="formData.executeEcord" :rows="3" placeholder="请输入执行记录和详细说明..." show-count :maxlength="500" />
<a-textarea v-model:value="formFields.executeEcord" :rows="3" placeholder="请输入执行记录和详细说明..." show-count :maxlength="500" />
</a-form-item>
<!-- 附件上传 -->
<a-form-item label="相关附件" name="attachments">
<JUpload v-model:value="formData.attachments" desText="支持扩展名: .rar .zip .doc .docx .pdf .jpg..." />
<JUpload v-model:value="formFields.attachments" desText="支持扩展名: .rar .zip .doc .docx .pdf .jpg..." />
</a-form-item>
</a-form>
</div>
......@@ -90,7 +90,8 @@
const planFormStore = usePlanFormStore();
const isInitialized = ref(false);
const formData = reactive({
// 本地表单数据 - 使用 formFields 作为内部状态
const formFields = reactive({
id: '',
executeStatus: '',
actualStartTime: null as any,
......@@ -100,19 +101,76 @@
});
const props = defineProps({
initialData: {
// 改名:不使用 formData 作为 prop 名
formData: {
type: Object,
default: () => ({}),
},
disabled: {
type: Boolean,
default: false,
},
readonly: {
type: Boolean,
default: false,
},
});
// 安全日期转换函数 - 从字符串或Date或dayjs对象转换为dayjs对象
const safeDateParse = (dateValue) => {
if (!dateValue) return null;
// 如果已经是dayjs对象,直接返回
if (dateValue && typeof dateValue.format === 'function' && dateValue.isValid && dateValue.isValid()) {
return dateValue;
}
// 如果是Date对象,转换为dayjs
if (dateValue instanceof Date) {
return dayjs(dateValue);
}
// 如果是字符串,尝试解析
if (typeof dateValue === 'string' && dateValue.trim()) {
try {
const parsed = dayjs(dateValue);
if (parsed.isValid()) {
return parsed;
}
} catch (e) {
console.error('日期解析失败:', dateValue, e);
return null;
}
}
return null;
};
// 安全解析JSON
const safeJsonParse = (str) => {
if (!str) return [];
if (Array.isArray(str)) return str;
try {
return JSON.parse(str);
} catch (e) {
return [];
}
};
// sync incoming prop into internal reactive formData
watch(
() => props.initialData,
() => props.formData,
(v) => {
if (v && Object.keys(v).length > 0) {
Object.assign(formData, v);
const preparationData = {
id: v.id || '',
executeStatus: v.executeStatus || '',
actualStartTime: safeDateParse(v.actualStartTime),
actualEndTime: safeDateParse(v.actualEndTime),
executeEcord: v.executeEcord || '',
attachments: safeJsonParse(v.attachments),
};
Object.assign(formFields, preparationData);
console.log('从props同步表单数据:', preparationData);
}
},
{ immediate: true, deep: true }
......@@ -121,25 +179,20 @@
// 监听formData变化,用于调试
watch(
() => formData,
() => formFields,
(newVal) => {},
{ deep: true, immediate: true }
);
// 安全解析JSON
const safeJsonParse = (str) => {
if (!str) return [];
if (Array.isArray(str)) return str;
try {
return JSON.parse(str);
} catch (e) {
return [];
}
};
// 初始化表单数据
const initFormData = async (id?: string) => {
try {
// 优先检查props.formData中是否已有完整数据和id
if (props.formData && props.formData.id && Object.keys(props.formData).length > 1) {
console.log('表单数据已从props传入,使用props数据,跳过queryById');
return; // 数据已通过watch同步到formData
}
// 检查是否已正在加载,避免重复请求
if (loading.value && planId.value === (id || route.query.id)) {
return;
......@@ -151,8 +204,8 @@
// 从参数或URL获取id
const targetId = id || (route.query.id as string);
if (!targetId) {
message.warning('未获取到计划ID');
console.warn('[v0] 未获取到计划ID, id参数:', id, '路由ID:', route.query.id);
loading.value = false;
return;
}
......@@ -172,14 +225,14 @@
const newFormData = {
id: data.id || '',
executeStatus: data.executeStatus || '',
actualStartTime: data.actualStartTime ? dayjs(data.actualStartTime) : null,
actualEndTime: data.actualEndTime ? dayjs(data.actualEndTime) : null,
actualStartTime: safeDateParse(data.actualStartTime),
actualEndTime: safeDateParse(data.actualEndTime),
executeEcord: data.executeEcord || '',
// 安全解析附件字段(可能是字符串或数组)
attachments: safeJsonParse(data.attachments),
};
// 直接替换整个 ref 值,确保触发响应式更新
Object.assign(formData, newFormData);
Object.assign(formFields, newFormData);
// 强制等待 DOM 更新
await nextTick();
planFormStore.updateFormDataCache(newFormData);
......@@ -187,7 +240,10 @@
}
} catch (error: any) {
console.error('初始化表单数据失败:', error);
message.error('初始化表单数据失败');
// 只有在从URL获取id时才提示错误
if (id || route.query.id) {
message.error('初始化表单数据失败');
}
planFormStore.formLoadingState.isError = true;
planFormStore.formLoadingState.errorMessage = error?.message || '加载失败';
} finally {
......@@ -205,12 +261,12 @@
if (!value) {
return Promise.resolve();
}
if (!formData.actualStartTime) {
if (!formFields.actualStartTime) {
return Promise.resolve();
}
// 比较日期:结束日期不能早于开始日期
const endTime = dayjs(value);
const startTime = dayjs(formData.actualStartTime);
const startTime = dayjs(formFields.actualStartTime);
if (endTime.isBefore(startTime, 'day')) {
return Promise.reject(new Error('结束日期不能早于开始日期'));
}
......@@ -229,7 +285,7 @@
return;
}
if (!formData.id) {
if (!formFields.id) {
message.error('缺少计划ID,无法保存');
return;
}
......@@ -260,13 +316,13 @@
// 准备提交数据,将数组类型转换为字符串,日期格式化
const submitData = {
id: formData.id,
executeStatus: formData.executeStatus || '',
actualStartTime: formatDate(formData.actualStartTime),
actualEndTime: formatDate(formData.actualEndTime),
executeEcord: formData.executeEcord || '',
id: formFields.id,
executeStatus: formFields.executeStatus || '',
actualStartTime: formatDate(formFields.actualStartTime),
actualEndTime: formatDate(formFields.actualEndTime),
executeEcord: formFields.executeEcord || '',
// 将附件数组转换为JSON字符串(如果是数组)或保留原值(如果已是字符串)
attachments: Array.isArray(formData.attachments) ? JSON.stringify(formData.attachments) : formData.attachments || '',
attachments: Array.isArray(formFields.attachments) ? JSON.stringify(formFields.attachments) : formFields.attachments || '',
};
// 调用 saveOrUpdate 接口,isUpdate=true表示执行更新操作
......@@ -275,7 +331,7 @@
if (response) {
// message.success('保存成功');
// 更新缓存
planFormStore.updateFormDataCache(formData);
planFormStore.updateFormDataCache(formFields);
}
} catch (error: any) {
console.error('保存失败:', error);
......@@ -297,6 +353,14 @@
// 等待 iframe DOM 完全渲染
await nextTick();
// 优先检查props.formData是否已有完整数据
if (props.formData && props.formData.id && Object.keys(props.formData).length > 1) {
console.log('props.formData已有完整数据,直接使用,不再查询接口');
initFormData();
planFormStore.registerSubmitCallback(submitForm);
return;
}
// 从 URL 直接解析 id 参数(iframe 场景)
const urlParams = new URLSearchParams(window.location.search);
const idFromUrl = urlParams.get('id');
......@@ -352,7 +416,7 @@
resetForm,
initFormData,
isInitialized,
getFormData: () => toRaw(formData),
getFormData: () => toRaw(formFields),
});
</script>
......
......@@ -41,7 +41,7 @@
<script lang="ts" setup>
import { BasicForm, useForm } from '/@/components/Form/index';
import { computed, ref, onMounted, watchEffect, toRaw, watch } from 'vue';
import { computed, ref, onMounted, watchEffect, toRaw, watch, nextTick } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { getBpmFormSchema } from '../StPlanMan.data';
import { saveOrUpdate } from '../StPlanMan.api';
......@@ -76,13 +76,26 @@
// watch incoming prop and populate the form when provided
watch(
() => props.formData,
(v) => {
async (v) => {
if (v && Object.keys(v).length > 0) {
formData.value = { ...v };
// 延迟调用setFieldsValue,确保表单已经初始化
// 当选项卡切换时,表单可能还没有完全准备好
await nextTick();
try {
setFieldsValue(v);
await setFieldsValue(v);
console.log('setFieldsValue 成功:', v);
} catch (e) {
console.warn('setFieldsValue failed:', e);
console.warn('setFieldsValue failed (first attempt):', e);
// 如果第一次失败,延迟后重试
await new Promise((resolve) => setTimeout(resolve, 100));
try {
await setFieldsValue(v);
console.log('setFieldsValue 成功 (retry):', v);
} catch (e2) {
console.warn('setFieldsValue failed (retry):', e2);
}
}
}
},
......@@ -145,12 +158,25 @@
// 初始化表单数据
const initFormData = async () => {
try {
// 如果props.formData已经有数据,说明是从流程表单打开,不需要再调用接口
if (props.formData && Object.keys(props.formData).length > 0 && props.formData.id) {
console.log('表单数据已从props传入,跳过queryById接口调用');
return;
}
loading.value = true;
const timestamp = new Date().getTime();
let tid = toRaw(route.query).id;
// 如果URL中也没有id,则不调用接口
if (!tid) {
console.warn('未找到id参数,既非URL query参数也非props传入');
loading.value = false;
return;
}
const queryByIdUrl = '/plan.main/stPlanMan/queryById';
const data = await defHttp.get({
url: queryByIdUrl,
......@@ -167,7 +193,10 @@
await setProps({ disabled: formDisabled.value });
} catch (error) {
console.error('初始化表单数据失败:', error);
createMessage.error('初始化表单数据失败');
// 只有在非预期情况下才提示错误
if (!props.formData?.id) {
createMessage.error('初始化表单数据失败');
}
} finally {
loading.value = false;
}
......@@ -192,7 +221,12 @@
};
onMounted(() => {
initFormData();
// 如果formData已经通过props传入,则不需要再调用initFormData
if (props.formData && Object.keys(props.formData).length > 0 && props.formData.id) {
console.log('form data已通过props传入,跳过接口调用');
} else {
initFormData();
}
});
// expose getFormData so parent modal can read current values
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论