From 08fff8bbc5a2ca8af69fd238ef4b7a79c80f4732 Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Sun, 19 Jul 2026 18:21:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(performance):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=BB=A9=E6=95=88=E4=B8=8B=E7=AD=BE=E5=8A=9F=E8=83=BD=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20-=20=E5=9C=A8=E6=9C=88=E6=8A=A5=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=A2=9E=E5=8A=A0=20sign-off=20=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=E6=94=AF=E6=8C=81=20-=20=E6=B7=BB=E5=8A=A0=E7=BB=A9?= =?UTF-8?q?=E6=95=88=E4=B8=8B=E7=AD=BE=E5=AF=B9=E8=AF=9D=E6=A1=86=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=AE=9E=E7=8E=B0=E6=89=B9=E9=87=8F=E4=B8=8B=E7=AD=BE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20-=20=E5=A2=9E=E5=8A=A0=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E7=BB=A9=E6=95=88=E5=8E=8B=E7=BC=A9=E5=8C=85=E5=92=8C=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E4=B8=8B=E7=AD=BE=E4=B8=A4=E7=A7=8D=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=20-=20=E5=AE=9E=E7=8E=B0=E7=94=B5=E5=AD=90?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E9=A2=84=E8=A7=88=E5=92=8C=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E6=98=BE=E7=A4=BA=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20-=20=E6=9B=B4=E6=96=B0=E7=BB=A9=E6=95=88=E6=9D=83=E9=99=90?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=A2=9E=E5=8A=A0=E4=B8=8B=E7=AD=BE=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=9D=83=E9=99=90=20-=20=E4=BC=98=E5=8C=96=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=A0=87=E9=A2=98=E5=92=8C?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E6=96=87=E6=A1=88=E6=98=BE=E7=A4=BA=20-=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=BC=E5=85=A5=E7=BB=93=E6=9E=9C=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E5=92=8C=E6=96=87=E4=BB=B6=E9=AA=8C=E8=AF=81=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20-=20=E5=AE=9E=E7=8E=B0=E5=9B=A2=E9=98=9F=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E4=B8=8B=E7=9A=84=E4=B8=8B=E7=AD=BE=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=20-=20=E5=AE=8C=E5=96=84=E7=BB=A9=E6=95=88?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E7=8A=B6=E6=80=81=E5=92=8C=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=9D=83=E9=99=90=E5=88=A4=E6=96=AD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/api/performance.ts | 52 +++ src/service/api/system-manage.ts | 64 ++++ src/service/api/work-report.ts | 60 +++- src/typings/api/performance.d.ts | 24 ++ src/typings/api/system-manage.d.ts | 19 + src/typings/api/work-report.d.ts | 15 + src/typings/components.d.ts | 2 + .../personal-center/my-performance/index.vue | 41 ++- .../modules/performance-action-dialog.vue | 8 +- .../performance-excel-editor-drawer.vue | 2 +- .../modules/performance-shared.ts | 2 + .../modules/performance-sign-off-dialog.vue | 300 ++++++++++++++++ .../monthly/modules/approval-page.vue | 297 ++++++++++++---- .../monthly/modules/sign-off-page.vue | 198 +++++++++++ .../components/prototype-page-dialog.vue | 126 ++++++- .../user/modules/user-operate-dialog.vue | 331 +++++++++++++++++- .../modules/workbench-todo-panel.vue | 98 +++++- 17 files changed, 1523 insertions(+), 116 deletions(-) create mode 100644 src/views/personal-center/my-performance/modules/performance-sign-off-dialog.vue create mode 100644 src/views/personal-center/work-report/monthly/modules/sign-off-page.vue diff --git a/src/service/api/performance.ts b/src/service/api/performance.ts index a643d2a..0c55b23 100644 --- a/src/service/api/performance.ts +++ b/src/service/api/performance.ts @@ -44,6 +44,15 @@ type SheetPageResponse = { list: SheetResponse[]; }; +type ImportZipResultResponse = Omit & { + workbookCount?: number | string | null; + sheetCount?: number | string | null; + successCount?: number | string | null; + skippedCount?: number | string | null; + failedCount?: number | string | null; + messages?: string[] | null; +}; + type StatusLogResponse = Omit & { id: StringIdResponse; sheetId: StringIdResponse; @@ -144,6 +153,8 @@ function normalizeSheet(response: SheetResponse): Api.Performance.Sheet.Sheet { fileId: normalizeNullableStringId(response.fileId), fileName: response.fileName ?? null, statusName: response.statusName || response.statusCode, + signOffStatus: response.signOffStatus ?? null, + signOffStatusName: response.signOffStatusName ?? null, actualScoreTotal: response.actualScoreTotal ?? null, baseScoreTotal: response.baseScoreTotal ?? null, extraScoreTotal: response.extraScoreTotal ?? null, @@ -282,6 +293,7 @@ function createSheetQuery(params: Api.Performance.Sheet.SearchParams = {}) { appendValue(query, 'employeeDeptId', params.employeeDeptId); appendValue(query, 'managerName', params.managerName); appendValue(query, 'statusCode', params.statusCode); + appendValue(query, 'signOffStatus', params.signOffStatus); return query.toString(); } @@ -377,6 +389,46 @@ export async function createPerformanceSheet(data: Api.Performance.Sheet.CreateP return mapServiceResult(result as ServiceRequestResult, normalizeStringId); } +export async function importPerformanceSheetZip(data: Api.Performance.Sheet.ImportZipParams) { + const formData = new FormData(); + formData.append('file', data.file); + formData.append('periodMonth', data.periodMonth); + data.employeeIds?.forEach(id => { + formData.append('employeeIds', id); + }); + + const result = await request({ + ...safeJsonRequestConfig, + url: `${SHEET_PREFIX}/import-zip`, + method: 'post', + data: formData + }); + + return mapServiceResult(result as ServiceRequestResult, payload => ({ + workbookCount: normalizeTotal(payload.workbookCount), + sheetCount: normalizeTotal(payload.sheetCount), + successCount: normalizeTotal(payload.successCount), + skippedCount: normalizeTotal(payload.skippedCount), + failedCount: normalizeTotal(payload.failedCount), + messages: payload.messages ?? [] + })); +} + +export async function signOffPerformanceSheets(data: Api.Performance.Sheet.SignOffParams) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${SHEET_PREFIX}/sign-off`, + method: 'post', + data: { + ...data, + employeeIds: data.employeeIds && data.employeeIds.length ? data.employeeIds : undefined, + remark: data.remark?.trim() || undefined + } + }); + + return mapServiceResult(result as ServiceRequestResult, normalizeStringId); +} + export function updatePerformanceSheetExcel(id: string, data: Api.Performance.Sheet.ExcelUpdateParams) { return request({ ...safeJsonRequestConfig, diff --git a/src/service/api/system-manage.ts b/src/service/api/system-manage.ts index 301af6b..9337ffb 100644 --- a/src/service/api/system-manage.ts +++ b/src/service/api/system-manage.ts @@ -17,6 +17,7 @@ const DEPT_PREFIX = `${SYSTEM_SERVICE_PREFIX}/dept`; const POST_PREFIX = `${SYSTEM_SERVICE_PREFIX}/post`; const ORG_LEADER_PREFIX = `${SYSTEM_SERVICE_PREFIX}/org-leader`; const USER_MANAGEMENT_RELATION_PREFIX = `${SYSTEM_SERVICE_PREFIX}/user-management-relation`; +const USER_SIGNATURE_PREFIX = `${SYSTEM_SERVICE_PREFIX}/user-signatures`; function appendQueryValue(query: URLSearchParams, key: string, value: unknown) { if (value === null || value === undefined || value === '') { @@ -146,6 +147,12 @@ type MySubordinateTreeNodeResponse = Omit & { + id: string | number; + userId: string | number; + fileId: string | number; +}; + function normalizeUserSimple(user: UserSimpleResponse): Api.SystemManage.UserSimple { return { ...user, @@ -217,6 +224,20 @@ function normalizeMySubordinateTreeNode(node: MySubordinateTreeNodeResponse): Ap }; } +function normalizeUserSignature(signature: UserSignatureResponse): Api.SystemManage.UserSignature { + return { + ...signature, + id: normalizeStringId(signature.id), + userId: normalizeStringId(signature.userId), + fileId: normalizeStringId(signature.fileId), + fileName: signature.fileName ?? null, + status: signature.status ?? null, + remark: signature.remark ?? null, + createTime: signature.createTime ?? null, + updateTime: signature.updateTime ?? null + }; +} + /** 获取角色分页 */ export async function fetchGetRolePage(params?: Api.SystemManage.RoleSearchParams) { const query = createRolePageQuery(params); @@ -742,6 +763,49 @@ export function fetchAssignUserRoles(data: Api.SystemManage.AssignUserRoleParams }); } +export async function fetchGetUserSignature(userId: string) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${USER_SIGNATURE_PREFIX}/get`, + method: 'get', + params: { userId } + }); + + return mapServiceResult(result as ServiceRequestResult, data => + data ? normalizeUserSignature(data) : null + ); +} + +export function fetchSaveUserSignature(data: Api.SystemManage.SaveUserSignatureParams) { + return request({ + ...safeJsonRequestConfig, + url: `${USER_SIGNATURE_PREFIX}/save`, + method: 'post', + data + }); +} + +export function fetchDeleteUserSignature(userId: string) { + return request({ + ...safeJsonRequestConfig, + url: `${USER_SIGNATURE_PREFIX}/delete`, + method: 'delete', + params: { userId } + }); +} + +export async function fetchGetMyUserSignature() { + const result = await request({ + ...safeJsonRequestConfig, + url: `${USER_SIGNATURE_PREFIX}/my`, + method: 'get' + }); + + return mapServiceResult(result as ServiceRequestResult, data => + data ? normalizeUserSignature(data) : null + ); +} + // ==================== 用户管理链路相关 API ==================== /** * 获取用户管理链路树形结构 diff --git a/src/service/api/work-report.ts b/src/service/api/work-report.ts index 8186ad2..9a9d627 100644 --- a/src/service/api/work-report.ts +++ b/src/service/api/work-report.ts @@ -88,11 +88,18 @@ type ApprovalRecordResponse = Omit< type MonthlyApprovalRecordResponse = Omit< Api.WorkReport.Monthly.MonthlyReportApprovalRecord, - 'id' | 'statusLogId' | 'auditorUserId' + 'id' | 'statusLogId' | 'auditorUserId' | 'employeeSignatureFileId' | 'supervisorSignatureFileId' > & { id: StringIdResponse; statusLogId: StringIdResponse; auditorUserId: StringIdResponse; + employeeSignatureFileId?: MaybeStringIdResponse; + supervisorSignatureFileId?: MaybeStringIdResponse; +}; + +type MonthlySignOffDetailResponse = { + report: MonthlyReportResponse; + latestApprovalRecord?: MonthlyApprovalRecordResponse | null; }; type ProjectOptionResponse = Omit & { @@ -229,6 +236,7 @@ function createWeeklyPageQuery(params: Api.WorkReport.Weekly.WeeklyReportSearchP function createMonthlyPageQuery(params: Api.WorkReport.Monthly.MonthlyReportSearchParams = {}) { const query = createBasePageQuery(params); appendNullableArrayFlag(query, 'reporterIds', params.reporterIds); + appendValue(query, 'signOffStatus', params.signOffStatus); return query.toString(); } @@ -298,6 +306,8 @@ function normalizeMonthlyReport(response: MonthlyReportResponse): Api.WorkReport reporterDeptName: response.reporterDeptName ?? null, reporterPostName: response.reporterPostName ?? null, statusName: response.statusName || response.statusCode, + signOffStatus: response.signOffStatus ?? null, + signOffStatusName: response.signOffStatusName ?? null, allowEdit: normalizeBooleanFlag(response.allowEdit), terminal: normalizeBooleanFlag(response.terminal), totalWorkHours: normalizeReportTotalWorkHours(response.totalWorkHours, fallbackTotalWorkHours), @@ -361,7 +371,12 @@ function normalizeMonthlyApprovalRecord( statusLogId: normalizeStringId(response.statusLogId), auditorUserId: normalizeStringId(response.auditorUserId), conclusion: normalizeApprovalConclusion(response.conclusion), - opinion: response.opinion ?? null + opinion: response.opinion ?? null, + meetingDate: normalizeDateText(response.meetingDate) ?? null, + employeeSignatureFileId: normalizeNullableStringId(response.employeeSignatureFileId), + employeeSignedDate: normalizeDateText(response.employeeSignedDate) ?? null, + supervisorSignatureFileId: normalizeNullableStringId(response.supervisorSignatureFileId), + supervisorSignedDate: normalizeDateText(response.supervisorSignedDate) ?? null }; } @@ -697,6 +712,19 @@ export async function fetchGetMonthlyReportApprovalPage(params: Api.WorkReport.M ); } +export async function fetchGetMonthlyReportSignOffPage(params: Api.WorkReport.Monthly.MonthlyReportSearchParams = {}) { + const query = createMonthlyPageQuery(params); + const result = await request>({ + ...safeJsonRequestConfig, + url: query ? `${MONTHLY_PREFIX}/sign-off-page?${query}` : `${MONTHLY_PREFIX}/sign-off-page`, + method: 'get' + }); + + return mapServiceResult(result as ServiceRequestResult>, data => + mapPage(data, normalizeMonthlyReport) + ); +} + export async function fetchGetMonthlyReportDetail(id: string) { const result = await request({ ...safeJsonRequestConfig, @@ -799,6 +827,34 @@ export async function fetchGetMonthlyReportApprovalRecords(id: string) { ); } +export async function fetchGetMonthlyReportSignOffDetail(id: string) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${MONTHLY_PREFIX}/${id}/sign-off-detail`, + method: 'get' + }); + + return mapServiceResult(result as ServiceRequestResult, data => ({ + report: normalizeMonthlyReport(data.report), + latestApprovalRecord: data.latestApprovalRecord ? normalizeMonthlyApprovalRecord(data.latestApprovalRecord) : null + })); +} + +export function fetchConfirmMonthlyReportSignOff( + id: string, + data: Api.WorkReport.Monthly.MonthlyReportSignOffConfirmParams +) { + return request({ + ...safeJsonRequestConfig, + url: `${MONTHLY_PREFIX}/${id}/confirm-sign-off`, + method: 'post', + data: { + meetingDate: data.meetingDate, + employeeSignedDate: data.employeeSignedDate || undefined + } + }); +} + export function fetchExportMonthlyReports(params: Api.WorkReport.Monthly.MonthlyReportSearchParams = {}) { const query = createMonthlyPageQuery(params); return request({ diff --git a/src/typings/api/performance.d.ts b/src/typings/api/performance.d.ts index 2d12ddb..1bd9be3 100644 --- a/src/typings/api/performance.d.ts +++ b/src/typings/api/performance.d.ts @@ -74,6 +74,8 @@ declare namespace Api { fileVersion: number; statusCode: Common.SheetStatusCode; statusName: string; + signOffStatus?: string | null; + signOffStatusName?: string | null; actualScoreTotal?: string | number | null; baseScoreTotal?: string | number | null; extraScoreTotal?: string | number | null; @@ -96,6 +98,7 @@ declare namespace Api { managerId: string; managerName: string; statusCode: Common.SheetStatusCode; + signOffStatus: string; } >; @@ -121,6 +124,27 @@ declare namespace Api { ids: string[]; } + interface ImportZipParams { + file: File; + periodMonth: string; + employeeIds?: string[] | null; + } + + interface ImportZipResult { + workbookCount: number; + sheetCount: number; + successCount: number; + skippedCount: number; + failedCount: number; + messages: string[]; + } + + interface SignOffParams { + periodMonth: string; + employeeIds?: string[] | null; + remark?: string | null; + } + interface StatusDict { statusCode: Common.SheetStatusCode; statusName: string; diff --git a/src/typings/api/system-manage.d.ts b/src/typings/api/system-manage.d.ts index d3ff7ac..351ae01 100644 --- a/src/typings/api/system-manage.d.ts +++ b/src/typings/api/system-manage.d.ts @@ -239,6 +239,25 @@ declare namespace Api { roleIds: string[]; } + interface UserSignature { + id: string; + userId: string; + fileId: string; + fileName?: string | null; + status?: number | null; + remark?: string | null; + createTime?: string | null; + updateTime?: string | null; + } + + interface SaveUserSignatureParams { + userId: string; + fileId: string; + fileName?: string | null; + status?: number | null; + remark?: string | null; + } + /** * menu type * diff --git a/src/typings/api/work-report.d.ts b/src/typings/api/work-report.d.ts index ca17469..0d06641 100644 --- a/src/typings/api/work-report.d.ts +++ b/src/typings/api/work-report.d.ts @@ -189,6 +189,8 @@ declare namespace Api { periodEndDate: string; statusCode: Common.WorkReportStatusCode | string; statusName: string; + signOffStatus?: string | null; + signOffStatusName?: string | null; allowEdit: boolean; terminal: boolean; totalWorkHours?: number | string | null; @@ -204,6 +206,7 @@ declare namespace Api { type MonthlyReportSearchParams = Common.WorkReportBaseSearchParams & { reporterIds?: string[] | null; + signOffStatus?: string | null; }; interface MonthlyReportSaveParams { @@ -245,10 +248,22 @@ declare namespace Api { improvementSuggestion?: string | null; performanceResult?: string | null; employeeSignName?: string | null; + employeeSignatureFileId?: string | null; employeeSignedDate?: string | null; supervisorSignName?: string | null; + supervisorSignatureFileId?: string | null; supervisorSignedDate?: string | null; } + + interface MonthlyReportSignOffConfirmParams { + meetingDate: string; + employeeSignedDate?: string | null; + } + + interface MonthlyReportSignOffDetail { + report: MonthlyReport; + latestApprovalRecord: MonthlyReportApprovalRecord | null; + } } namespace Project { diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts index bf60d36..62d6202 100644 --- a/src/typings/components.d.ts +++ b/src/typings/components.d.ts @@ -130,11 +130,13 @@ declare module 'vue' { IconMdiFolderOpen: typeof import('~icons/mdi/folder-open')['default'] IconMdiFolderOutline: typeof import('~icons/mdi/folder-outline')['default'] IconMdiFolderPlusOutline: typeof import('~icons/mdi/folder-plus-outline')['default'] + IconMdiFolderZipOutline: typeof import('~icons/mdi/folder-zip-outline')['default'] IconMdiInboxMultipleOutline: typeof import('~icons/mdi/inbox-multiple-outline')['default'] IconMdiInformationOutline: typeof import('~icons/mdi/information-outline')['default'] IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default'] IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default'] IconMdiLinkVariant: typeof import('~icons/mdi/link-variant')['default'] + IconMdiPenCheckOutline: typeof import('~icons/mdi/pen-check-outline')['default'] IconMdiPencilOutline: typeof import('~icons/mdi/pencil-outline')['default'] IconMdiPlus: typeof import('~icons/mdi/plus')['default'] IconMdiRefresh: typeof import('~icons/mdi/refresh')['default'] diff --git a/src/views/personal-center/my-performance/index.vue b/src/views/personal-center/my-performance/index.vue index 5778386..8d61773 100644 --- a/src/views/personal-center/my-performance/index.vue +++ b/src/views/personal-center/my-performance/index.vue @@ -34,6 +34,7 @@ import PerformanceActionDialog from './modules/performance-action-dialog.vue'; import PerformanceExcelEditorDrawer from './modules/performance-excel-editor-drawer.vue'; import PerformanceRecordDialog from './modules/performance-record-dialog.vue'; import PerformanceSearch from './modules/performance-search.vue'; +import PerformanceSignOffDialog from './modules/performance-sign-off-dialog.vue'; import PerformanceSummary from './modules/performance-summary.vue'; import PerformanceTemplateDialog from './modules/performance-template-dialog.vue'; import { @@ -116,6 +117,7 @@ const excelMode = ref<'view' | 'edit' | 'create'>('view'); const actionVisible = ref(false); const actionType = ref<'confirm' | 'reject'>('confirm'); const recordVisible = ref(false); +const signOffVisible = ref(false); const exporting = ref(false); const currentUserId = computed(() => authStore.userInfo.userId || ''); @@ -138,6 +140,9 @@ const canUpdate = computed(() => hasAuth(PerformancePermission.SheetUpdate)); const canDelete = computed(() => hasAuth(PerformancePermission.SheetDelete)); const canConfirm = computed(() => hasAuth(PerformancePermission.SheetConfirm)); const canReject = computed(() => hasAuth(PerformancePermission.SheetReject)); +const canSignOff = computed( + () => hasAuth(PerformancePermission.SheetImport) && hasAuth(PerformancePermission.SheetSignOff) +); const canExport = computed(() => hasAuth(PerformancePermission.SheetExport)); const canManageTemplate = computed( () => hasAuth(PerformancePermission.TemplateQuery) || hasAuth(PerformancePermission.TemplateUpdate) @@ -303,6 +308,21 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination, r }); const totalCount = computed(() => mobilePagination.value.total || data.value.length); +const currentActionEmployeeIds = computed(() => { + if (!isTeamMode.value) { + return undefined; + } + + return currentEmployeeIds.value ?? undefined; +}); +const signOffInitialPeriodMonth = computed(() => formattedPeriodMonthEnd.value || createDefaultPeriodMonth()); + +function canHandlePersonalSheet(row: Api.Performance.Sheet.Sheet) { + return ( + row.statusCode === 'sent' && + ['pending_employee_confirm', 'employee_rejected'].includes(String(row.signOffStatus || '')) + ); +} function getRowActions(row: Api.Performance.Sheet.Sheet): BusinessTableAction[] { const actions: BusinessTableAction[] = [ @@ -357,7 +377,7 @@ function getRowActions(row: Api.Performance.Sheet.Sheet): BusinessTableAction[] onClick: () => handleDelete(row) }); } - } else if (row.statusCode === 'sent') { + } else if (canHandlePersonalSheet(row)) { if (canConfirm.value) { actions.push({ key: 'confirm', @@ -419,6 +439,10 @@ function handleSelectionChange(rows: Api.Performance.Sheet.Sheet[]) { selectedRows.value = rows; } +function openSignOffDialog() { + signOffVisible.value = true; +} + function openExcel(row: Api.Performance.Sheet.Sheet, mode: 'view' | 'edit') { currentRow.value = row; excelMode.value = mode; @@ -743,6 +767,12 @@ onMounted(async () => { + + + 下签 + diff --git a/src/views/personal-center/my-performance/modules/performance-action-dialog.vue b/src/views/personal-center/my-performance/modules/performance-action-dialog.vue index 2cfb31d..318c340 100644 --- a/src/views/personal-center/my-performance/modules/performance-action-dialog.vue +++ b/src/views/personal-center/my-performance/modules/performance-action-dialog.vue @@ -41,15 +41,15 @@ const form = reactive<{ const isReject = computed(() => form.actionType === 'reject'); const title = computed(() => { - if (props.selectableActionType) return '绩效审批'; + if (props.selectableActionType) return '绩效确认'; return isReject.value ? '退回绩效表' : '确认绩效表'; }); const confirmText = computed(() => { if (props.selectableActionType) return '确认提交'; return isReject.value ? '确认退回' : '确认'; }); -const opinionLabel = computed(() => (isReject.value ? '退回原因' : '审批意见')); -const opinionPlaceholder = computed(() => (isReject.value ? `请输入${opinionLabel.value}` : '可填写审批意见')); +const opinionLabel = computed(() => (isReject.value ? '退回原因' : '意见')); +const opinionPlaceholder = computed(() => (isReject.value ? `请输入${opinionLabel.value}` : '可填写意见')); const rules = computed(() => ({ reason: isReject.value ? [createRequiredRule('请输入退回原因')] : [] })); @@ -104,7 +104,7 @@ watch(visible, isVisible => {
- +
diff --git a/src/views/personal-center/my-performance/modules/performance-shared.ts b/src/views/personal-center/my-performance/modules/performance-shared.ts index c3b7f37..6f97d16 100644 --- a/src/views/personal-center/my-performance/modules/performance-shared.ts +++ b/src/views/personal-center/my-performance/modules/performance-shared.ts @@ -16,6 +16,8 @@ export const PerformancePermission = { SheetDelete: 'project:performance-sheet:delete', SheetConfirm: 'project:performance-sheet:confirm', SheetReject: 'project:performance-sheet:reject', + SheetImport: 'project:performance-sheet:import', + SheetSignOff: 'project:performance-sheet:sign-off', SheetExport: 'project:performance-sheet:export', TeamDashboard: 'project:performance-sheet:team-dashboard' } as const; diff --git a/src/views/personal-center/my-performance/modules/performance-sign-off-dialog.vue b/src/views/personal-center/my-performance/modules/performance-sign-off-dialog.vue new file mode 100644 index 0000000..7764026 --- /dev/null +++ b/src/views/personal-center/my-performance/modules/performance-sign-off-dialog.vue @@ -0,0 +1,300 @@ + + + + + diff --git a/src/views/personal-center/work-report/monthly/modules/approval-page.vue b/src/views/personal-center/work-report/monthly/modules/approval-page.vue index 96c7a29..4922e92 100644 --- a/src/views/personal-center/work-report/monthly/modules/approval-page.vue +++ b/src/views/personal-center/work-report/monthly/modules/approval-page.vue @@ -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(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(() => { + 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(() => ({ 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(
- +
@@ -731,10 +768,23 @@ watch(