From 366b7a12c8016018245b51250dcdd23727ac6455 Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Fri, 24 Jul 2026 16:34:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(performance):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=BB=A9=E6=95=88=E6=A8=A1=E6=9D=BF=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增绩效模板上传范围接口,限制组织负责人权限 - 实现单人上传和批量导入两种模板上传方式 - 添加模板年份、员工筛选等搜索条件 - 集成文件上传验证和批量导入结果展示 - 重构模板对话框UI布局,提升用户体验 - 更新API类型定义和数据转换逻辑 --- src/service/api/performance.ts | 104 ++- src/typings/api/performance.d.ts | 63 +- .../personal-center/my-performance/index.vue | 34 +- .../performance-excel-editor-drawer.vue | 9 +- .../modules/performance-template-dialog.vue | 803 ++++++++++-------- 5 files changed, 633 insertions(+), 380 deletions(-) diff --git a/src/service/api/performance.ts b/src/service/api/performance.ts index 0c55b23..370d579 100644 --- a/src/service/api/performance.ts +++ b/src/service/api/performance.ts @@ -19,6 +19,8 @@ type TemplateResponse = Omit | null; +}; + +type TemplateBatchImportResponse = { + successCount?: number | string | null; + failureCount?: number | string | null; + successes?: Array<{ + employeeId: StringIdResponse; + employeeName: string; + templateId: StringIdResponse; + sourceFileName: string; + sheetName?: string | null; + }> | null; + failures?: Array<{ + sourceFileName: string; + sheetName?: string | null; + reason: string; + }> | null; +}; + type SheetResponse = Omit< Api.Performance.Sheet.Sheet, 'id' | 'employeeId' | 'employeeDeptId' | 'managerId' | 'templateId' | 'fileId' @@ -135,6 +165,8 @@ function normalizeTemplate(response: TemplateResponse): Api.Performance.Template ...response, id: normalizeStringId(response.id), fileId: normalizeStringId(response.fileId), + employeeId: normalizeNullableStringId(response.employeeId), + employeeDeptId: normalizeNullableStringId(response.employeeDeptId), uploadUserId: normalizeStringId(response.uploadUserId), activeFlag: normalizeBooleanFlag(response.activeFlag), remark: response.remark ?? null, @@ -142,6 +174,40 @@ function normalizeTemplate(response: TemplateResponse): Api.Performance.Template }; } +function normalizeTemplateUploadScope(response: TemplateUploadScopeResponse): Api.Performance.Template.UploadScope { + return { + orgLeader: normalizeBooleanFlag(response.orgLeader), + employeeCount: normalizeTotal(response.employeeCount), + employees: (response.employees || []).map(item => ({ + employeeId: normalizeStringId(item.employeeId), + employeeName: item.employeeName, + employeeDeptId: normalizeNullableStringId(item.employeeDeptId), + employeeDeptName: item.employeeDeptName ?? null + })) + }; +} + +function normalizeTemplateBatchImportResult( + response: TemplateBatchImportResponse +): Api.Performance.Template.BatchImportResult { + return { + successCount: normalizeTotal(response.successCount), + failureCount: normalizeTotal(response.failureCount), + successes: (response.successes || []).map(item => ({ + employeeId: normalizeStringId(item.employeeId), + employeeName: item.employeeName, + templateId: normalizeStringId(item.templateId), + sourceFileName: item.sourceFileName, + sheetName: item.sheetName ?? null + })), + failures: (response.failures || []).map(item => ({ + sourceFileName: item.sourceFileName, + sheetName: item.sheetName ?? null, + reason: item.reason + })) + }; +} + function normalizeSheet(response: SheetResponse): Api.Performance.Sheet.Sheet { return { ...response, @@ -304,15 +370,21 @@ function createTemplateQuery(params: Api.Performance.Template.SearchParams = {}) query.append('pageNo', String(params.pageNo ?? 1)); query.append('pageSize', String(params.pageSize ?? 10)); appendValue(query, 'templateName', params.templateName); + appendValue(query, 'employeeId', params.employeeId); + appendValue(query, 'employeeName', params.employeeName); + appendValue(query, 'templateYear', params.templateYear); + appendValue(query, 'uploadUserName', params.uploadUserName); appendValue(query, 'activeFlag', params.activeFlag); return query.toString(); } -export async function fetchPerformanceTemplateCurrent() { +export async function fetchPerformanceTemplateCurrent(employeeId?: string | null) { + const query = new URLSearchParams(); + appendValue(query, 'employeeId', employeeId); const result = await request({ ...safeJsonRequestConfig, - url: `${TEMPLATE_PREFIX}/current`, + url: query.toString() ? `${TEMPLATE_PREFIX}/current?${query.toString()}` : `${TEMPLATE_PREFIX}/current`, method: 'get' }); @@ -335,10 +407,20 @@ export async function fetchPerformanceTemplatePage(params: Api.Performance.Templ })); } -export async function uploadPerformanceTemplate(data: Api.Performance.Template.UploadParams) { +export async function fetchPerformanceTemplateUploadScope() { + const result = await request({ + ...safeJsonRequestConfig, + url: `${TEMPLATE_PREFIX}/upload-scope`, + method: 'get' + }); + + return mapServiceResult(result as ServiceRequestResult, normalizeTemplateUploadScope); +} + +export async function uploadSinglePerformanceTemplate(data: Api.Performance.Template.SingleUploadParams) { const result = await request({ ...safeJsonRequestConfig, - url: `${TEMPLATE_PREFIX}/upload`, + url: `${TEMPLATE_PREFIX}/upload-single`, method: 'post', data }); @@ -346,6 +428,20 @@ export async function uploadPerformanceTemplate(data: Api.Performance.Template.U return mapServiceResult(result as ServiceRequestResult, normalizeStringId); } +export async function batchImportPerformanceTemplates(data: Api.Performance.Template.BatchImportParams) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${TEMPLATE_PREFIX}/batch-import`, + method: 'post', + data + }); + + return mapServiceResult( + result as ServiceRequestResult, + normalizeTemplateBatchImportResult + ); +} + export function activatePerformanceTemplate(id: string) { return request({ ...safeJsonRequestConfig, diff --git a/src/typings/api/performance.d.ts b/src/typings/api/performance.d.ts index 1bd9be3..504bec1 100644 --- a/src/typings/api/performance.d.ts +++ b/src/typings/api/performance.d.ts @@ -32,6 +32,11 @@ declare namespace Api { templateName: string; fileId: string; fileName: string; + employeeId?: string | null; + employeeName?: string | null; + employeeDeptId?: string | null; + employeeDeptName?: string | null; + templateYear?: number | null; versionNo: number; activeFlag: boolean; uploadUserId: string; @@ -44,17 +49,67 @@ declare namespace Api { type SearchParams = CommonType.RecordNullable< Common.PageParams & { templateName: string; + employeeId: string; + employeeName: string; + templateYear: number; + uploadUserName: string; activeFlag: boolean; } >; - interface UploadParams { + interface ScopeEmployee { + employeeId: string; + employeeName: string; + employeeDeptId?: string | null; + employeeDeptName?: string | null; + } + + interface UploadScope { + orgLeader: boolean; + employeeCount: number; + employees: ScopeEmployee[]; + } + + interface SingleUploadParams { + employeeId: string; templateName: string; - fileId: string; - fileName: string; - activeFlag?: boolean | null; + templateYear?: number | null; + sourceFileId: string; + sourceFileName: string; remark?: string | null; } + + interface BatchImportFileItem { + sourceFileId: string; + sourceFileName: string; + } + + interface BatchImportParams { + templateYear?: number | null; + remark?: string | null; + files: BatchImportFileItem[]; + } + + interface BatchImportSuccessItem { + employeeId: string; + employeeName: string; + templateId: string; + sourceFileName: string; + sheetName?: string | null; + } + + interface BatchImportFailureItem { + sourceFileName: string; + sheetName?: string | null; + reason: string; + } + + interface BatchImportResult { + successCount: number; + failureCount: number; + successes: BatchImportSuccessItem[]; + failures: BatchImportFailureItem[]; + } } namespace Sheet { diff --git a/src/views/personal-center/my-performance/index.vue b/src/views/personal-center/my-performance/index.vue index 2de760a..210a621 100644 --- a/src/views/personal-center/my-performance/index.vue +++ b/src/views/personal-center/my-performance/index.vue @@ -13,6 +13,7 @@ import { fetchGetMySubordinateTree, fetchPerformanceSheetPage, fetchPerformanceSheetStatusDict, + fetchPerformanceTemplateUploadScope, fetchTeamPerformanceSummary, formatToYYYYMM, resendPerformanceSheet, @@ -111,6 +112,8 @@ const deptOptions = ref>([]); const directSubordinateOptions = ref>([]); const statusOptions = ref>([]); const currentDeptId = ref(''); +const templateUploadScope = ref(null); +const templateUploadScopeLoading = ref(false); const templateVisible = ref(false); const excelVisible = ref(false); @@ -144,9 +147,10 @@ const canSignOff = computed( () => hasAuth(PerformancePermission.SheetImport) && hasAuth(PerformancePermission.SheetSignOff) ); const canExport = computed(() => hasAuth(PerformancePermission.SheetExport)); -const canManageTemplate = computed( +const hasTemplatePermission = computed( () => hasAuth(PerformancePermission.TemplateQuery) || hasAuth(PerformancePermission.TemplateUpdate) ); +const canManageTemplate = computed(() => hasTemplatePermission.value && Boolean(templateUploadScope.value?.orgLeader)); const isTeamMode = computed(() => teamViewMode.value === 'team'); const allSubordinateUserIds = computed(() => collectSubordinateUserIds(subordinateTree.value)); const subordinateOptions = computed(() => { @@ -656,6 +660,19 @@ async function loadStatusOptions() { statusOptions.value = getPerformanceStatusOptions(statusDict); } +async function loadTemplateUploadScope() { + if (!hasTemplatePermission.value) { + templateUploadScope.value = null; + return; + } + + templateUploadScopeLoading.value = true; + const { error, data: scopeData } = await fetchPerformanceTemplateUploadScope(); + templateUploadScopeLoading.value = false; + + templateUploadScope.value = error || !scopeData ? null : scopeData; +} + async function handleTeamViewModeChange(mode: TeamViewMode) { teamViewMode.value = mode; @@ -694,7 +711,12 @@ watch(excelVisible, isVisible => { }); onMounted(async () => { - await Promise.all([loadDeptOptions(), loadDirectSubordinateOptions(), loadStatusOptions()]); + await Promise.all([ + loadDeptOptions(), + loadDirectSubordinateOptions(), + loadStatusOptions(), + loadTemplateUploadScope() + ]); if (canUseTeamDashboard.value) { await loadSubordinateTree(); @@ -773,7 +795,12 @@ onMounted(async () => { 下签 - + @@ -821,6 +848,7 @@ onMounted(async () => { diff --git a/src/views/personal-center/my-performance/modules/performance-excel-editor-drawer.vue b/src/views/personal-center/my-performance/modules/performance-excel-editor-drawer.vue index 69adeb2..1181dbc 100644 --- a/src/views/personal-center/my-performance/modules/performance-excel-editor-drawer.vue +++ b/src/views/personal-center/my-performance/modules/performance-excel-editor-drawer.vue @@ -520,7 +520,12 @@ async function loadWorkbook() { let sourceFileId = ''; if (isCreateMode.value) { - const templateResult = await fetchPerformanceTemplateCurrent(); + if (!createForm.employeeId) { + errorMessage.value = '请选择下属后再加载绩效模板'; + return; + } + + const templateResult = await fetchPerformanceTemplateCurrent(createForm.employeeId); if (templateResult.error || !templateResult.data) { errorMessage.value = '当前没有可用的绩效模板'; return; @@ -533,7 +538,7 @@ async function loadWorkbook() { const [sheetResult, templateResult] = await Promise.all([ fetchPerformanceSheet(props.rowData.id), - fetchPerformanceTemplateCurrent() + fetchPerformanceTemplateCurrent(props.rowData.employeeId) ]); if (sheetResult.error || !sheetResult.data) { diff --git a/src/views/personal-center/my-performance/modules/performance-template-dialog.vue b/src/views/personal-center/my-performance/modules/performance-template-dialog.vue index a7d3036..ef85064 100644 --- a/src/views/personal-center/my-performance/modules/performance-template-dialog.vue +++ b/src/views/personal-center/my-performance/modules/performance-template-dialog.vue @@ -1,181 +1,254 @@ -