feat(新增加班申请功能): 新增申请功能,可在工作台进行审核。

fix(dict_data): 在字典数据新增、编辑时可以操作颜色类型字段(color_type)。
This commit is contained in:
dk
2026-06-01 21:37:08 +08:00
parent b2da882b31
commit d3d0830820
29 changed files with 1966 additions and 23 deletions

View File

@@ -1,5 +1,6 @@
import { SYSTEM_SERVICE_PREFIX } from '@/constants/service';
import { request } from '../request';
import { type ServiceRequestResult, mapServiceResult } from './shared';
const DICT_TYPE_PREFIX = `${SYSTEM_SERVICE_PREFIX}/dict-type`;
const DICT_DATA_PREFIX = `${SYSTEM_SERVICE_PREFIX}/dict-data`;
@@ -15,6 +16,52 @@ function createBatchDeleteQuery(ids: number[]) {
return query.toString();
}
type DictDataResponse = Omit<Api.Dict.DictData, 'colorType'> & {
colorType?: string | null;
color_type?: string | null;
};
type DictDataPageResponse = Omit<Api.Dict.PageResult<Api.Dict.DictData>, 'list'> & {
list: DictDataResponse[];
};
type FrontendDictDataResponse = Omit<Api.Dict.FrontendDictData, 'colorType'> & {
colorType?: string | null;
color_type?: string | null;
};
type FrontendDictCacheResponse = Record<string, FrontendDictDataResponse[]>;
function normalizeColorType(value?: string | null) {
return value?.trim() || null;
}
function normalizeDictData(data: DictDataResponse): Api.Dict.DictData {
const { color_type: colorTypeFromSnakeCase, ...rest } = data;
return {
...rest,
colorType: normalizeColorType(data.colorType ?? colorTypeFromSnakeCase)
};
}
function normalizeFrontendDictData(data: FrontendDictDataResponse): Api.Dict.FrontendDictData {
const { color_type: colorTypeFromSnakeCase, ...rest } = data;
return {
...rest,
colorType: normalizeColorType(data.colorType ?? colorTypeFromSnakeCase)
};
}
function toSaveDictDataRequest(data: Api.Dict.SaveDictDataParams) {
return {
...data,
colorType: normalizeColorType(data.colorType),
remark: data.remark?.trim() || null
};
}
/** 获取字典类型分页 */
export function fetchGetDictTypePage(params?: Api.Dict.DictTypeSearchParams) {
return request<Api.Dict.PageResult<Api.Dict.DictType>>({
@@ -60,20 +107,40 @@ export function fetchBatchDeleteDictType(ids: number[]) {
}
/** 获取字典数据分页 */
export function fetchGetDictDataPage(params: Api.Dict.DictDataSearchParams) {
return request<Api.Dict.PageResult<Api.Dict.DictData>>({
export async function fetchGetDictDataPage(params: Api.Dict.DictDataSearchParams) {
const result = await request<DictDataPageResponse>({
url: `${DICT_DATA_PREFIX}/page`,
method: 'get',
params
});
if (result.error || !result.data) {
return result as unknown as Awaited<ReturnType<typeof request<Api.Dict.PageResult<Api.Dict.DictData>>>>;
}
return {
...result,
data: {
...result.data,
list: result.data.list.map(normalizeDictData)
}
};
}
/** 获取前端运行时字典缓存 */
export function fetchGetFrontendDictCache() {
return request<Api.Dict.FrontendDictCache>({
export async function fetchGetFrontendDictCache() {
const result = await request<FrontendDictCacheResponse>({
url: `${DICT_DATA_PREFIX}/frontend-cache`,
method: 'get'
});
return mapServiceResult(
result as ServiceRequestResult<FrontendDictCacheResponse>,
data =>
Object.fromEntries(
Object.entries(data).map(([dictType, list]) => [dictType, list.map(normalizeFrontendDictData)])
) as Api.Dict.FrontendDictCache
);
}
/** 创建字典数据 */
@@ -81,7 +148,7 @@ export function fetchCreateDictData(data: Api.Dict.SaveDictDataParams) {
return request<number>({
url: `${DICT_DATA_PREFIX}/create`,
method: 'post',
data
data: toSaveDictDataRequest(data)
});
}
@@ -90,7 +157,7 @@ export function fetchUpdateDictData(data: { id: number } & Api.Dict.SaveDictData
return request<boolean>({
url: `${DICT_DATA_PREFIX}/update`,
method: 'put',
data
data: toSaveDictDataRequest(data)
});
}
@@ -112,9 +179,14 @@ export function fetchBatchDeleteDictData(ids: number[]) {
}
/** 通过岗位编码获取该字典的所有字典数据 */
export function fetchGetDictDataByCode(code: string) {
return request<Api.Dict.PageResult<Api.Dict.DictData>>({
export async function fetchGetDictDataByCode(code: string) {
const result = await request<DictDataPageResponse>({
url: `${DICT_DATA_PREFIX}/code?code=${code}`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<DictDataPageResponse>, data => ({
...data,
list: data.list.map(normalizeDictData)
}));
}

View File

@@ -3,6 +3,7 @@ export * from './dict';
export * from './file';
export * from './infra';
export * from './object-context';
export * from './overtime-application';
export * from './personal-item';
export * from './product';
export * from './project';

View File

@@ -0,0 +1,280 @@
import { WEB_SERVICE_PREFIX } from '@/constants/service';
import { request } from '../request';
import { type ProjectLocalDateValue, normalizeProjectLocalDate } from './project-shared';
import {
type ServiceRequestResult,
mapServiceResult,
normalizeNullableStringId,
normalizeStringId,
safeJsonRequestConfig
} from './shared';
const OVERTIME_APPLICATION_PREFIX = `${WEB_SERVICE_PREFIX}/project/overtime-applications`;
type StringIdResponse = string | number;
type OvertimeApplicationResponse = Omit<
Api.OvertimeApplication.OvertimeApplication,
'id' | 'applicantId' | 'approverId' | 'overtimeDate' | 'allowEdit' | 'terminal'
> & {
id: StringIdResponse;
applicantId: StringIdResponse;
approverId: StringIdResponse;
overtimeDate: ProjectLocalDateValue;
allowEdit?: boolean | number | string | null;
terminal?: boolean | number | string | null;
};
type OvertimeApplicationPageResponse = Omit<Api.OvertimeApplication.OvertimeApplicationPageResult, 'total' | 'list'> & {
total: number | string;
list: OvertimeApplicationResponse[];
};
type OvertimeApplicationStatusLogResponse = Omit<
Api.OvertimeApplication.OvertimeApplicationStatusLog,
'id' | 'applicationId' | 'operatorUserId' | 'overtimeDateSnapshot'
> & {
id: StringIdResponse;
applicationId: StringIdResponse;
operatorUserId: StringIdResponse;
overtimeDateSnapshot: ProjectLocalDateValue;
};
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(total: number | string) {
const value = Number(total);
return Number.isFinite(value) ? Math.max(0, value) : 0;
}
function normalizeOvertimeApplication(
response: OvertimeApplicationResponse
): Api.OvertimeApplication.OvertimeApplication {
return {
...response,
id: normalizeStringId(response.id),
applicantId: normalizeStringId(response.applicantId),
approverId: normalizeStringId(response.approverId),
overtimeDate: normalizeProjectLocalDate(response.overtimeDate) ?? '',
statusName: response.statusName || response.statusCode,
allowEdit: normalizeBooleanFlag(response.allowEdit),
terminal: normalizeBooleanFlag(response.terminal),
approvalComment: response.approvalComment ?? null,
approvalTime: response.approvalTime ?? null
};
}
function normalizeStatusLog(
response: OvertimeApplicationStatusLogResponse
): Api.OvertimeApplication.OvertimeApplicationStatusLog {
return {
...response,
id: normalizeStringId(response.id),
applicationId: normalizeStringId(response.applicationId),
operatorUserId: normalizeStringId(response.operatorUserId),
overtimeDateSnapshot: normalizeProjectLocalDate(response.overtimeDateSnapshot) ?? '',
fromStatus: normalizeNullableStringId(response.fromStatus),
reason: response.reason ?? null,
remark: response.remark ?? null
};
}
function createPageQuery(params: Api.OvertimeApplication.OvertimeApplicationSearchParams = {}) {
const query = new URLSearchParams();
query.append('pageNo', String(params.pageNo ?? 1));
query.append('pageSize', String(params.pageSize ?? 10));
if (params.keyword) {
query.append('keyword', params.keyword);
}
if (params.applicantName) {
query.append('applicantName', params.applicantName);
}
if (params.approverId) {
query.append('approverId', params.approverId);
}
if (params.approverName) {
query.append('approverName', params.approverName);
}
if (params.statusCode) {
query.append('statusCode', params.statusCode);
}
params.overtimeDate?.forEach(item => {
if (item) {
query.append('overtimeDate', item);
}
});
params.createTime?.forEach(item => {
if (item) {
query.append('createTime', item);
}
});
return query.toString();
}
function toSaveRequest(data: Api.OvertimeApplication.SaveOvertimeApplicationParams) {
return {
overtimeDate: data.overtimeDate,
overtimeDuration: data.overtimeDuration,
overtimeReason: data.overtimeReason.trim(),
overtimeContent: data.overtimeContent.trim(),
approverId: data.approverId
};
}
function toStatusActionRequest(data: Api.OvertimeApplication.StatusActionParams = {}) {
return {
reason: data.reason?.trim() || undefined
};
}
export async function fetchGetOvertimeApplicationPage(
params: Api.OvertimeApplication.OvertimeApplicationSearchParams = {}
) {
const query = createPageQuery(params);
const result = await request<OvertimeApplicationPageResponse>({
...safeJsonRequestConfig,
url: query ? `${OVERTIME_APPLICATION_PREFIX}/page?${query}` : `${OVERTIME_APPLICATION_PREFIX}/page`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<OvertimeApplicationPageResponse>, data => ({
total: normalizeTotal(data.total),
list: data.list.map(normalizeOvertimeApplication)
}));
}
export async function fetchGetOvertimeApplicationApprovalPage(
params: Api.OvertimeApplication.OvertimeApplicationSearchParams = {}
) {
const query = createPageQuery(params);
const result = await request<OvertimeApplicationPageResponse>({
...safeJsonRequestConfig,
url: query
? `${OVERTIME_APPLICATION_PREFIX}/approval-page?${query}`
: `${OVERTIME_APPLICATION_PREFIX}/approval-page`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<OvertimeApplicationPageResponse>, data => ({
total: normalizeTotal(data.total),
list: data.list.map(normalizeOvertimeApplication)
}));
}
export async function fetchGetOvertimeApplicationDetail(id: string) {
const result = await request<OvertimeApplicationResponse>({
...safeJsonRequestConfig,
url: `${OVERTIME_APPLICATION_PREFIX}/${id}`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<OvertimeApplicationResponse>, normalizeOvertimeApplication);
}
export async function fetchCreateOvertimeApplication(data: Api.OvertimeApplication.SaveOvertimeApplicationParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: OVERTIME_APPLICATION_PREFIX,
method: 'post',
data: toSaveRequest(data)
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
export function fetchUpdateRejectedOvertimeApplication(
id: string,
data: Api.OvertimeApplication.SaveOvertimeApplicationParams
) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${OVERTIME_APPLICATION_PREFIX}/${id}`,
method: 'put',
data: toSaveRequest(data)
});
}
export function fetchApproveOvertimeApplication(id: string, data: Api.OvertimeApplication.StatusActionParams = {}) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${OVERTIME_APPLICATION_PREFIX}/${id}/approve`,
method: 'post',
data: toStatusActionRequest(data)
});
}
export function fetchRejectOvertimeApplication(id: string, data: Api.OvertimeApplication.StatusActionParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${OVERTIME_APPLICATION_PREFIX}/${id}/reject`,
method: 'post',
data: toStatusActionRequest(data)
});
}
export function fetchCancelOvertimeApplication(id: string, data: Api.OvertimeApplication.StatusActionParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${OVERTIME_APPLICATION_PREFIX}/${id}/cancel`,
method: 'post',
data: toStatusActionRequest(data)
});
}
export function fetchDeleteOvertimeApplication(id: string) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${OVERTIME_APPLICATION_PREFIX}/${id}`,
method: 'delete'
});
}
export async function fetchGetOvertimeApplicationStatusLogs(id: string) {
const result = await request<OvertimeApplicationStatusLogResponse[]>({
...safeJsonRequestConfig,
url: `${OVERTIME_APPLICATION_PREFIX}/${id}/status-logs`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<OvertimeApplicationStatusLogResponse[]>, data =>
data.map(normalizeStatusLog)
);
}
export function fetchExportOvertimeApplications(params: Api.OvertimeApplication.OvertimeApplicationSearchParams = {}) {
const query = createPageQuery(params);
return request<Blob, 'blob'>({
url: query ? `${OVERTIME_APPLICATION_PREFIX}/export?${query}` : `${OVERTIME_APPLICATION_PREFIX}/export`,
method: 'get',
responseType: 'blob'
});
}

View File

@@ -455,6 +455,19 @@ export async function fetchGetUserSimpleList() {
);
}
/** 获取当前登录人的直属上级 */
export async function fetchGetLoginUserDirectManager() {
return request<UserSimpleResponse | null>({
...safeJsonRequestConfig,
url: `${USER_PREFIX}/profile/direct-manager`,
method: 'get'
}).then(result =>
mapServiceResult(result as ServiceRequestResult<UserSimpleResponse | null>, data =>
data ? normalizeUserSimple(data) : null
)
);
}
/** 获取用户分页 */
export function fetchGetUserPage(params?: Api.SystemManage.UserSearchParams) {
return request<Api.SystemManage.UserList>({