feat(我的绩效): 开发我的绩效功能。

fix(加班申请、工作报告): 重构加班申请在审批时的样式,工作报告在新增时的对话框、报告详情页的样式。
This commit is contained in:
dk
2026-06-21 18:22:44 +08:00
parent cd64cf42cc
commit 9a5845708d
35 changed files with 4211 additions and 924 deletions

View File

@@ -6,6 +6,7 @@ export * from './notice';
export * from './notify-message';
export * from './object-context';
export * from './overtime-application';
export * from './performance';
export * from './personal-item';
export * from './product';
export * from './project';

View File

@@ -0,0 +1,501 @@
import dayjs from 'dayjs';
import { WEB_SERVICE_PREFIX } from '@/constants/service';
import { request } from '../request';
import {
type ServiceRequestResult,
mapServiceResult,
normalizeNullableStringId,
normalizeStringId,
safeJsonRequestConfig
} from './shared';
const TEMPLATE_PREFIX = `${WEB_SERVICE_PREFIX}/project/performance-templates`;
const SHEET_PREFIX = `${WEB_SERVICE_PREFIX}/project/performance-sheets`;
const TEAM_PREFIX = `${SHEET_PREFIX}/team`;
type StringIdResponse = string | number;
type TemplateResponse = Omit<Api.Performance.Template.Template, 'id' | 'fileId' | 'uploadUserId' | 'activeFlag'> & {
id: StringIdResponse;
fileId: StringIdResponse;
uploadUserId: StringIdResponse;
activeFlag?: boolean | number | string | null;
};
type TemplatePageResponse = {
total: number | string;
list: TemplateResponse[];
};
type SheetResponse = Omit<
Api.Performance.Sheet.Sheet,
'id' | 'employeeId' | 'employeeDeptId' | 'managerId' | 'templateId' | 'fileId'
> & {
id: StringIdResponse;
employeeId: StringIdResponse;
employeeDeptId: StringIdResponse;
managerId: StringIdResponse;
templateId: StringIdResponse;
fileId?: StringIdResponse | null;
};
type SheetPageResponse = {
total: number | string;
list: SheetResponse[];
};
type StatusLogResponse = Omit<Api.Performance.Sheet.StatusLog, 'id' | 'sheetId' | 'operatorUserId'> & {
id: StringIdResponse;
sheetId: StringIdResponse;
operatorUserId: StringIdResponse;
};
type ResponseRecordResponse = Omit<
Api.Performance.Sheet.ResponseRecord,
'id' | 'sheetId' | 'statusLogId' | 'responderUserId'
> & {
id: StringIdResponse;
sheetId: StringIdResponse;
statusLogId: StringIdResponse;
responderUserId: StringIdResponse;
};
type MonthlyResultResponse = Omit<Api.Performance.Sheet.MonthlyResult, 'sheetId' | 'employeeId'> & {
sheetId?: StringIdResponse | null;
employeeId: StringIdResponse;
};
type TeamSummaryResponse = Omit<
Api.Performance.Team.Summary,
| 'totalSheetCount'
| 'pendingSendCount'
| 'pendingConfirmCount'
| 'pendingSendUsers'
| 'pendingConfirmUsers'
| 'deptOrgAverages'
> & {
totalSheetCount?: number | string | null;
pendingSendCount?: number | string | null;
pendingConfirmCount?: number | string | null;
pendingSendUsers?: Array<
Omit<Api.Performance.Team.PendingSendUser, 'userId' | 'managerUserId' | 'sheetId'> & {
userId: StringIdResponse;
managerUserId: StringIdResponse;
sheetId?: StringIdResponse | null;
}
> | null;
pendingConfirmUsers?: Array<
Omit<Api.Performance.Team.PendingConfirmUser, 'userId' | 'sheetId'> & {
userId: StringIdResponse;
sheetId: StringIdResponse;
}
> | null;
deptOrgAverages?: Array<
Omit<Api.Performance.Team.DeptOrgAverage, 'deptId' | 'confirmedCount'> & {
deptId: StringIdResponse;
confirmedCount?: number | string | null;
}
> | null;
};
function normalizeBooleanFlag(value: boolean | number | string | null | undefined) {
if (typeof value === 'boolean') return value;
if (typeof value === 'number') return value === 1;
if (typeof value === 'string') {
const normalized = value.trim().toLowerCase();
return !['', '0', 'false', 'n', 'no'].includes(normalized);
}
return false;
}
function normalizeTotal(value: number | string | null | undefined) {
const total = Number(value ?? 0);
return Number.isFinite(total) ? Math.max(0, total) : 0;
}
function normalizeTemplate(response: TemplateResponse): Api.Performance.Template.Template {
return {
...response,
id: normalizeStringId(response.id),
fileId: normalizeStringId(response.fileId),
uploadUserId: normalizeStringId(response.uploadUserId),
activeFlag: normalizeBooleanFlag(response.activeFlag),
remark: response.remark ?? null,
scoreCellMapping: response.scoreCellMapping ?? null
};
}
function normalizeSheet(response: SheetResponse): Api.Performance.Sheet.Sheet {
return {
...response,
id: normalizeStringId(response.id),
employeeId: normalizeStringId(response.employeeId),
employeeDeptId: normalizeStringId(response.employeeDeptId),
managerId: normalizeStringId(response.managerId),
templateId: normalizeStringId(response.templateId),
fileId: normalizeNullableStringId(response.fileId),
fileName: response.fileName ?? null,
statusName: response.statusName || response.statusCode,
actualScoreTotal: response.actualScoreTotal ?? null,
baseScoreTotal: response.baseScoreTotal ?? null,
extraScoreTotal: response.extraScoreTotal ?? null,
sentTime: response.sentTime ?? null,
confirmedTime: response.confirmedTime ?? null,
rejectedTime: response.rejectedTime ?? null,
lastStatusReason: response.lastStatusReason ?? null,
createTime: response.createTime ?? null,
updateTime: response.updateTime ?? null
};
}
function normalizeStatusLog(response: StatusLogResponse): Api.Performance.Sheet.StatusLog {
return {
...response,
id: normalizeStringId(response.id),
sheetId: normalizeStringId(response.sheetId),
operatorUserId: normalizeStringId(response.operatorUserId),
reason: response.reason ?? null,
remark: response.remark ?? null
};
}
function normalizeResponseRecord(response: ResponseRecordResponse): Api.Performance.Sheet.ResponseRecord {
return {
...response,
id: normalizeStringId(response.id),
sheetId: normalizeStringId(response.sheetId),
statusLogId: normalizeStringId(response.statusLogId),
responderUserId: normalizeStringId(response.responderUserId),
opinion: response.opinion ?? null
};
}
function normalizeMonthlyResult(response: MonthlyResultResponse): Api.Performance.Sheet.MonthlyResult {
return {
...response,
sheetId: normalizeNullableStringId(response.sheetId),
employeeId: normalizeStringId(response.employeeId),
actualScoreTotal: response.actualScoreTotal ?? null,
baseScoreTotal: response.baseScoreTotal ?? null,
extraScoreTotal: response.extraScoreTotal ?? null,
statusCode: response.statusCode ?? null
};
}
function normalizeTeamSummary(response: TeamSummaryResponse): Api.Performance.Team.Summary {
return {
...response,
totalSheetCount: normalizeTotal(response.totalSheetCount),
pendingSendCount: normalizeTotal(response.pendingSendCount),
pendingConfirmCount: normalizeTotal(response.pendingConfirmCount),
pendingSendUsers: (response.pendingSendUsers || []).map(item => ({
...item,
userId: normalizeStringId(item.userId),
managerUserId: normalizeStringId(item.managerUserId),
sheetId: normalizeNullableStringId(item.sheetId),
statusCode: item.statusCode ?? null
})),
pendingConfirmUsers: (response.pendingConfirmUsers || []).map(item => ({
...item,
userId: normalizeStringId(item.userId),
sheetId: normalizeStringId(item.sheetId),
sentTime: item.sentTime ?? null
})),
deptOrgAverages: (response.deptOrgAverages || []).map(item => ({
...item,
deptId: normalizeStringId(item.deptId),
averageScore: item.averageScore ?? null,
confirmedCount: normalizeTotal(item.confirmedCount)
}))
};
}
function appendValue(query: URLSearchParams, key: string, value: unknown) {
if (value === null || value === undefined || value === '') return;
if (Array.isArray(value)) {
if (!value.length) {
query.append(key, '');
return;
}
value.forEach(item => appendValue(query, key, item));
return;
}
query.append(key, String(value));
}
export function formatToYYYYMM(value?: string | null) {
if (!value) return '';
const d = dayjs(value);
return d.isValid() ? d.format('YYYY-MM') : value.slice(0, 7);
}
function createSheetQuery(params: Api.Performance.Sheet.SearchParams = {}) {
const query = new URLSearchParams();
query.append('pageNo', String(params.pageNo ?? 1));
query.append('pageSize', String(params.pageSize ?? 10));
appendValue(query, 'employeeIds', params.employeeIds);
// 将 periodMonthRange 拆为 periodMonthStart / periodMonthEnd
if (params.periodMonthRange?.length === 2) {
appendValue(query, 'periodMonthStart', formatToYYYYMM(params.periodMonthRange[0]));
appendValue(query, 'periodMonthEnd', formatToYYYYMM(params.periodMonthRange[1]));
}
// employeeId 单选追加到 employeeIds
if (params.employeeId) {
query.append('employeeIds', params.employeeId);
}
appendValue(query, 'employeeDeptId', params.employeeDeptId);
appendValue(query, 'managerName', params.managerName);
appendValue(query, 'statusCode', params.statusCode);
return query.toString();
}
function createTemplateQuery(params: Api.Performance.Template.SearchParams = {}) {
const query = new URLSearchParams();
query.append('pageNo', String(params.pageNo ?? 1));
query.append('pageSize', String(params.pageSize ?? 10));
appendValue(query, 'templateName', params.templateName);
appendValue(query, 'activeFlag', params.activeFlag);
return query.toString();
}
export async function fetchPerformanceTemplateCurrent() {
const result = await request<TemplateResponse | null>({
...safeJsonRequestConfig,
url: `${TEMPLATE_PREFIX}/current`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<TemplateResponse | null>, data =>
data ? normalizeTemplate(data) : null
);
}
export async function fetchPerformanceTemplatePage(params: Api.Performance.Template.SearchParams = {}) {
const query = createTemplateQuery(params);
const result = await request<TemplatePageResponse>({
...safeJsonRequestConfig,
url: query ? `${TEMPLATE_PREFIX}/page?${query}` : `${TEMPLATE_PREFIX}/page`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<TemplatePageResponse>, data => ({
total: normalizeTotal(data.total),
list: data.list.map(normalizeTemplate)
}));
}
export async function uploadPerformanceTemplate(data: Api.Performance.Template.UploadParams) {
const result = await request<StringIdResponse>({
...safeJsonRequestConfig,
url: `${TEMPLATE_PREFIX}/upload`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<StringIdResponse>, normalizeStringId);
}
export function activatePerformanceTemplate(id: string) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${TEMPLATE_PREFIX}/${id}/activate`,
method: 'post'
});
}
export async function fetchPerformanceSheetPage(params: Api.Performance.Sheet.SearchParams = {}) {
const query = createSheetQuery(params);
const result = await request<SheetPageResponse>({
...safeJsonRequestConfig,
url: query ? `${SHEET_PREFIX}/page?${query}` : `${SHEET_PREFIX}/page`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<SheetPageResponse>, data => ({
total: normalizeTotal(data.total),
list: data.list.map(normalizeSheet)
}));
}
export async function fetchPerformanceSheet(id: string) {
const result = await request<SheetResponse>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/${id}`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<SheetResponse>, normalizeSheet);
}
export async function createPerformanceSheet(data: Api.Performance.Sheet.CreateParams) {
const result = await request<StringIdResponse>({
...safeJsonRequestConfig,
url: SHEET_PREFIX,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<StringIdResponse>, normalizeStringId);
}
export function updatePerformanceSheetExcel(id: string, data: Api.Performance.Sheet.ExcelUpdateParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/${id}/excel`,
method: 'put',
data
});
}
export function deletePerformanceSheet(id: string) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/${id}`,
method: 'delete'
});
}
export function sendPerformanceSheet(id: string) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/${id}/send`,
method: 'post'
});
}
export function resendPerformanceSheet(id: string) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/${id}/resend`,
method: 'post'
});
}
export function confirmPerformanceSheet(id: string, data: Api.Performance.Sheet.StatusActionParams = {}) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/${id}/confirm`,
method: 'post',
data
});
}
export function rejectPerformanceSheet(id: string, data: Api.Performance.Sheet.StatusActionParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/${id}/reject`,
method: 'post',
data
});
}
export function downloadPerformanceSheet(id: string) {
return request<Blob, 'blob'>({
url: `${SHEET_PREFIX}/${id}/download`,
method: 'get',
responseType: 'blob'
});
}
export function batchDownloadPerformanceSheets(data: Api.Performance.Sheet.BatchDownloadParams) {
return request<Blob, 'blob'>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/batch-download`,
method: 'post',
data,
responseType: 'blob'
});
}
export function exportPerformanceSheets(params: Api.Performance.Sheet.SearchParams = {}) {
const query = createSheetQuery(params);
return request<Blob, 'blob'>({
url: query ? `${SHEET_PREFIX}/export?${query}` : `${SHEET_PREFIX}/export`,
method: 'get',
responseType: 'blob'
});
}
export async function fetchPerformanceSheetStatusLogs(id: string) {
const result = await request<StatusLogResponse[]>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/${id}/status-logs`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<StatusLogResponse[]>, data => data.map(normalizeStatusLog));
}
export async function fetchPerformanceSheetResponseRecords(id: string) {
const result = await request<ResponseRecordResponse[]>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/${id}/response-records`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<ResponseRecordResponse[]>, data =>
data.map(normalizeResponseRecord)
);
}
export async function fetchPerformanceMonthlyResult(employeeId: string, periodMonth: string) {
const result = await request<MonthlyResultResponse | null>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/monthly-result`,
method: 'get',
params: { employeeId, periodMonth }
});
return mapServiceResult(result as ServiceRequestResult<MonthlyResultResponse | null>, data =>
data ? normalizeMonthlyResult(data) : null
);
}
export function fetchPerformanceSheetStatusDict() {
return request<Api.Performance.Sheet.StatusDict[]>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/status-dict`,
method: 'get'
});
}
export function fetchPerformanceSheetStatusTransitions() {
return request<Api.Performance.Sheet.StatusTransition[]>({
...safeJsonRequestConfig,
url: `${SHEET_PREFIX}/status-transitions`,
method: 'get'
});
}
export async function fetchTeamPerformanceSummary(params: Api.Performance.Team.SummaryParams = {}) {
const result = await request<TeamSummaryResponse>({
...safeJsonRequestConfig,
url: `${TEAM_PREFIX}/summary`,
method: 'get',
params
});
return mapServiceResult(result as ServiceRequestResult<TeamSummaryResponse>, normalizeTeamSummary);
}
export function remindTeamPerformance(data: Api.Performance.Team.RemindParams) {
return request<Api.Performance.Team.RemindResult>({
...safeJsonRequestConfig,
url: `${TEAM_PREFIX}/remind`,
method: 'post',
data
});
}