660 lines
19 KiB
Vue
660 lines
19 KiB
Vue
<template>
|
||
<BasicLayout>
|
||
<!-- 统一的滚动容器,包含编辑组件和流程组件 -->
|
||
<scroll-view scroll-y class="unified-scroll" :class="{ 'full-height': hideBottomBtn }">
|
||
<!-- 引入编辑组件(不包含滚动) -->
|
||
<JfEdit v-if="isLoginReady" ref="jfEditRef" :jf-id="jfId" @saved="handleDataSaved" @loaded="handleJfEditLoaded" @popup-change="handlePickerPopupChange" />
|
||
|
||
<!-- 审批流程展示 -->
|
||
<view class="flow-section" v-if="jfId">
|
||
<LcglSp :yw-id="jfId" yw-type="JF" :key="jfId" />
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<template #bottom>
|
||
<view :class="{ 'hide-bottom': hideBottomBtn }">
|
||
<YwConfirm
|
||
v-if="showButton"
|
||
:spApi="wrappedJfSpApi"
|
||
:stopApi="wrappedJfStopApi"
|
||
:transferApi="wrappedJfTransferApi"
|
||
:params="spParams"
|
||
:autoToMessage="false"
|
||
:showXt="false"
|
||
:showReject="!isYiban"
|
||
:showTransfer="false"
|
||
:showApprove="!isYiban"
|
||
:showReturn="false"
|
||
:showStop="true"
|
||
:showXtDk="false"
|
||
approveText="通过"
|
||
@submit="handleSubmit"
|
||
@reject="handleReject"
|
||
@transfer="handleTransfer"
|
||
@stop="handleStop"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<!-- 遮罩层 -->
|
||
<view v-if="loading" class="loading-mask">
|
||
<view class="loading-content">
|
||
<view class="loading-spinner"></view>
|
||
<text class="loading-text">{{ loadingText }}</text>
|
||
</view>
|
||
</view>
|
||
</BasicLayout>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { onLoad, onBackPress } from "@dcloudio/uni-app";
|
||
import { ref, computed } from "vue";
|
||
import dayjs from "dayjs";
|
||
import BasicLayout from "@/components/BasicLayout/Layout.vue";
|
||
import LcglSp from "@/components/LcglSp/index.vue";
|
||
import YwConfirm from "@/pages/components/YwConfirm/index.vue";
|
||
import JfEdit from "./JfEdit.vue";
|
||
import { jfFlowByIdApi, jfSpApi, jfTransferApi, jfStopApi, jfSaveApi } from "@/api/base/jfApi";
|
||
import { xxtsFindByIdApi } from "@/api/base/xxtsApi";
|
||
import { useUserStore } from "@/store/modules/user";
|
||
|
||
const { loginByOpenId, getJs } = useUserStore();
|
||
|
||
const jfId = ref<string>("");
|
||
const jfInfo = ref<any>({});
|
||
const showButton = ref<boolean>(false);
|
||
const isLoginReady = ref<boolean>(false); // 登录是否准备就绪
|
||
// 编辑组件的引用,用于获取表单数据
|
||
const jfEditRef = ref<any>(null);
|
||
// 控制底部按钮区域显示/隐藏
|
||
const hideBottomBtn = ref(false);
|
||
// 遮罩层状态
|
||
const loading = ref<boolean>(false);
|
||
const loadingText = ref<string>("处理中...");
|
||
// 数据加载状态
|
||
const jfInfoLoaded = ref<boolean>(false); // 流程数据是否已加载
|
||
const jfEditLoaded = ref<boolean>(false); // 编辑组件数据是否已加载
|
||
// 是否已办(dbZt === "B" 表示已办)
|
||
const dbZtFromUrl = ref<string>("");
|
||
// 直接用一个 ref 保存 xxtsId
|
||
const xxtsId = ref<string>("");
|
||
const isYiban = computed(() => {
|
||
return dbZtFromUrl.value === "B";
|
||
});
|
||
|
||
// 审批参数(只包含审批相关字段,业务字段通过 save 接口保存)
|
||
const spParams = computed(() => {
|
||
return {
|
||
xxtsId: xxtsId.value,
|
||
ywId: jfId.value,
|
||
// 业务字段已通过 save 接口保存,这里不再传递
|
||
};
|
||
});
|
||
|
||
// 读取流程详情(用于审批时获取最新数据)
|
||
const getJfInfo = async () => {
|
||
try {
|
||
const res: any = await jfFlowByIdApi({ id: jfId.value });
|
||
if (res && res.resultCode === 1 && res.result) {
|
||
jfInfo.value = res.result.jfInfo || {};
|
||
const spResult = jfInfo.value?.spResult;
|
||
const spJd = jfInfo.value?.spJd;
|
||
|
||
// 已完结或驳回:隐藏审批按钮
|
||
if (spResult === "C" || (spJd === "Z" && spResult === "B")) {
|
||
showButton.value = false;
|
||
} else if (spResult && spResult !== "A" && dbZtFromUrl.value === "A") {
|
||
// 历史逻辑:当流程已完成且待办状态关闭时,不显示审批按钮
|
||
showButton.value = false;
|
||
} else {
|
||
showButton.value = true;
|
||
}
|
||
} else {
|
||
showButton.value = false;
|
||
}
|
||
jfInfoLoaded.value = true;
|
||
checkAllDataLoaded();
|
||
} catch (error) {
|
||
console.error("获取积分流程失败:", error);
|
||
showButton.value = false;
|
||
jfInfoLoaded.value = true;
|
||
checkAllDataLoaded();
|
||
}
|
||
};
|
||
|
||
// 获取编辑组件的表单数据
|
||
const getJfEditFormData = () => {
|
||
if (!jfEditRef.value) {
|
||
console.warn('JfEdit 组件引用不存在');
|
||
return null;
|
||
}
|
||
// 通过 ref 访问子组件的 formData
|
||
const formData = jfEditRef.value.formData;
|
||
if (!formData) {
|
||
console.warn('无法从 JfEdit 组件获取 formData');
|
||
return null;
|
||
}
|
||
console.log('获取到表单数据:', formData);
|
||
return formData;
|
||
};
|
||
|
||
// 保存编辑组件数据(审批前调用)
|
||
const saveJfDataBeforeApprove = async () => {
|
||
console.log('开始保存编辑组件数据...');
|
||
console.log('jfEditRef.value:', jfEditRef.value);
|
||
|
||
// 显示遮罩层
|
||
loading.value = true;
|
||
loadingText.value = '正在保存数据...';
|
||
|
||
try {
|
||
// 等待一下,确保组件已挂载
|
||
await new Promise(resolve => setTimeout(resolve, 100));
|
||
|
||
const formData = getJfEditFormData();
|
||
if (!formData) {
|
||
console.error('无法获取表单数据');
|
||
loading.value = false;
|
||
return { success: false, message: '无法获取表单数据,请确保编辑组件已加载' };
|
||
}
|
||
|
||
// 验证必要字段
|
||
if (!formData.jfTypeId) {
|
||
console.warn('表单数据缺少 jfTypeId');
|
||
loading.value = false;
|
||
return { success: false, message: '请先选择业绩类别' };
|
||
}
|
||
const jfTypeId = typeof formData.jfTypeId === 'object' && formData.jfTypeId !== null
|
||
? formData.jfTypeId.value
|
||
: formData.jfTypeId;
|
||
|
||
let hjtime = formData.hjtime;
|
||
if (hjtime && hjtime.length === 7 && hjtime.match(/^\d{4}-\d{2}$/)) {
|
||
hjtime = hjtime + '-01';
|
||
}
|
||
|
||
let fileUrl = '';
|
||
let fileName = '';
|
||
let fileFormat = '';
|
||
if (formData.attachments && formData.attachments.length > 0) {
|
||
const urls: string[] = [];
|
||
const names: string[] = [];
|
||
const exts: string[] = [];
|
||
formData.attachments.forEach((att: any) => {
|
||
if (att.url) urls.push(att.url);
|
||
if (att.name) names.push(att.name);
|
||
const ext = (att.name || '').split('.').pop()?.toLowerCase() || '';
|
||
if (ext) exts.push(ext);
|
||
});
|
||
fileUrl = urls.join(',');
|
||
fileName = names.join(',');
|
||
fileFormat = exts.join(',');
|
||
}
|
||
|
||
// 获取当前用户信息,设置考核人
|
||
const js = getJs;
|
||
let khJsId = '';
|
||
let khjsxm = '';
|
||
if (js) {
|
||
khJsId = js.id || '';
|
||
khjsxm = js.jsxm || js.xm || '';
|
||
}
|
||
// 设置考核时间为今天
|
||
const today = new Date();
|
||
const khTime = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
|
||
|
||
const submitData: any = {
|
||
...formData,
|
||
id: jfId.value, // 确保有ID
|
||
jfTypeId: jfTypeId,
|
||
hjtime: hjtime,
|
||
fileUrl: fileUrl,
|
||
fileName: fileName,
|
||
fileFormat: fileFormat,
|
||
khJsId: khJsId,
|
||
khjsxm: khjsxm,
|
||
khTime: khTime,
|
||
};
|
||
|
||
delete submitData.attachments;
|
||
|
||
// 处理学生获奖名单:转换为 studentList 格式(只保存有效状态的学生)
|
||
// 注意:需要判断是否需要保存学生名单(根据 showStudentAward 或 formData 中的标识)
|
||
if (formData.hjmdList && Array.isArray(formData.hjmdList) && formData.hjmdList.length > 0) {
|
||
const validStudents = formData.hjmdList.filter((s: any) => s.status !== 'D');
|
||
submitData.studentList = validStudents.map((student: any) => {
|
||
// 如果没有 bc,根据 njbc、njmc、bjmc 组合生成
|
||
let bc = student.bc;
|
||
if (!bc && (student.njmc || student.njbc || student.bjmc)) {
|
||
const parts: string[] = [];
|
||
if (student.njmc) parts.push(student.njmc);
|
||
if (student.njbc) parts.push(`(${student.njbc})`);
|
||
if (student.bjmc) parts.push(student.bjmc);
|
||
bc = parts.join('');
|
||
}
|
||
|
||
return {
|
||
id: student.id, // 编辑时保留ID
|
||
xsId: student.xsId,
|
||
xsxm: student.xsxm,
|
||
njId: student.njId,
|
||
njmcId: student.njmcId,
|
||
bjId: student.bjId,
|
||
njbc: student.njbc,
|
||
njmc: student.njmc,
|
||
bjmc: student.bjmc,
|
||
bc: bc || '',
|
||
status: student.status || 'A',
|
||
remark: student.remark || ''
|
||
};
|
||
});
|
||
} else {
|
||
// 如果没有学生,传递空数组(用于删除所有学生)
|
||
submitData.studentList = [];
|
||
}
|
||
|
||
console.log('准备调用保存接口,数据:', submitData);
|
||
const res: any = await jfSaveApi(submitData);
|
||
console.log('保存接口返回:', res);
|
||
|
||
if (res && res.resultCode === 1) {
|
||
console.log('数据保存成功(包含学生获奖名单)');
|
||
|
||
// 保存成功后,更新 jfInfo 用于审批参数
|
||
await getJfInfo();
|
||
loading.value = false;
|
||
return { success: true };
|
||
} else {
|
||
console.error('数据保存失败:', res?.resultMsg);
|
||
loading.value = false;
|
||
return { success: false, message: res?.resultMsg || '保存失败' };
|
||
}
|
||
} catch (error) {
|
||
console.error('保存数据失败:', error);
|
||
loading.value = false;
|
||
return { success: false, message: '保存数据失败' };
|
||
}
|
||
};
|
||
|
||
// 包装审批接口,先保存数据再审批
|
||
const wrappedJfSpApi = async (params: any) => {
|
||
console.log('审批接口被调用,参数:', params);
|
||
|
||
try {
|
||
// 先保存编辑组件的数据
|
||
console.log('步骤1: 保存编辑组件数据');
|
||
const saveResult = await saveJfDataBeforeApprove();
|
||
|
||
if (!saveResult.success) {
|
||
console.error('保存数据失败:', saveResult.message);
|
||
loading.value = false;
|
||
uni.showToast({
|
||
title: saveResult.message || '保存数据失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
throw new Error(saveResult.message || '保存数据失败');
|
||
}
|
||
|
||
console.log('步骤2: 数据保存成功,开始审批');
|
||
loadingText.value = '正在审批...';
|
||
|
||
// 保存成功后,再调用审批接口(只传递审批相关参数,不传递业务字段)
|
||
const spParams = {
|
||
xxtsId: params.xxtsId,
|
||
ywId: params.ywId,
|
||
spStatus: params.spStatus,
|
||
spRemark: params.spRemark
|
||
};
|
||
|
||
console.log('步骤3: 调用审批接口,参数:', spParams);
|
||
const result = await jfSpApi(spParams);
|
||
console.log('审批接口返回:', result);
|
||
loading.value = false;
|
||
return result;
|
||
} catch (error: any) {
|
||
console.error('审批流程出错:', error);
|
||
loading.value = false;
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 包装转办接口,先保存数据再转办
|
||
const wrappedJfTransferApi = async (params: any) => {
|
||
console.log('转办接口被调用,参数:', params);
|
||
|
||
try {
|
||
// 先保存编辑组件的数据
|
||
console.log('步骤1: 保存编辑组件数据');
|
||
const saveResult = await saveJfDataBeforeApprove();
|
||
|
||
if (!saveResult.success) {
|
||
console.error('保存数据失败:', saveResult.message);
|
||
loading.value = false;
|
||
uni.showToast({
|
||
title: saveResult.message || '保存数据失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
throw new Error(saveResult.message || '保存数据失败');
|
||
}
|
||
|
||
console.log('步骤2: 数据保存成功,开始转办');
|
||
loadingText.value = '正在转办...';
|
||
|
||
// 保存成功后,再调用转办接口
|
||
const transferParams = {
|
||
xxtsId: params.xxtsId,
|
||
ywId: params.ywId,
|
||
zbrIds: params.zbrIds,
|
||
csrIds: params.csrIds,
|
||
spRemark: params.spRemark,
|
||
};
|
||
|
||
console.log('步骤3: 调用转办接口,参数:', transferParams);
|
||
const result = await jfTransferApi(transferParams);
|
||
console.log('转办接口返回:', result);
|
||
loading.value = false;
|
||
return result;
|
||
} catch (error: any) {
|
||
console.error('转办流程出错:', error);
|
||
loading.value = false;
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 包装终止接口,先保存数据再终止
|
||
const wrappedJfStopApi = async (params: any) => {
|
||
console.log('终止接口被调用,参数:', params);
|
||
|
||
try {
|
||
// 先保存编辑组件的数据
|
||
console.log('步骤1: 保存编辑组件数据');
|
||
const saveResult = await saveJfDataBeforeApprove();
|
||
|
||
if (!saveResult.success) {
|
||
console.error('保存数据失败:', saveResult.message);
|
||
loading.value = false;
|
||
uni.showToast({
|
||
title: saveResult.message || '保存数据失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
throw new Error(saveResult.message || '保存数据失败');
|
||
}
|
||
|
||
console.log('步骤2: 数据保存成功,开始终止');
|
||
loadingText.value = '正在终止...';
|
||
|
||
// 保存成功后,再调用终止接口
|
||
const stopParams = {
|
||
xxtsId: params.xxtsId,
|
||
ywId: params.ywId,
|
||
spRemark: params.spRemark,
|
||
};
|
||
|
||
console.log('步骤3: 调用终止接口,参数:', stopParams);
|
||
const result = await jfStopApi(stopParams);
|
||
console.log('终止接口返回:', result);
|
||
loading.value = false;
|
||
return result;
|
||
} catch (error: any) {
|
||
console.error('终止流程出错:', error);
|
||
loading.value = false;
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 处理选择器弹窗状态变化
|
||
const handlePickerPopupChange = (show: boolean) => {
|
||
hideBottomBtn.value = show;
|
||
};
|
||
|
||
// 处理编辑组件数据加载完成
|
||
const handleJfEditLoaded = () => {
|
||
jfEditLoaded.value = true;
|
||
checkAllDataLoaded();
|
||
};
|
||
|
||
// 检查所有数据是否已加载完成
|
||
const checkAllDataLoaded = () => {
|
||
if (jfInfoLoaded.value && jfEditLoaded.value) {
|
||
// 延迟一点时间,确保界面渲染完成
|
||
setTimeout(() => {
|
||
loading.value = false;
|
||
}, 300);
|
||
}
|
||
};
|
||
|
||
// 处理数据保存后的回调
|
||
const handleDataSaved = async () => {
|
||
// 数据保存后,重新获取最新数据用于审批
|
||
await getJfInfo();
|
||
};
|
||
|
||
// 处理审批操作完成后的跳转
|
||
const handleSubmit = () => {
|
||
navigateToIndex();
|
||
};
|
||
|
||
const handleReject = () => {
|
||
navigateToIndex();
|
||
};
|
||
|
||
const handleTransfer = () => {
|
||
navigateToIndex();
|
||
};
|
||
|
||
const handleStop = () => {
|
||
navigateToIndex();
|
||
};
|
||
|
||
// 跳转回审批列表页面
|
||
const navigateToIndex = () => {
|
||
setTimeout(() => {
|
||
// 使用 navigateBack 返回上一页,保留查询条件
|
||
// 如果上一页不存在(直接打开详情页),则使用 redirectTo
|
||
const pages = getCurrentPages();
|
||
if (pages.length > 1) {
|
||
uni.navigateBack({
|
||
delta: 1
|
||
});
|
||
} else {
|
||
uni.redirectTo({
|
||
url: '/pages/view/routine/JiFenPingJia/jfsp/index'
|
||
});
|
||
}
|
||
}, 500);
|
||
};
|
||
|
||
// 返回按钮监听(仅 App 端生效,H5 端已在 ImagePreviewWithRotate 组件内部处理)
|
||
onBackPress(() => {
|
||
// 检查 JfEdit 子组件是否正在预览图片
|
||
if (jfEditRef.value && jfEditRef.value.certificateUploadRef) {
|
||
const certUpload = jfEditRef.value.certificateUploadRef;
|
||
const isPreviewing = certUpload.isPreviewing;
|
||
if (isPreviewing) {
|
||
// 如果正在预览图片,关闭预览并阻止返回
|
||
certUpload.closePreview();
|
||
return true; // 阻止默认返回行为
|
||
}
|
||
}
|
||
// 没有预览图片,允许正常返回
|
||
return false;
|
||
});
|
||
|
||
onLoad(async (options: any) => {
|
||
if (!options || !options.id) {
|
||
uni.showToast({ title: "缺少积分ID", icon: "none" });
|
||
setTimeout(() => uni.navigateBack(), 1500);
|
||
return;
|
||
}
|
||
|
||
// 如果有 openId,先进行认证(防止 token 过期)
|
||
if (options.openId) {
|
||
try {
|
||
const loginSuccess = await loginByOpenId(options.openId);
|
||
if (!loginSuccess) {
|
||
// 登录失败,停止后续流程
|
||
return;
|
||
}
|
||
// 等待一下,确保 token 已保存到 store 中
|
||
await new Promise(resolve => setTimeout(resolve, 100));
|
||
} catch (e) {
|
||
console.error("openId认证失败:", e);
|
||
// 认证失败,停止后续流程
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 标记登录准备就绪,允许组件开始加载数据(无论是否有 openId)
|
||
isLoginReady.value = true;
|
||
|
||
// 显示遮罩层
|
||
loading.value = true;
|
||
loadingText.value = '正在加载数据...';
|
||
// 重置加载状态
|
||
jfInfoLoaded.value = false;
|
||
jfEditLoaded.value = false;
|
||
|
||
let actualJfId = options.id; // 默认使用传入的 ID
|
||
|
||
// 如果是从待办进入的(from=db),需要先获取 xxts 信息,然后获取业务 ID
|
||
if (options.from === 'db') {
|
||
try {
|
||
// 从后端根据 url 中的 id(xxts ID)查询待办信息
|
||
const xxtsRes = await xxtsFindByIdApi({ id: options.id });
|
||
|
||
if (xxtsRes && xxtsRes.result) {
|
||
const xxts = xxtsRes.result;
|
||
|
||
// 直接保存 xxtsId
|
||
if (xxts.id) {
|
||
xxtsId.value = xxts.id;
|
||
}
|
||
|
||
// 从 xxts 中获取业务 ID(xxzbId)
|
||
if (xxts.xxzbId) {
|
||
actualJfId = xxts.xxzbId;
|
||
}
|
||
|
||
// 从 xxts 获取 dbZt
|
||
if (xxts.dbZt) {
|
||
dbZtFromUrl.value = xxts.dbZt;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('获取待办信息失败:', error);
|
||
// 如果获取失败,继续使用原始 ID
|
||
}
|
||
} else {
|
||
// 如果不是从待办进入,检查 URL 参数中是否有 xxtsId
|
||
if (options.xxtsId) {
|
||
xxtsId.value = options.xxtsId;
|
||
}
|
||
|
||
// 从 URL 参数获取 dbZt(如果传递了的话)
|
||
if (options.dbZt) {
|
||
dbZtFromUrl.value = options.dbZt;
|
||
}
|
||
}
|
||
|
||
// 使用实际的业务 ID
|
||
jfId.value = actualJfId;
|
||
|
||
getJfInfo();
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.unified-scroll {
|
||
flex: 1;
|
||
height: calc(100vh - 120rpx);
|
||
overflow-y: auto;
|
||
|
||
&.full-height {
|
||
height: 100vh;
|
||
}
|
||
}
|
||
|
||
.flow-section {
|
||
padding: 0;
|
||
background-color: #f5f5f5;
|
||
margin-top: 30rpx;
|
||
|
||
// 确保流程组件与编辑组件有间距
|
||
:deep(.lcgl-info) {
|
||
padding-top: 0;
|
||
margin-top: 0;
|
||
padding-left: 0;
|
||
padding-right: 0;
|
||
}
|
||
|
||
// 流程组件的 section 也要移除左右边距
|
||
:deep(.info-section) {
|
||
border-radius: 0;
|
||
margin-left: 0;
|
||
margin-right: 0;
|
||
}
|
||
}
|
||
|
||
// 统一滚动容器的样式
|
||
.unified-scroll {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
// 遮罩层样式
|
||
.loading-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 9999;
|
||
|
||
.loading-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #ffffff;
|
||
border-radius: 16rpx;
|
||
padding: 60rpx 80rpx;
|
||
min-width: 300rpx;
|
||
|
||
.loading-spinner {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border: 6rpx solid #f3f3f3;
|
||
border-top: 6rpx solid #007aff;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 28rpx;
|
||
color: #333333;
|
||
text-align: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
// 隐藏底部按钮区域
|
||
.hide-bottom {
|
||
display: none !important;
|
||
height: 0 !important;
|
||
padding: 0 !important;
|
||
}
|
||
</style>
|
||
|