2026-01-10 10:09:15 +08:00

660 lines
19 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>
<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 中的 idxxts 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 中获取业务 IDxxzbId
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>