feat(performance): 新增绩效下签功能支持
- 在月报审批页面增加 sign-off 场景支持 - 添加绩效下签对话框组件实现批量下签功能 - 增加导入绩效压缩包和直接下签两种操作模式 - 实现电子签名预览和日期格式化显示功能 - 更新绩效权限配置增加下签相关权限 - 优化审批对话框标题和按钮文案显示 - 添加导入结果提示和文件验证功能 - 实现团队模式下的下签操作入口 - 完善绩效表格状态和操作权限判断逻辑
This commit is contained in:
@@ -20,16 +20,20 @@ const props = defineProps<{
|
||||
reportType: string;
|
||||
period: string;
|
||||
mode?: 'add' | 'edit' | 'detail';
|
||||
scene?: 'fill' | 'detail' | 'approval';
|
||||
scene?: 'fill' | 'detail' | 'approval' | 'sign-off';
|
||||
baseInfo?: Api.WorkReport.Monthly.MonthlyReport | null;
|
||||
model: Api.WorkReport.Monthly.MonthlyReportSaveParams;
|
||||
approvalModel: Api.WorkReport.Monthly.MonthlyReportApproveParams;
|
||||
supervisorSignaturePreviewUrl?: string;
|
||||
supervisorSignatureLoading?: boolean;
|
||||
employeeSignaturePreviewUrl?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
back: [];
|
||||
requestApprove: [];
|
||||
requestReject: [];
|
||||
requestConfirmSignOff: [];
|
||||
changeApproval: [payload: Api.WorkReport.Monthly.MonthlyReportApproveParams];
|
||||
viewAudit: [];
|
||||
}>();
|
||||
@@ -79,6 +83,9 @@ const performanceDrawerVisible = ref(false);
|
||||
const performanceDrawerLoading = ref(false);
|
||||
const performanceDrawerMode = ref<'create' | 'edit'>('create');
|
||||
const performanceDrawerRow = ref<Api.Performance.Sheet.Sheet | null>(null);
|
||||
const isSignOffScene = computed(() => props.scene === 'sign-off');
|
||||
const hasSupervisorSignaturePreview = computed(() => Boolean(props.supervisorSignaturePreviewUrl));
|
||||
const hasEmployeeSignaturePreview = computed(() => Boolean(props.employeeSignaturePreviewUrl));
|
||||
|
||||
const auditDialogVisible = ref(false);
|
||||
const auditForm = reactive({
|
||||
@@ -95,22 +102,47 @@ const pageValidationModel = reactive({
|
||||
return props.approvalModel.performanceResult || '';
|
||||
}
|
||||
});
|
||||
const pageRules: FormRules = {
|
||||
performanceResult: [
|
||||
createRequiredRule('请输入绩效考核结果'),
|
||||
{
|
||||
validator: (_rule, value: string, callback) => {
|
||||
if (!value?.trim()) {
|
||||
callback(new Error('请输入绩效考核结果'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
};
|
||||
function formatDisplayDate(value: unknown) {
|
||||
const text = String(value ?? '')
|
||||
.replace(/^\[\s*/u, '')
|
||||
.replace(/\s*\]$/u, '')
|
||||
.trim();
|
||||
if (!text) return '--';
|
||||
|
||||
const commaDateMatch = text.match(/^(\d{4})\s*,\s*(\d{1,2})\s*,\s*(\d{1,2})$/u);
|
||||
if (commaDateMatch) {
|
||||
const [, year, month, day] = commaDateMatch;
|
||||
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
const pageRules = computed<FormRules>(() => {
|
||||
if (isSignOffScene.value) {
|
||||
return {
|
||||
meetingDate: [createRequiredRule('请选择面谈日期')]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
performanceResult: [
|
||||
createRequiredRule('请输入绩效考核结果'),
|
||||
{
|
||||
validator: (_rule, value: string, callback) => {
|
||||
if (!value?.trim()) {
|
||||
callback(new Error('请输入绩效考核结果'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
const auditRules = computed<FormRules>(() => ({
|
||||
opinion: rejectOpinionRequired.value
|
||||
? [
|
||||
@@ -252,24 +284,12 @@ const mainForm = reactive({
|
||||
});
|
||||
|
||||
const signatureForm = reactive({
|
||||
get employeeSign() {
|
||||
return props.approvalModel.employeeSignName || '';
|
||||
},
|
||||
set employeeSign(value: string) {
|
||||
patchApproval('employeeSignName', value);
|
||||
},
|
||||
get employeeDate() {
|
||||
return props.approvalModel.employeeSignedDate || todayText.value;
|
||||
return props.approvalModel.employeeSignedDate || '';
|
||||
},
|
||||
set employeeDate(value: string) {
|
||||
patchApproval('employeeSignedDate', value);
|
||||
},
|
||||
get supervisorSign() {
|
||||
return props.approvalModel.supervisorSignName || '';
|
||||
},
|
||||
set supervisorSign(value: string) {
|
||||
patchApproval('supervisorSignName', value);
|
||||
},
|
||||
get supervisorDate() {
|
||||
return props.approvalModel.supervisorSignedDate || todayText.value;
|
||||
},
|
||||
@@ -560,7 +580,8 @@ async function submitAudit() {
|
||||
}
|
||||
}
|
||||
|
||||
patchApproval('employeeSignedDate', signatureForm.employeeDate);
|
||||
patchApproval('employeeSignName', undefined);
|
||||
patchApproval('employeeSignedDate', undefined);
|
||||
patchApproval('supervisorSignedDate', signatureForm.supervisorDate);
|
||||
patchApproval(
|
||||
'reason',
|
||||
@@ -617,6 +638,16 @@ function handlePerformanceSaved(payload: { actualScoreTotal: string }) {
|
||||
patchApproval('performanceResult', payload.actualScoreTotal);
|
||||
}
|
||||
|
||||
async function handleConfirmSignOff() {
|
||||
try {
|
||||
await validatePageForm();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('requestConfirmSignOff');
|
||||
}
|
||||
|
||||
watch(rejectOpinionRequired, async () => {
|
||||
if (!auditDialogVisible.value) return;
|
||||
|
||||
@@ -670,7 +701,13 @@ watch(
|
||||
<div class="field field-form-item">
|
||||
<label>面谈时间</label>
|
||||
<ElFormItem class="field-inline-form-item" prop="meetingDate">
|
||||
<ElDatePicker v-model="mainForm.meetingDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
|
||||
<ElDatePicker
|
||||
v-model="mainForm.meetingDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
disabled
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
</div>
|
||||
@@ -731,10 +768,23 @@ watch(
|
||||
<span class="feedback-tag success">优势</span>
|
||||
</div>
|
||||
<div class="feedback-cell">
|
||||
<ElInput v-model="feedbackForm.strengthDesc" type="textarea" :rows="2" placeholder="请输入优势描述" />
|
||||
<div v-if="isSignOffScene" class="rich-editor readonly feedback-readonly">
|
||||
{{ feedbackForm.strengthDesc || '' }}
|
||||
</div>
|
||||
<ElInput
|
||||
v-else
|
||||
v-model="feedbackForm.strengthDesc"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入优势描述"
|
||||
/>
|
||||
</div>
|
||||
<div class="feedback-cell">
|
||||
<div v-if="isSignOffScene" class="rich-editor readonly feedback-readonly">
|
||||
{{ feedbackForm.strengthExample || '' }}
|
||||
</div>
|
||||
<ElInput
|
||||
v-else
|
||||
v-model="feedbackForm.strengthExample"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
@@ -747,10 +797,23 @@ watch(
|
||||
<span class="feedback-tag warning">劣势</span>
|
||||
</div>
|
||||
<div class="feedback-cell">
|
||||
<ElInput v-model="feedbackForm.weaknessDesc" type="textarea" :rows="2" placeholder="请输入劣势描述" />
|
||||
<div v-if="isSignOffScene" class="rich-editor readonly feedback-readonly">
|
||||
{{ feedbackForm.weaknessDesc || '' }}
|
||||
</div>
|
||||
<ElInput
|
||||
v-else
|
||||
v-model="feedbackForm.weaknessDesc"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入劣势描述"
|
||||
/>
|
||||
</div>
|
||||
<div class="feedback-cell">
|
||||
<div v-if="isSignOffScene" class="rich-editor readonly feedback-readonly">
|
||||
{{ feedbackForm.weaknessExample || '' }}
|
||||
</div>
|
||||
<ElInput
|
||||
v-else
|
||||
v-model="feedbackForm.weaknessExample"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
@@ -760,7 +823,11 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
<div class="feedback-right">
|
||||
<div v-if="isSignOffScene" class="rich-editor readonly feedback-readonly">
|
||||
{{ feedbackForm.improvementSuggestion || '' }}
|
||||
</div>
|
||||
<ElInput
|
||||
v-else
|
||||
v-model="feedbackForm.improvementSuggestion"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
@@ -778,7 +845,7 @@ watch(
|
||||
<div class="performance-result">
|
||||
<div class="performance-label">
|
||||
绩效考核结果
|
||||
<span class="field-required-mark">*</span>
|
||||
<span v-if="!isSignOffScene" class="field-required-mark">*</span>
|
||||
</div>
|
||||
<ElFormItem class="performance-inline-form-item" prop="performanceResult">
|
||||
<div class="performance-input-group">
|
||||
@@ -790,6 +857,7 @@ watch(
|
||||
@input="handlePerformanceScoreInput"
|
||||
/>
|
||||
<ElButton
|
||||
v-if="!isSignOffScene"
|
||||
plain
|
||||
size="small"
|
||||
type="primary"
|
||||
@@ -836,39 +904,92 @@ watch(
|
||||
</div>
|
||||
|
||||
<div class="signature-section">
|
||||
<div class="signature-row">
|
||||
<span>被考核人签名:</span>
|
||||
<ElInput v-model="signatureForm.employeeSign" class="signature-input" placeholder="请输入签名" />
|
||||
<span>日期:</span>
|
||||
<ElDatePicker
|
||||
v-model="signatureForm.employeeDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
class="signature-date"
|
||||
/>
|
||||
</div>
|
||||
<div class="signature-row">
|
||||
<span>上级签名:</span>
|
||||
<ElInput v-model="signatureForm.supervisorSign" class="signature-input" placeholder="请输入签名" />
|
||||
<span>日期:</span>
|
||||
<ElDatePicker
|
||||
v-model="signatureForm.supervisorDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
class="signature-date"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="isSignOffScene">
|
||||
<div class="signature-row">
|
||||
<span>被考核人签名:</span>
|
||||
<div
|
||||
class="signature-preview-panel"
|
||||
:class="{ 'signature-preview-panel--empty': !hasEmployeeSignaturePreview }"
|
||||
>
|
||||
<img
|
||||
v-if="hasEmployeeSignaturePreview"
|
||||
:src="props.employeeSignaturePreviewUrl"
|
||||
alt="被考核人电子签名"
|
||||
class="signature-preview-image"
|
||||
/>
|
||||
<span v-else>未维护电子签名</span>
|
||||
</div>
|
||||
<span>日期:</span>
|
||||
<div class="signature-readonly">{{ formatDisplayDate(signatureForm.employeeDate) }}</div>
|
||||
</div>
|
||||
<div class="signature-row">
|
||||
<span>上级签名:</span>
|
||||
<div
|
||||
v-loading="props.supervisorSignatureLoading"
|
||||
class="signature-preview-panel"
|
||||
:class="{ 'signature-preview-panel--empty': !hasSupervisorSignaturePreview }"
|
||||
>
|
||||
<img
|
||||
v-if="hasSupervisorSignaturePreview"
|
||||
:src="props.supervisorSignaturePreviewUrl"
|
||||
alt="上级电子签名"
|
||||
class="signature-preview-image"
|
||||
/>
|
||||
<span v-else>未维护电子签名</span>
|
||||
</div>
|
||||
<span>日期:</span>
|
||||
<div class="signature-readonly">{{ formatDisplayDate(props.approvalModel.supervisorSignedDate) }}</div>
|
||||
</div>
|
||||
<div v-if="!hasEmployeeSignaturePreview" class="signature-warning">
|
||||
当前账号未维护电子签名,确认后后端将无法回填员工签名图片。
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="signature-row">
|
||||
<span>被考核人签名:</span>
|
||||
<div class="signature-readonly">将在下签确认时自动补齐</div>
|
||||
<span>日期:</span>
|
||||
<div class="signature-readonly">将在下签确认时自动补齐</div>
|
||||
</div>
|
||||
<div class="signature-row">
|
||||
<span>上级签名:</span>
|
||||
<div
|
||||
v-loading="props.supervisorSignatureLoading"
|
||||
class="signature-preview-panel"
|
||||
:class="{ 'signature-preview-panel--empty': !hasSupervisorSignaturePreview }"
|
||||
>
|
||||
<img
|
||||
v-if="hasSupervisorSignaturePreview"
|
||||
:src="props.supervisorSignaturePreviewUrl"
|
||||
alt="上级电子签名"
|
||||
class="signature-preview-image"
|
||||
/>
|
||||
<span v-else>未维护电子签名</span>
|
||||
</div>
|
||||
<span>日期:</span>
|
||||
<ElDatePicker
|
||||
v-model="signatureForm.supervisorDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
class="signature-date"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!hasSupervisorSignaturePreview" class="signature-warning">
|
||||
当前登录用户未维护电子签名,审批提交后将无法回填上级签名图片。
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</ElForm>
|
||||
|
||||
<div class="form-actions approval-form-actions">
|
||||
<ElButton @click="emit('back')">退出</ElButton>
|
||||
<ElButton type="primary" @click="openAuditDialog">审批</ElButton>
|
||||
<ElButton v-if="isSignOffScene" type="primary" @click="handleConfirmSignOff">确认</ElButton>
|
||||
<ElButton v-else type="primary" @click="openAuditDialog">审批</ElButton>
|
||||
</div>
|
||||
|
||||
<BusinessFormDialog
|
||||
v-if="!isSignOffScene"
|
||||
v-model="auditDialogVisible"
|
||||
title="月报审批"
|
||||
preset="sm"
|
||||
@@ -937,6 +1058,7 @@ watch(
|
||||
</BusinessFormDialog>
|
||||
|
||||
<PerformanceExcelEditorDrawer
|
||||
v-if="!isSignOffScene"
|
||||
v-model:visible="performanceDrawerVisible"
|
||||
:row-data="performanceDrawerRow"
|
||||
:mode="performanceDrawerMode"
|
||||
@@ -1731,6 +1853,11 @@ watch(
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.feedback-readonly {
|
||||
width: 100%;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.feedback-cell {
|
||||
padding: 16px;
|
||||
border-right: 1px solid #f1f5f9;
|
||||
@@ -1800,27 +1927,37 @@ watch(
|
||||
border-top: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.signature-input {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.signature-input :deep(.el-input__wrapper) {
|
||||
box-shadow: none !important;
|
||||
background: transparent;
|
||||
.signature-readonly {
|
||||
min-width: 180px;
|
||||
padding: 0 4px 4px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
border-radius: 0;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.signature-input :deep(.el-input__inner) {
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: #334155;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.signature-input :deep(.el-input__inner::placeholder) {
|
||||
color: #cbd5e1;
|
||||
font-weight: 400;
|
||||
.signature-preview-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 116px;
|
||||
height: 44px;
|
||||
padding: 3px;
|
||||
border: 1px dashed #cbd5e1;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.signature-preview-panel--empty {
|
||||
color: #94a3b8;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.signature-preview-image {
|
||||
max-width: 98px;
|
||||
max-height: 28px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.signature-date {
|
||||
@@ -1846,6 +1983,14 @@ watch(
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.signature-warning {
|
||||
margin-top: 8px;
|
||||
color: #c2410c;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.performance-result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
downloadFile,
|
||||
fetchConfirmMonthlyReportSignOff,
|
||||
fetchGetMonthlyReportSignOffDetail,
|
||||
fetchGetMyUserSignature
|
||||
} from '@/service/api';
|
||||
import MonthlyReportApprovalPage from './approval-page.vue';
|
||||
|
||||
defineOptions({ name: 'MonthlySignOffPage' });
|
||||
|
||||
const props = defineProps<{
|
||||
reportId?: string;
|
||||
baseInfo?: Api.WorkReport.Monthly.MonthlyReport | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
back: [];
|
||||
submitted: [];
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const detail = ref<Api.WorkReport.Monthly.MonthlyReportSignOffDetail | null>(null);
|
||||
const supervisorSignaturePreviewUrl = ref('');
|
||||
const supervisorSignatureObjectUrl = ref<string | null>(null);
|
||||
const employeeSignaturePreviewUrl = ref('');
|
||||
const employeeSignatureObjectUrl = ref<string | null>(null);
|
||||
|
||||
const report = computed(() => detail.value?.report || props.baseInfo || null);
|
||||
const monthlyModel = computed<Api.WorkReport.Monthly.MonthlyReportSaveParams>(() => ({
|
||||
periodKey: report.value?.periodKey || '',
|
||||
periodLabel: report.value?.periodLabel || '',
|
||||
periodStartDate: report.value?.periodStartDate || '',
|
||||
periodEndDate: report.value?.periodEndDate || '',
|
||||
reviewItems: report.value?.reviewItems || [],
|
||||
planItems: report.value?.planItems || []
|
||||
}));
|
||||
|
||||
const approvalDraft = reactive<Api.WorkReport.Monthly.MonthlyReportApproveParams>({
|
||||
reason: '',
|
||||
meetingDate: dayjs().format('YYYY-MM-DD'),
|
||||
strengthDesc: '',
|
||||
strengthExample: '',
|
||||
weaknessDesc: '',
|
||||
weaknessExample: '',
|
||||
improvementSuggestion: '',
|
||||
performanceResult: '',
|
||||
employeeSignName: undefined,
|
||||
employeeSignedDate: dayjs().format('YYYY-MM-DD'),
|
||||
supervisorSignName: '',
|
||||
supervisorSignedDate: ''
|
||||
});
|
||||
|
||||
function patchApprovalRecord(record: Api.WorkReport.Monthly.MonthlyReportApprovalRecord | null | undefined) {
|
||||
Object.assign(approvalDraft, {
|
||||
reason: '',
|
||||
meetingDate: dayjs().format('YYYY-MM-DD'),
|
||||
strengthDesc: record?.strengthDesc || '',
|
||||
strengthExample: record?.strengthExample || '',
|
||||
weaknessDesc: record?.weaknessDesc || '',
|
||||
weaknessExample: record?.weaknessExample || '',
|
||||
improvementSuggestion: record?.improvementSuggestion || '',
|
||||
performanceResult: record?.performanceResult || '',
|
||||
employeeSignName: record?.employeeSignName,
|
||||
employeeSignedDate: dayjs().format('YYYY-MM-DD'),
|
||||
supervisorSignName: record?.supervisorSignName || '',
|
||||
supervisorSignedDate: record?.supervisorSignedDate || ''
|
||||
});
|
||||
}
|
||||
|
||||
function revokePreviewUrl(target: 'supervisor' | 'employee') {
|
||||
const refValue = target === 'supervisor' ? supervisorSignatureObjectUrl : employeeSignatureObjectUrl;
|
||||
const previewRef = target === 'supervisor' ? supervisorSignaturePreviewUrl : employeeSignaturePreviewUrl;
|
||||
|
||||
if (refValue.value) {
|
||||
URL.revokeObjectURL(refValue.value);
|
||||
refValue.value = null;
|
||||
}
|
||||
|
||||
previewRef.value = '';
|
||||
}
|
||||
|
||||
async function loadSignaturePreview(fileId: string | null | undefined, target: 'supervisor' | 'employee') {
|
||||
revokePreviewUrl(target);
|
||||
|
||||
if (!fileId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { error, data } = await downloadFile(fileId);
|
||||
if (error || !data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const objectUrl = URL.createObjectURL(data);
|
||||
if (target === 'supervisor') {
|
||||
supervisorSignatureObjectUrl.value = objectUrl;
|
||||
supervisorSignaturePreviewUrl.value = objectUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
employeeSignatureObjectUrl.value = objectUrl;
|
||||
employeeSignaturePreviewUrl.value = objectUrl;
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
if (!props.reportId) {
|
||||
detail.value = null;
|
||||
patchApprovalRecord(null);
|
||||
revokePreviewUrl('supervisor');
|
||||
revokePreviewUrl('employee');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
const [detailResult, mySignatureResult] = await Promise.all([
|
||||
fetchGetMonthlyReportSignOffDetail(props.reportId),
|
||||
fetchGetMyUserSignature()
|
||||
]);
|
||||
loading.value = false;
|
||||
|
||||
if (detailResult.error || !detailResult.data) {
|
||||
detail.value = null;
|
||||
patchApprovalRecord(null);
|
||||
revokePreviewUrl('supervisor');
|
||||
revokePreviewUrl('employee');
|
||||
return;
|
||||
}
|
||||
|
||||
detail.value = detailResult.data;
|
||||
patchApprovalRecord(detailResult.data.latestApprovalRecord);
|
||||
|
||||
await Promise.all([
|
||||
loadSignaturePreview(detailResult.data.latestApprovalRecord?.supervisorSignatureFileId, 'supervisor'),
|
||||
loadSignaturePreview(mySignatureResult.error ? null : mySignatureResult.data?.fileId, 'employee')
|
||||
]);
|
||||
}
|
||||
|
||||
function handleApprovalChange(payload: Api.WorkReport.Monthly.MonthlyReportApproveParams) {
|
||||
Object.assign(approvalDraft, payload);
|
||||
}
|
||||
|
||||
async function handleConfirm() {
|
||||
if (!props.reportId) {
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
const result = await fetchConfirmMonthlyReportSignOff(props.reportId, {
|
||||
meetingDate: approvalDraft.meetingDate || dayjs().format('YYYY-MM-DD'),
|
||||
employeeSignedDate: approvalDraft.employeeSignedDate || undefined
|
||||
});
|
||||
submitting.value = false;
|
||||
|
||||
if (result.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.$message?.success('月报下签确认成功');
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.reportId,
|
||||
() => {
|
||||
loadDetail();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
revokePreviewUrl('supervisor');
|
||||
revokePreviewUrl('employee');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<MonthlyReportApprovalPage
|
||||
report-type="个人月报"
|
||||
:period="report?.periodLabel || ''"
|
||||
mode="detail"
|
||||
scene="sign-off"
|
||||
:base-info="report"
|
||||
:model="monthlyModel"
|
||||
:approval-model="approvalDraft"
|
||||
:supervisor-signature-preview-url="supervisorSignaturePreviewUrl"
|
||||
:employee-signature-preview-url="employeeSignaturePreviewUrl"
|
||||
:supervisor-signature-loading="false"
|
||||
@back="emit('back')"
|
||||
@change-approval="handleApprovalChange"
|
||||
@request-confirm-sign-off="handleConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user