feat(performance): 新增绩效下签功能支持

- 在月报审批页面增加 sign-off 场景支持
- 添加绩效下签对话框组件实现批量下签功能
- 增加导入绩效压缩包和直接下签两种操作模式
- 实现电子签名预览和日期格式化显示功能
- 更新绩效权限配置增加下签相关权限
- 优化审批对话框标题和按钮文案显示
- 添加导入结果提示和文件验证功能
- 实现团队模式下的下签操作入口
- 完善绩效表格状态和操作权限判断逻辑
This commit is contained in:
dk
2026-07-19 18:21:14 +08:00
parent c2f9c922cf
commit 08fff8bbc5
17 changed files with 1523 additions and 116 deletions

View File

@@ -44,6 +44,15 @@ type SheetPageResponse = {
list: SheetResponse[];
};
type ImportZipResultResponse = Omit<Api.Performance.Sheet.ImportZipResult, 'messages'> & {
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<Api.Performance.Sheet.StatusLog, 'id' | 'sheetId' | 'operatorUserId'> & {
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<StringIdResponse>, 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<ImportZipResultResponse>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/import-zip`,
method: 'post',
data: formData
});
return mapServiceResult(result as ServiceRequestResult<ImportZipResultResponse>, 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<StringIdResponse>({
...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<StringIdResponse>, normalizeStringId);
}
export function updatePerformanceSheetExcel(id: string, data: Api.Performance.Sheet.ExcelUpdateParams) {
return request<boolean>({
...safeJsonRequestConfig,

View File

@@ -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<Api.SystemManage.MySubordinateTreeNode
children?: MySubordinateTreeNodeResponse[] | null;
};
type UserSignatureResponse = Omit<Api.SystemManage.UserSignature, 'id' | 'userId' | 'fileId'> & {
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<UserSignatureResponse | null>({
...safeJsonRequestConfig,
url: `${USER_SIGNATURE_PREFIX}/get`,
method: 'get',
params: { userId }
});
return mapServiceResult(result as ServiceRequestResult<UserSignatureResponse | null>, data =>
data ? normalizeUserSignature(data) : null
);
}
export function fetchSaveUserSignature(data: Api.SystemManage.SaveUserSignatureParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${USER_SIGNATURE_PREFIX}/save`,
method: 'post',
data
});
}
export function fetchDeleteUserSignature(userId: string) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${USER_SIGNATURE_PREFIX}/delete`,
method: 'delete',
params: { userId }
});
}
export async function fetchGetMyUserSignature() {
const result = await request<UserSignatureResponse | null>({
...safeJsonRequestConfig,
url: `${USER_SIGNATURE_PREFIX}/my`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<UserSignatureResponse | null>, data =>
data ? normalizeUserSignature(data) : null
);
}
// ==================== 用户管理链路相关 API ====================
/**
* 获取用户管理链路树形结构

View File

@@ -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<Api.WorkReport.Project.ProjectReportOwnerProjectOption, 'id'> & {
@@ -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<PageResponse<MonthlyReportResponse>>({
...safeJsonRequestConfig,
url: query ? `${MONTHLY_PREFIX}/sign-off-page?${query}` : `${MONTHLY_PREFIX}/sign-off-page`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<PageResponse<MonthlyReportResponse>>, data =>
mapPage(data, normalizeMonthlyReport)
);
}
export async function fetchGetMonthlyReportDetail(id: string) {
const result = await request<MonthlyReportResponse>({
...safeJsonRequestConfig,
@@ -799,6 +827,34 @@ export async function fetchGetMonthlyReportApprovalRecords(id: string) {
);
}
export async function fetchGetMonthlyReportSignOffDetail(id: string) {
const result = await request<MonthlySignOffDetailResponse>({
...safeJsonRequestConfig,
url: `${MONTHLY_PREFIX}/${id}/sign-off-detail`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<MonthlySignOffDetailResponse>, data => ({
report: normalizeMonthlyReport(data.report),
latestApprovalRecord: data.latestApprovalRecord ? normalizeMonthlyApprovalRecord(data.latestApprovalRecord) : null
}));
}
export function fetchConfirmMonthlyReportSignOff(
id: string,
data: Api.WorkReport.Monthly.MonthlyReportSignOffConfirmParams
) {
return request<boolean>({
...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<Blob, 'blob'>({