From 3c1cf6c7faca93351a7fd7ab8f23cd4d30273aae Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Sun, 14 Jun 2026 23:57:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=B7=A5=E4=BD=9C=E6=8A=A5=E5=91=8A?= =?UTF-8?q?=E3=80=81=E5=8A=A0=E7=8F=AD=E7=94=B3=E8=AF=B7=E5=9B=A2=E9=98=9F?= =?UTF-8?q?=E8=A7=86=E8=A7=92):=20=E5=B7=A5=E4=BD=9C=E6=8A=A5=E5=91=8A?= =?UTF-8?q?=E3=80=81=E5=8A=A0=E7=8F=AD=E7=94=B3=E8=AF=B7=E7=8E=B0=E5=9C=A8?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E6=9F=A5=E7=9C=8B=E5=9B=A2=E9=98=9F=E8=A7=86?= =?UTF-8?q?=E8=A7=92=E4=BA=86=EF=BC=88=E6=9F=A5=E7=9C=8B=E4=B8=8B=E5=B1=9E?= =?UTF-8?q?=EF=BC=89=E3=80=82=20fix(=E5=B7=A5=E4=BD=9C=E6=8A=A5=E5=91=8A):?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=E5=91=A8=E6=8A=A5=E5=9C=A8=E6=96=B0?= =?UTF-8?q?=E5=A2=9E/=E7=BC=96=E8=BE=91=E6=97=B6=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E8=83=BD=E5=B1=95=E7=A4=BA=E5=B7=A5=E4=BD=9C=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/subordinate-selector.vue | 108 ++++++ src/components/custom/team-context-panel.vue | 163 +++++++++ src/service/api/overtime-application.ts | 25 ++ src/service/api/system-manage.ts | 24 ++ src/service/api/work-report.ts | 68 +++- src/typings/api/overtime-application.d.ts | 13 + src/typings/api/system-manage.d.ts | 18 + src/typings/api/work-report.d.ts | 34 +- src/typings/components.d.ts | 2 + .../overtime-application/index.vue | 339 +++++++++++++++--- .../personal-center/shared/team-dashboard.ts | 70 ++++ .../personal-center/work-report/index.vue | 148 +++++++- .../work-report/monthly/index.vue | 113 +++++- .../work-report/project/index.vue | 115 +++++- .../shared/components/team-report-summary.vue | 217 +++++++++++ .../work-report/shared/types.ts | 10 + .../work-report/shared/utils.ts | 77 ++++ .../work-report/weekly/index.vue | 113 +++++- .../work-report/weekly/modules/fill-page.vue | 55 ++- 19 files changed, 1618 insertions(+), 94 deletions(-) create mode 100644 src/components/custom/subordinate-selector.vue create mode 100644 src/components/custom/team-context-panel.vue create mode 100644 src/views/personal-center/shared/team-dashboard.ts create mode 100644 src/views/personal-center/work-report/shared/components/team-report-summary.vue 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 @@