diff --git a/src/components/custom/subordinate-selector.vue b/src/components/custom/subordinate-selector.vue new file mode 100644 index 0000000..a3cf2a9 --- /dev/null +++ b/src/components/custom/subordinate-selector.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/src/components/custom/team-context-panel.vue b/src/components/custom/team-context-panel.vue new file mode 100644 index 0000000..ddc16ab --- /dev/null +++ b/src/components/custom/team-context-panel.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/src/service/api/overtime-application.ts b/src/service/api/overtime-application.ts index 7cc8efe..6e7f189 100644 --- a/src/service/api/overtime-application.ts +++ b/src/service/api/overtime-application.ts @@ -34,6 +34,8 @@ type OvertimeApplicationApprovalRecordResponse = Omit< auditorUserId: StringIdResponse; }; +type TeamOvertimeSummaryResponse = Api.OvertimeApplication.TeamOvertimeSummary; + function normalizeBooleanFlag(value: boolean | number | string | null | undefined) { if (typeof value === 'boolean') { return value; @@ -94,6 +96,18 @@ function createPageQuery(params: Api.OvertimeApplication.OvertimeApplicationSear query.append('pageNo', String(params.pageNo ?? 1)); query.append('pageSize', String(params.pageSize ?? 10)); + if (params.applicantIds !== null && params.applicantIds !== undefined) { + if (params.applicantIds.length) { + params.applicantIds.forEach(item => { + if (item) { + query.append('applicantIds', item); + } + }); + } else { + query.append('applicantIds', ''); + } + } + if (params.keyword) { query.append('keyword', params.keyword); } @@ -287,6 +301,17 @@ export async function fetchGetOvertimeApplicationStatusDict() { ); } +export async function fetchGetTeamOvertimeSummary(params: Api.OvertimeApplication.TeamOvertimeSummaryParams = {}) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${OVERTIME_APPLICATION_PREFIX}/team/summary`, + method: 'get', + params + }); + + return mapServiceResult(result as ServiceRequestResult, data => data); +} + export function fetchExportOvertimeApplications(params: Api.OvertimeApplication.OvertimeApplicationSearchParams = {}) { const query = createPageQuery(params); diff --git a/src/service/api/system-manage.ts b/src/service/api/system-manage.ts index 53ab872..615421d 100644 --- a/src/service/api/system-manage.ts +++ b/src/service/api/system-manage.ts @@ -118,6 +118,11 @@ type UserManagementRelationTreeResponse = Omit< children?: UserManagementRelationTreeResponse[] | null; }; +type MySubordinateTreeNodeResponse = Omit & { + userId: string | number; + children?: MySubordinateTreeNodeResponse[] | null; +}; + function normalizeUserSimple(user: UserSimpleResponse): Api.SystemManage.UserSimple { return { ...user, @@ -181,6 +186,14 @@ function normalizeUserManagementRelationTree( }; } +function normalizeMySubordinateTreeNode(node: MySubordinateTreeNodeResponse): Api.SystemManage.MySubordinateTreeNode { + return { + ...node, + userId: normalizeStringId(node.userId), + children: node.children?.map(normalizeMySubordinateTreeNode) ?? null + }; +} + /** 获取角色分页 */ export async function fetchGetRolePage(params?: Api.SystemManage.RoleSearchParams) { const query = createRolePageQuery(params); @@ -712,6 +725,17 @@ export async function fetchGetUserManagementRelationQuery(query: UserManagementR ); } +/** 获取当前登录用户下属树 */ +export async function fetchGetMySubordinateTree() { + return request({ + ...safeJsonRequestConfig, + url: `${USER_MANAGEMENT_RELATION_PREFIX}/my-subordinate-tree`, + method: 'get' + }).then(result => + mapServiceResult(result as ServiceRequestResult, normalizeMySubordinateTreeNode) + ); +} + /** * 获取用户管理链路详情 * diff --git a/src/service/api/work-report.ts b/src/service/api/work-report.ts index 10cd504..923e594 100644 --- a/src/service/api/work-report.ts +++ b/src/service/api/work-report.ts @@ -99,6 +99,14 @@ type ProjectOptionResponse = Omit & { + userId: StringIdResponse; +}; + +type TeamReportSummaryResponse = Omit & { + unsubmittedUsers?: TeamReportPendingUserResponse[] | null; +}; + function normalizeBooleanFlag(value: boolean | number | string | null | undefined) { if (typeof value === 'boolean') return value; if (typeof value === 'number') return value === 1; @@ -173,6 +181,21 @@ function appendArray(query: URLSearchParams, key: string, values?: Array appendValue(query, key, value)); } +function appendNullableArrayFlag( + query: URLSearchParams, + key: string, + values?: Array | null +) { + if (values === null || values === undefined) return; + + if (!values.length) { + query.append(key, ''); + return; + } + + appendArray(query, key, values); +} + function createBasePageQuery(params: Api.WorkReport.Common.WorkReportBaseSearchParams = {}) { const query = new URLSearchParams(); @@ -189,16 +212,20 @@ function createBasePageQuery(params: Api.WorkReport.Common.WorkReportBaseSearchP function createWeeklyPageQuery(params: Api.WorkReport.Weekly.WeeklyReportSearchParams = {}) { const query = createBasePageQuery(params); + appendNullableArrayFlag(query, 'reporterIds', params.reporterIds); appendValue(query, 'isBusinessTrip', params.isBusinessTrip); return query.toString(); } function createMonthlyPageQuery(params: Api.WorkReport.Monthly.MonthlyReportSearchParams = {}) { - return createBasePageQuery(params).toString(); + const query = createBasePageQuery(params); + appendNullableArrayFlag(query, 'reporterIds', params.reporterIds); + return query.toString(); } function createProjectPageQuery(params: Api.WorkReport.Project.ProjectReportSearchParams = {}) { const query = createBasePageQuery(params); + appendNullableArrayFlag(query, 'projectOwnerIds', params.projectOwnerIds); appendValue(query, 'projectId', params.projectId); appendValue(query, 'flag', params.flag); return query.toString(); @@ -338,6 +365,17 @@ function normalizeProjectOption( }; } +function normalizeTeamReportSummary(response: TeamReportSummaryResponse): Api.WorkReport.Common.TeamReportSummary { + return { + ...response, + unsubmittedUsers: + response.unsubmittedUsers?.map(item => ({ + ...item, + userId: normalizeStringId(item.userId) + })) ?? [] + }; +} + function mapPage(data: PageResponse, mapper: (item: TInput) => TOutput) { return { total: normalizeTotal(data.total), @@ -440,6 +478,34 @@ export async function fetchGetWorkReportStatusDict() { return mapServiceResult(result as ServiceRequestResult, data => data); } +export async function fetchGetTeamReportSummary(params: Api.WorkReport.Common.TeamReportSummaryParams) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${WORK_REPORT_PREFIX}/team/summary`, + method: 'get', + params + }); + + return mapServiceResult(result as ServiceRequestResult, normalizeTeamReportSummary); +} + +export async function fetchRemindTeamReport(data: Api.WorkReport.Common.TeamReportRemindParams) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${WORK_REPORT_PREFIX}/team/remind`, + method: 'post', + data: { + ...data, + userIds: data.userIds && data.userIds.length ? data.userIds : undefined + } + }); + + return mapServiceResult( + result as ServiceRequestResult, + payload => payload + ); +} + export async function fetchGetWeeklyReportPage(params: Api.WorkReport.Weekly.WeeklyReportSearchParams = {}) { const query = createWeeklyPageQuery(params); const result = await request>({ diff --git a/src/typings/api/overtime-application.d.ts b/src/typings/api/overtime-application.d.ts index 0231680..82f6d00 100644 --- a/src/typings/api/overtime-application.d.ts +++ b/src/typings/api/overtime-application.d.ts @@ -32,6 +32,7 @@ declare namespace Api { type OvertimeApplicationSearchParams = CommonType.RecordNullable< Pick & { + applicantIds: string[] | null; keyword: string; applicantName: string; approverId: string; @@ -95,5 +96,17 @@ declare namespace Api { terminalFlag: boolean; allowEdit: boolean; } + + interface TeamOvertimeSummaryParams { + month?: string | null; + } + + interface TeamOvertimeSummary { + month: string; + totalApplicationCount: number; + pendingCount: number; + approvedCount: number; + rejectedCount: number; + } } } diff --git a/src/typings/api/system-manage.d.ts b/src/typings/api/system-manage.d.ts index 92a57dc..94d2af4 100644 --- a/src/typings/api/system-manage.d.ts +++ b/src/typings/api/system-manage.d.ts @@ -386,6 +386,24 @@ declare namespace Api { children?: UserManagementRelationTreeRespVO[] | null; } + /** + * 当前登录用户的下属树 + * + * 用于团队视角选择器;根节点代表“全部下属范围” + */ + interface MySubordinateTreeNode { + /** 用户 ID */ + userId: string; + /** 用户昵称 */ + userNickname: string; + /** 是否为当前登录用户根节点 */ + isRoot: boolean; + /** 全链路下属人数 */ + subordinateCount: number; + /** 下级用户列表 */ + children?: MySubordinateTreeNode[] | null; + } + /** * 用户管理链路保存参数 * diff --git a/src/typings/api/work-report.d.ts b/src/typings/api/work-report.d.ts index 72d448a..742c1b2 100644 --- a/src/typings/api/work-report.d.ts +++ b/src/typings/api/work-report.d.ts @@ -71,6 +71,34 @@ declare namespace Api { total: number; list: T[]; } + + interface TeamReportPendingUser { + userId: string; + userNickname: string; + } + + interface TeamReportSummary { + totalShouldSubmit: number; + submittedCount: number; + unsubmittedCount: number; + pendingApprovalCount: number; + unsubmittedUsers: TeamReportPendingUser[]; + } + + interface TeamReportSummaryParams { + reportType: ReportType; + periodKey: string; + } + + interface TeamReportRemindParams { + reportType: ReportType; + periodKey: string; + userIds?: string[] | null; + } + + interface TeamReportRemindResult { + remindedCount: number; + } } namespace Weekly { @@ -114,6 +142,7 @@ declare namespace Api { } type WeeklyReportSearchParams = Common.WorkReportBaseSearchParams & { + reporterIds?: string[] | null; isBusinessTrip?: boolean | string | null; }; @@ -164,7 +193,9 @@ declare namespace Api { planItems: Common.PersonalReportPlanItem[]; } - type MonthlyReportSearchParams = Common.WorkReportBaseSearchParams; + type MonthlyReportSearchParams = Common.WorkReportBaseSearchParams & { + reporterIds?: string[] | null; + }; interface MonthlyReportSaveParams { periodKey: string; @@ -266,6 +297,7 @@ declare namespace Api { } type ProjectReportSearchParams = Common.WorkReportBaseSearchParams & { + projectOwnerIds?: string[] | null; projectId?: string | null; flag?: number | null; }; diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts index 6c5f986..277cdae 100644 --- a/src/typings/components.d.ts +++ b/src/typings/components.d.ts @@ -178,12 +178,14 @@ declare module 'vue' { RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SoybeanAvatar: typeof import('./../components/custom/soybean-avatar.vue')['default'] + SubordinateSelector: typeof import('./../components/custom/subordinate-selector.vue')['default'] SvgIcon: typeof import('./../components/custom/svg-icon.vue')['default'] SystemLogo: typeof import('./../components/common/system-logo.vue')['default'] TableColumnSetting: typeof import('./../components/advanced/table-column-setting.vue')['default'] TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default'] TableSearchFields: typeof import('./../components/custom/table-search-fields.vue')['default'] TableSearchPanel: typeof import('./../components/custom/table-search-panel.vue')['default'] + TeamContextPanel: typeof import('./../components/custom/team-context-panel.vue')['default'] ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default'] UserPickerTrigger: typeof import('./../components/custom/business-user-picker/components/user-picker-trigger.vue')['default'] WaveBg: typeof import('./../components/custom/wave-bg.vue')['default'] diff --git a/src/views/personal-center/overtime-application/index.vue b/src/views/personal-center/overtime-application/index.vue index 34282f6..05dd58a 100644 --- a/src/views/personal-center/overtime-application/index.vue +++ b/src/views/personal-center/overtime-application/index.vue @@ -1,10 +1,24 @@