Files
threeonecheck_web/pages/closeout/approval.vue

1040 lines
28 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="padding page">
<view class="padding bg-white radius">
<view class="flex margin-bottom">
<view>隐患</view>
<view class="text-red">*</view>
</view>
<view class="picker-input readonly" v-if="hazardLocked">
<text :class="selectedHazard ? '' : 'text-gray'">{{ selectedHazard || '加载中...' }}</text>
</view>
<view class="picker-input" v-else @click="showHazardPicker = true">
<text :class="selectedHazard ? '' : 'text-gray'">{{ selectedHazard || '请选择隐患' }}</text>
</view>
<view class="flex margin-bottom margin-top">
<view>整改时限</view>
</view>
<view class="picker-input readonly">
<text :class="formData.rectifyDeadline ? '' : 'text-gray'">{{ formData.rectifyDeadline || '请先选择隐患' }}</text>
</view>
<view class="margin-bottom margin-top">隐患治理责任单位</view>
<view class="picker-input readonly">
<text :class="selectedDeptName ? '' : 'text-gray'">{{ selectedDeptName || '请先选择隐患' }}</text>
</view>
<view class="margin-bottom margin-top">主要负责人</view>
<view class="picker-input readonly">
<text :class="formData.responsiblePerson ? '' : 'text-gray'">{{ formData.responsiblePerson || '请先选择隐患' }}</text>
</view>
<view class="ai-btn-wrapper margin-top margin-bottom">
<button class="ai-analyze-btn" :loading="aiGenerating" :disabled="aiGenerating" @click="handleAiGenerate">
<text v-if="!aiGenerating" class="cuIcon-magic ai-btn-icon"></text>
{{ aiGenerating ? 'AI生成中...' : 'AI 生成销号方案' }}
</button>
</view>
<view class="margin-bottom margin-top">主要治理内容</view>
<up-textarea v-model="formData.mainTreatmentContent" placeholder="暂无" disabled></up-textarea>
<view class="margin-bottom margin-top">隐患治理完成内容</view>
<up-textarea v-model="formData.treatmentResult" placeholder="暂无" disabled></up-textarea>
<view class="margin-bottom margin-top">隐患治理责任单位自行验收的情况</view>
<up-textarea v-model="formData.selfVerifyContent" placeholder="暂无" disabled></up-textarea>
<view class="flex margin-bottom margin-top">
<view class="text-gray">审批结果</view>
<view class="text-red">*</view>
</view>
<view class="flex" style="gap: 20rpx;">
<button
:class="['result-btn', formData.approvalResult === 1 ? 'active' : '']"
@click="onApprovalResultChange(1)"
>通过</button>
<button
:class="['result-btn', formData.approvalResult === 2 ? 'active' : '']"
@click="onApprovalResultChange(2)"
>不通过</button>
</view>
<template v-if="formData.approvalResult === 1">
<view class="flex margin-bottom margin-top">
<view class="text-gray">是否快速审批</view>
<view class="text-red">*</view>
</view>
<up-radio-group
v-model="quickApproveRadio"
placement="row"
activeColor="#2667e9"
class="approval-radio-group"
>
<up-radio
label="是"
name="yes"
:customStyle="{ marginRight: '48rpx' }"
></up-radio>
<up-radio label="否" name="no"></up-radio>
</up-radio-group>
</template>
<template v-if="formData.approvalResult === 2">
<view class="flex margin-bottom margin-top">
<view class="text-gray">审批意见</view>
<view class="text-red">*</view>
</view>
<up-radio-group
v-model="formData.approvalOpinion"
placement="row"
activeColor="#2667e9"
class="approval-radio-group"
>
<up-radio
v-for="(opt, index) in approvalOptions"
:key="opt.value"
:label="opt.label"
:name="opt.value"
:customStyle="{ marginRight: index < approvalOptions.length - 1 ? '24rpx' : '0' }"
></up-radio>
</up-radio-group>
<view class="flex margin-bottom margin-top">
<view class="text-gray">意见说明</view>
<view class="text-red">*</view>
</view>
<up-textarea v-model="formData.opinionRemark" placeholder="请输入意见说明"></up-textarea>
</template>
<template v-if="formData.approvalResult === 1">
<view class="flex margin-bottom margin-top">
<view class="text-gray">下一步流程</view>
<view class="text-red">*</view>
</view>
<view class="static-field">{{ nextStepDisplay }}</view>
<template v-if="needNextAssignee">
<view class="flex margin-bottom margin-top">
<view class="text-gray">下一步处理人</view>
<view class="text-red">*</view>
</view>
<view class="select-trigger" @click="openAssigneePopup">
<view class="select-content" :class="{ 'text-gray': !selectedAssigneeName }">
{{ selectedAssigneeName || '请选择下一步处理人' }}
</view>
<text class="cuIcon-unfold"></text>
</view>
</template>
</template>
<view class="flex margin-bottom margin-top">
<view class="text-gray">短信提醒</view>
<view class="text-red">*</view>
</view>
<up-radio-group v-model="sendMsgFlagRadio" placement="row" activeColor="#2667e9">
<up-radio
label="是"
name="yes"
:customStyle="{ marginRight: '48rpx' }"
></up-radio>
<up-radio label="否" name="no"></up-radio>
</up-radio-group>
<template v-if="formData.approvalResult === 1 || formData.approvalResult === 2">
<view class="flex justify-between margin-bottom margin-top align-center">
<view class="text-gray flex align-center">
电子签名
<view class="text-red">*</view>
</view>
<button v-if="showCanvas" class="cu-btn sm round line-blue signature-action-btn" @click="clearSignature">清除重写</button>
<button v-else class="cu-btn sm round line-blue signature-action-btn" @click="reSign">重新签名</button>
</view>
<view class="signature-box margin-bottom">
<view v-if="!showCanvas" class="signature-display flex align-center justify-center">
<image
v-if="signatureUrl"
:src="signatureUrl"
class="signature-img"
mode="aspectFit"
></image>
<text v-else class="signature-placeholder">暂无签名请点击重新签名</text>
</view>
<view v-if="showCanvas && !showAssigneePopup" class="signature-pad-wrap">
<wd-signature
ref="signatureRef"
:width="signatureWidth"
:height="160"
backgroundColor="#f8f8f8"
penColor="#000000"
:lineWidth="3"
:enableHistory="false"
@confirm="(res) => onSignatureConfirm(res.tempFilePath)"
@start="onSignatureStart"
@signing="onSignatureSigning"
@end="onSignatureEnd"
@clear="onSignatureClear"
>
<template #footer></template>
</wd-signature>
</view>
</view>
</template>
<view class="flex margin-top-xl" style="gap: 20rpx;">
<button class="round flex-sub" @click="handleCancel">取消</button>
<button class="bg-blue round flex-sub" @click="handleSubmit">提交</button>
</view>
</view>
<up-picker
v-if="!hazardLocked"
:show="showHazardPicker"
:columns="hazardColumns"
@confirm="onHazardConfirm"
@cancel="showHazardPicker = false"
@close="showHazardPicker = false"
></up-picker>
<FlowAssigneePickerPopup
:show="showAssigneePopup"
:task-id="taskId"
v-model:picker-identity-id="pickerAssigneeIdentityId"
v-model:picker-name="pickerAssigneeName"
@cancel="cancelAssigneeSelect"
@confirm="confirmAssigneeSelect"
/>
</view>
</template>
<script setup>
import { ref, reactive, computed, watch, nextTick } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import {
writeoffApprove,
writeoffReject,
getHiddenDangerDetail,
getRectifyDetail,
generateWriteoffContent,
getFlowNextNodes,
getWriteoffForm
} from '@/request/api.js';
import FlowAssigneePickerPopup from '@/components/flow/FlowAssigneePickerPopup.vue';
import { uploadToCloud, toSubmitFileUrl } from '@/utils/upload.js';
import { buildWriteoffHazardFromOptions } from '@/utils/hazardNav.js';
const showHazardPicker = ref(false);
const hazardLocked = ref(false);
const selectedHazard = ref('');
const selectedHazardId = ref('');
const applyId = ref('');
const assignId = ref('');
const taskId = ref('');
const hazardColumns = ref([['暂无数据']]);
const acceptanceHazardList = ref([]);
const selectedDeptName = ref('');
const aiGenerating = ref(false);
const quickApproveRadio = ref('yes');
const quickApprove = computed(() => quickApproveRadio.value === 'yes');
const sendMsgFlagRadio = ref('yes');
const sendMsgFlag = computed(() => sendMsgFlagRadio.value === 'yes');
const nextStepName = ref('');
const nextStepLoading = ref(false);
const FLOW_END_STEP_NAME = '隐患流程结束';
const nextStepDisplay = computed(() => {
if (nextStepLoading.value) return '加载中...';
return nextStepName.value || '暂无下一步流程';
});
/** 通过 + 快速审批 + 下一步为流程结束时,无需选择处理人 */
const needNextAssignee = computed(() => {
if (formData.approvalResult !== 1) return false;
if (quickApprove.value && nextStepName.value === FLOW_END_STEP_NAME) return false;
return true;
});
const selectedAssigneeIdentityId = ref('');
const selectedAssigneeName = ref('');
const pickerAssigneeIdentityId = ref('');
const pickerAssigneeName = ref('');
const showAssigneePopup = ref(false);
const showCanvas = ref(true);
const signatureUrl = ref('');
const signatureServerPath = ref('');
const signatureWidth = ref(340);
const signatureRef = ref(null);
const isSignatureEmpty = ref(true);
const isSubmitting = ref(false);
const signatureLocalPath = ref('');
// 审批结果为「不通过」时的意见选项(不含「驳回」)
const approvalOptions = [
{ label: '重新整改', value: 'rectify' },
{ label: '重新验收', value: 'verify' }
];
const formData = reactive({
rectifyDeadline: '',
responsibleDeptId: '',
responsiblePerson: '',
mainTreatmentContent: '',
treatmentResult: '',
selfVerifyContent: '',
approvalResult: 1,
approvalOpinion: '',
opinionRemark: ''
});
const onApprovalResultChange = (result) => {
formData.approvalResult = result;
if (result === 2) {
quickApproveRadio.value = 'no';
formData.approvalOpinion = '';
formData.opinionRemark = '';
} else {
formData.approvalOpinion = '';
formData.opinionRemark = '';
quickApproveRadio.value = 'yes';
}
resetSignature();
selectedAssigneeIdentityId.value = '';
selectedAssigneeName.value = '';
if (result === 1) {
fetchNextStep();
} else {
nextStepName.value = '';
}
};
const findHazardInList = (hazardId) => {
return acceptanceHazardList.value.find(
(item) => String(item.hazardId) === String(hazardId) || String(item.id) === String(hazardId)
);
};
const fillHazardRelatedFields = (hazard) => {
formData.rectifyDeadline = hazard.deadline || '';
selectedDeptName.value = hazard.deptName || '';
formData.responsiblePerson = hazard.rectifierName || '';
formData.responsibleDeptId = hazard.deptId || '';
};
const applyWriteoffHazardPreview = (hazard) => {
if (!hazard) return;
selectedHazard.value = hazard.title || hazard.hazardTitle || `隐患${hazard.hazardId}`;
selectedHazardId.value = String(hazard.hazardId);
if (hazard.assignId) {
assignId.value = String(hazard.assignId);
}
if (hazard.taskId) {
taskId.value = String(hazard.taskId);
}
fillHazardRelatedFields(hazard);
};
const resolveTaskIdFromHazard = (hazard) => {
if (!hazard) return '';
const candidates = [
hazard.taskId,
hazard.flowTaskId,
hazard.currentTaskId,
hazard.flowTask?.taskId,
hazard.currentTask?.taskId
];
for (const id of candidates) {
if (id != null && id !== '') return String(id);
}
return '';
};
const resolveTaskIdFromAssign = (assign) => {
if (!assign) return '';
const candidates = [
assign.taskId,
assign.flowTaskId,
assign.currentTaskId,
assign.rectify?.taskId,
assign.rectify?.flowTaskId,
assign.rectify?.currentTaskId,
assign.flow?.taskId,
assign.currentTask?.taskId
];
for (const id of candidates) {
if (id != null && id !== '') return String(id);
}
return '';
};
const resolveAssignWithRectify = (assigns, currentAssignId) => {
if (!assigns?.length) return null;
if (currentAssignId) {
const byAssignId = assigns.find(
(item) => String(item.assignId) === String(currentAssignId) && item.rectify
);
if (byAssignId) return byAssignId;
}
return assigns.find((item) => item.rectify) || assigns[0] || null;
};
const resolveTaskIdFromDetail = (data) => {
if (!data) return '';
if (data.taskId) return String(data.taskId);
if (data.flowTaskId) return String(data.flowTaskId);
if (data.currentTaskId) return String(data.currentTaskId);
const assigns = data.assigns || [];
const matchedAssign = resolveAssignWithRectify(assigns, assignId.value);
const fromMatched = resolveTaskIdFromAssign(matchedAssign);
if (fromMatched) return fromMatched;
for (const assign of assigns) {
const id = resolveTaskIdFromAssign(assign);
if (id) return id;
}
return '';
};
const resolveNextTaskName = (data) => {
if (!data) return '';
const branches = data.branches || [];
const matchedBranch = branches.find((item) => item.matched) || branches[0];
return matchedBranch?.nextNode?.taskName || '';
};
const buildPreviewVariables = () => {
if (formData.approvalResult === 2) {
return { pass: false };
}
return {
pass: true,
quickApprove: quickApproveRadio.value === 'yes',
type: 'agree'
};
};
const openAssigneePopup = () => {
if (!taskId.value) {
uni.showToast({ title: '缺少任务ID', icon: 'none' });
return;
}
pickerAssigneeIdentityId.value = selectedAssigneeIdentityId.value;
pickerAssigneeName.value = selectedAssigneeName.value;
showAssigneePopup.value = true;
};
const confirmAssigneeSelect = ({ identityId, name }) => {
selectedAssigneeIdentityId.value = identityId;
selectedAssigneeName.value = name;
showAssigneePopup.value = false;
};
const cancelAssigneeSelect = () => {
showAssigneePopup.value = false;
};
const fetchNextStep = async () => {
nextStepLoading.value = true;
try {
const currentTaskId = taskId.value;
if (!currentTaskId) {
nextStepName.value = '';
return;
}
const res = await getFlowNextNodes({
taskId: currentTaskId,
previewVariables: buildPreviewVariables(),
includeSubProcess: true
});
if (res.code === 0) {
const payload = res.data && typeof res.data === 'object' ? res.data : res;
nextStepName.value = resolveNextTaskName(payload);
if (!needNextAssignee.value) {
selectedAssigneeIdentityId.value = '';
selectedAssigneeName.value = '';
}
} else {
nextStepName.value = '';
}
} catch (error) {
console.error('获取下一步流程失败:', error);
nextStepName.value = '';
} finally {
nextStepLoading.value = false;
}
};
const resolveTaskIdForHazard = async (hazard) => {
const presetTaskId = taskId.value;
const fromHazard = resolveTaskIdFromHazard(hazard);
if (fromHazard) {
taskId.value = fromHazard;
}
if (!hazard?.hazardId) {
return;
}
if (!assignId.value && hazard.assignId) {
assignId.value = String(hazard.assignId);
}
if (taskId.value) {
return;
}
try {
const params = { hazardId: hazard.hazardId };
if (assignId.value) {
params.assignId = assignId.value;
}
const res = await getHiddenDangerDetail(params);
if (res.code === 0 && res.data) {
taskId.value = resolveTaskIdFromDetail(res.data) || presetTaskId;
}
} catch (error) {
console.error('获取隐患任务信息失败:', error);
}
if (!taskId.value && presetTaskId) {
taskId.value = presetTaskId;
}
};
const resetFlowSelection = () => {
selectedAssigneeIdentityId.value = '';
selectedAssigneeName.value = '';
nextStepName.value = '';
};
const applySelectedHazard = async (hazard) => {
if (!hazard) return;
selectedHazard.value = hazard.title || hazard.hazardTitle || `隐患${hazard.hazardId}`;
selectedHazardId.value = hazard.hazardId;
assignId.value = hazard.assignId ? String(hazard.assignId) : '';
fillHazardRelatedFields(hazard);
resetFlowSelection();
await resolveTaskIdForHazard(hazard);
await fetchNextStep();
};
const formatDeadline = (value) => {
if (!value) return '';
return String(value).split(' ')[0];
};
const applyWriteoffFormData = (data) => {
if (!data) return;
if (data.hazardId != null) {
selectedHazardId.value = String(data.hazardId);
}
selectedHazard.value = data.title || '';
formData.rectifyDeadline = formatDeadline(data.rectifyDeadline);
formData.responsibleDeptId = data.responsibleDeptId ?? '';
selectedDeptName.value = data.responsibleDeptName || '';
formData.responsiblePerson = data.responsiblePerson || '';
if (data.applyId != null) {
applyId.value = String(data.applyId);
}
formData.mainTreatmentContent = data.mainTreatmentContent || '';
formData.treatmentResult = data.treatmentResult || '';
formData.selfVerifyContent = data.selfVerifyContent || '';
};
const fetchWriteoffForm = async (hazardId) => {
if (!hazardId) return null;
try {
const res = await getWriteoffForm(hazardId);
if (res.code === 0 && res.data) {
applyWriteoffFormData(res.data);
} else {
uni.showToast({ title: res.msg || '获取销号审批表单失败', icon: 'none' });
}
return res;
} catch (error) {
console.error('获取销号审批表单失败:', error);
uni.showToast({ title: '获取销号审批表单失败', icon: 'none' });
return null;
}
};
const preselectHazardById = async (hazardId) => {
const hazard = findHazardInList(hazardId);
if (!hazard) return false;
await applySelectedHazard(hazard);
return true;
};
const onHazardConfirm = async (e) => {
if (e.value && e.value.length > 0) {
const index = e.indexs[0];
const hazard = acceptanceHazardList.value[index];
if (hazard) {
await applySelectedHazard(hazard);
}
}
showHazardPicker.value = false;
};
watch(quickApproveRadio, (value) => {
selectedAssigneeIdentityId.value = '';
selectedAssigneeName.value = '';
if (value === 'yes') {
formData.approvalOpinion = '';
formData.opinionRemark = '';
}
fetchNextStep();
});
watch(needNextAssignee, (value) => {
if (!value) {
selectedAssigneeIdentityId.value = '';
selectedAssigneeName.value = '';
}
});
const applySignatureFromServer = (signPath) => {
const url = signPath ? toSubmitFileUrl(signPath) : '';
if (!url) {
showCanvas.value = true;
signatureServerPath.value = '';
signatureUrl.value = '';
signatureLocalPath.value = '';
isSignatureEmpty.value = true;
return;
}
signatureServerPath.value = url;
signatureUrl.value = url;
signatureLocalPath.value = '';
showCanvas.value = false;
isSignatureEmpty.value = false;
};
const applySignatureFromLocal = (localPath) => {
if (!localPath) return;
signatureLocalPath.value = localPath;
signatureUrl.value = localPath;
signatureServerPath.value = '';
showCanvas.value = false;
isSignatureEmpty.value = false;
};
const resetSignature = () => {
showCanvas.value = true;
signatureUrl.value = '';
signatureServerPath.value = '';
signatureLocalPath.value = '';
isSignatureEmpty.value = true;
if (signatureRef.value) {
signatureRef.value.clear();
}
};
const onSignatureStart = () => {
isSignatureEmpty.value = false;
};
const onSignatureSigning = () => {
isSignatureEmpty.value = false;
};
const onSignatureClear = () => {
isSignatureEmpty.value = true;
signatureLocalPath.value = '';
};
const onSignatureEnd = () => {
isSignatureEmpty.value = false;
};
const clearSignature = () => {
isSignatureEmpty.value = true;
signatureLocalPath.value = '';
if (signatureRef.value) {
signatureRef.value.clear();
}
};
const reSign = () => {
isSignatureEmpty.value = true;
showCanvas.value = true;
signatureUrl.value = '';
signatureServerPath.value = '';
signatureLocalPath.value = '';
nextTick(() => {
if (signatureRef.value) {
signatureRef.value.clear();
}
});
};
const onSignatureConfirm = async (tempFilePath) => {
try {
const { url } = await uploadToCloud(tempFilePath);
applySignatureFromServer(url);
if (isSubmitting.value) {
await executeSubmit();
}
} catch (err) {
isSubmitting.value = false;
uni.hideLoading();
console.error('签名上传失败:', err);
uni.showToast({ title: '签名上传失败,请重试', icon: 'none' });
}
};
const handleAiGenerate = async () => {
if (!selectedHazardId.value) {
uni.showToast({ title: '请先选择隐患', icon: 'none' });
return;
}
aiGenerating.value = true;
try {
const hazardRes = await getHiddenDangerDetail({ hazardId: selectedHazardId.value });
if (hazardRes.code !== 0 || !hazardRes.data) {
uni.showToast({ title: '获取隐患详情失败', icon: 'none' });
return;
}
const assigns = hazardRes.data.assigns;
if (!assigns || assigns.length === 0 || !assigns[0].rectify) {
uni.showToast({ title: '该隐患暂无整改记录', icon: 'none' });
return;
}
const rectifyId = assigns[0].rectify.rectifyId;
const rectifyRes = await getRectifyDetail({ rectifyId });
if (rectifyRes.code !== 0 || !rectifyRes.data) {
uni.showToast({ title: '获取整改详情失败', icon: 'none' });
return;
}
const rectifyPlan = rectifyRes.data.rectifyPlan;
if (!rectifyPlan) {
uni.showToast({ title: '整改方案内容为空', icon: 'none' });
return;
}
const aiRes = await generateWriteoffContent({ rectifyContent: rectifyPlan });
if (aiRes.code === 0 && aiRes.data) {
formData.mainTreatmentContent = aiRes.data.mainContent || '';
formData.treatmentResult = aiRes.data.completionContent || '';
formData.selfVerifyContent = aiRes.data.selfInspection || '';
uni.showToast({ title: 'AI生成成功', icon: 'success' });
} else {
uni.showToast({ title: aiRes.msg || 'AI生成失败', icon: 'none' });
}
} catch (error) {
console.error('AI生成销号方案失败:', error);
uni.showToast({ title: 'AI生成失败请重试', icon: 'none' });
} finally {
aiGenerating.value = false;
}
};
const handleCancel = () => {
uni.navigateBack();
};
const ensureSignatureAndSubmit = async () => {
if (showCanvas.value) {
if (!signatureRef.value || isSignatureEmpty.value) {
uni.showToast({ title: '请进行电子签名', icon: 'none' });
return;
}
isSubmitting.value = true;
uni.showLoading({ title: '正在提交...', mask: true });
signatureRef.value.confirm();
return;
}
if (!signatureServerPath.value && !signatureLocalPath.value) {
uni.showToast({ title: '请进行电子签名', icon: 'none' });
return;
}
isSubmitting.value = true;
uni.showLoading({ title: '正在提交...', mask: true });
try {
if (!signatureServerPath.value && signatureLocalPath.value) {
const { url } = await uploadToCloud(signatureLocalPath.value);
applySignatureFromServer(url);
}
await executeSubmit();
} catch (err) {
isSubmitting.value = false;
uni.hideLoading();
console.error('签名上传失败:', err);
uni.showToast({ title: '签名上传失败,请重试', icon: 'none' });
}
};
const handleSubmit = async () => {
if (!selectedHazardId.value) {
uni.showToast({ title: '请选择隐患', icon: 'none' });
return;
}
if (!formData.approvalResult) {
uni.showToast({ title: '请选择审批结果', icon: 'none' });
return;
}
if (!applyId.value) {
uni.showToast({ title: '缺少申请ID', icon: 'none' });
return;
}
if (formData.approvalResult === 1) {
if (!quickApproveRadio.value) {
uni.showToast({ title: '请选择是否快速审批', icon: 'none' });
return;
}
if (needNextAssignee.value && !selectedAssigneeIdentityId.value) {
uni.showToast({ title: '请选择下一步处理人', icon: 'none' });
return;
}
await ensureSignatureAndSubmit();
return;
}
if (formData.approvalResult === 2) {
if (!formData.approvalOpinion) {
uni.showToast({ title: '请选择审批意见', icon: 'none' });
return;
}
if (!formData.opinionRemark?.trim()) {
uni.showToast({ title: '请输入意见说明', icon: 'none' });
return;
}
await ensureSignatureAndSubmit();
}
};
const executeSubmit = async () => {
const hazardId = Number(selectedHazardId.value);
const applyIdNum = Number(applyId.value);
const signPath = signatureServerPath.value || '';
try {
let res;
if (formData.approvalResult === 1) {
const approvePayload = {
hazardId,
applyId: applyIdNum,
signPath,
type: 'agree',
quickApprove: quickApproveRadio.value === 'yes',
sendMsgFlag: sendMsgFlag.value
};
if (needNextAssignee.value) {
approvePayload.assigneeId = Number(selectedAssigneeIdentityId.value);
}
res = await writeoffApprove(approvePayload);
} else {
res = await writeoffReject({
hazardId,
applyId: applyIdNum,
signPath,
type: formData.approvalOpinion,
remark: formData.opinionRemark.trim(),
sendMsgFlag: sendMsgFlag.value
});
}
uni.hideLoading();
if (res.code === 0) {
uni.showToast({ title: '审批成功', icon: 'success' });
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({ title: res.msg || '审批失败', icon: 'none' });
}
} catch (error) {
console.error('审批失败:', error);
uni.showToast({ title: '请求失败', icon: 'none' });
} finally {
isSubmitting.value = false;
uni.hideLoading();
}
};
onLoad(async (options) => {
try {
const sysInfo = uni.getSystemInfoSync();
signatureWidth.value = sysInfo.windowWidth - 40;
} catch (error) {
console.error('获取系统信息失败:', error);
}
const hazardId = options?.hazardId;
hazardLocked.value = options?.locked === '1';
if (options?.assignId) {
assignId.value = String(options.assignId);
}
if (options?.taskId) {
taskId.value = String(options.taskId);
}
const hazardFromOptions = buildWriteoffHazardFromOptions(options);
if (hazardFromOptions) {
applyWriteoffHazardPreview(hazardFromOptions);
}
if (hazardId) {
selectedHazardId.value = String(hazardId);
await fetchWriteoffForm(hazardId);
if (taskId.value) {
await fetchNextStep();
}
} else if (taskId.value) {
await fetchNextStep();
}
});
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #EBF2FC;
}
.result-btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 8rpx;
background: #f5f5f5;
color: #666;
font-size: 28rpx;
&::after {
border: none;
}
&.active {
background: #2667E9;
color: #fff;
}
}
.ai-btn-wrapper {
display: flex;
justify-content: flex-end;
}
.ai-analyze-btn {
display: flex;
align-items: center;
justify-content: center;
height: 72rpx;
padding: 0 32rpx;
font-size: 28rpx;
color: #fff;
background: linear-gradient(135deg, #4facfe 0%, #2668EA 100%);
border-radius: 36rpx;
border: none;
&::after {
border: none;
}
.ai-btn-icon {
margin-right: 8rpx;
font-size: 30rpx;
}
&[disabled] {
opacity: 0.7;
}
}
.picker-input {
background: #fff;
border-radius: 8rpx;
padding: 24rpx 20rpx;
margin-bottom: 20rpx;
border: 1rpx solid #eee;
text {
font-size: 28rpx;
}
&.readonly {
background: #f5f5f5;
color: #666;
}
}
.static-field {
background: #fff;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 20rpx 24rpx;
margin-bottom: 20rpx;
font-size: 28rpx;
color: #333;
line-height: 1.5;
}
.approval-radio-group {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 20rpx 24rpx;
margin-bottom: 20rpx;
.select-content {
flex: 1;
font-size: 28rpx;
color: #333;
}
}
.signature-action-btn {
margin: 0;
padding: 0 20rpx;
height: 50rpx;
font-size: 22rpx;
}
.signature-box {
width: 100%;
min-height: 240rpx;
background: #f8f8f8;
border: 1rpx dashed #dcdfe6;
border-radius: 8rpx;
.signature-display {
width: 100%;
height: 160px;
background-color: #f8f8f8;
}
.signature-pad-wrap {
border: 1px dashed #dcdfe6;
border-radius: 8rpx;
overflow: hidden;
background-color: #f8f8f8;
}
.signature-img {
width: 100%;
height: 100%;
}
.signature-placeholder {
color: #909399;
font-size: 28rpx;
}
}
</style>