From a9c6ddccbac8cb2c5142ea9139c4015c81a100a7 Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Fri, 24 Jul 2026 10:55:14 +0800 Subject: [PATCH] =?UTF-8?q?refactor(work-report):=20=E5=B0=86=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=8A=A5=E5=91=8A=E5=91=A8=E6=9C=9F=E4=BB=8E=E6=9C=88?= =?UTF-8?q?=E4=BB=BD=E5=8D=8A=E6=9C=88=E6=94=B9=E4=B8=BA=E8=87=AA=E7=94=B1?= =?UTF-8?q?=E7=9A=84=E6=97=A5=E6=9C=9F=E8=8C=83=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除项目报告中的 flag 字段必填限制,改为可选 - 将项目报告创建对话框中的月份和半月选择改为日期范围选择器 - 更新项目报告详情对话框,移除半月周期显示字段 - 修改项目报告搜索面板,调整周期字段标签和类型 - 更新团队报告汇总组件中的项目报告催办逻辑 - 调整项目报告表格列宽和标签显示 - 重构项目报告周期构建逻辑,支持灵活的日期范围输入 - 更新相关类型定义和 API 接口中的 flag 字段为可选 - 统一将"项目半月报"更名为"研发简报"以反映新的日期范围特性 --- src/locales/langs/zh-cn.ts | 2 +- src/typings/api/work-report.d.ts | 4 +- .../personal-center/work-report/index.vue | 6 +- .../work-report/project/index.vue | 40 +----- .../shared/components/create-dialog.vue | 125 ++++++---------- .../shared/components/detail-dialog.vue | 4 - .../components/prototype-page-dialog.vue | 2 +- .../shared/components/search-panel.vue | 4 +- .../shared/components/team-report-summary.vue | 46 +++++- .../work-report/shared/types.ts | 8 +- .../work-report/shared/utils.ts | 135 +++++++++--------- .../modules/workbench-todo-panel.vue | 4 +- 12 files changed, 175 insertions(+), 205 deletions(-) diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index 6674d06..b10f738 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -172,7 +172,7 @@ const local: App.I18n.Schema = { 'personal-center_work-report': '工作报告', 'personal-center_work-report_weekly': '个人周报', 'personal-center_work-report_monthly': '个人月报', - 'personal-center_work-report_project': '项目半月报', + 'personal-center_work-report_project': '研发简报', 'personal-center_my-performance': '绩效管理', 'personal-center_my-application': '我的申请', 'personal-center_overtime-application': '业务学习', diff --git a/src/typings/api/work-report.d.ts b/src/typings/api/work-report.d.ts index 0d06641..c7aaf95 100644 --- a/src/typings/api/work-report.d.ts +++ b/src/typings/api/work-report.d.ts @@ -300,7 +300,7 @@ declare namespace Api { periodLabel: string; periodStartDate: string; periodEndDate: string; - flag: number; + flag?: number | null; statusCode: Common.WorkReportStatusCode | string; statusName: string; allowEdit: boolean; @@ -332,7 +332,7 @@ declare namespace Api { periodLabel: string; periodStartDate: string; periodEndDate: string; - flag: number; + flag?: number | null; projectStatusDesc?: string | null; projectProgressPlan?: string | null; projectKeyPoints?: string | null; diff --git a/src/views/personal-center/work-report/index.vue b/src/views/personal-center/work-report/index.vue index 09fc35e..da25492 100644 --- a/src/views/personal-center/work-report/index.vue +++ b/src/views/personal-center/work-report/index.vue @@ -111,7 +111,7 @@ const teamContext = computed(() => { }; }); -/** 项目选项是否加载成功(用于项目半月报列表内部判断) */ +/** 项目选项是否加载成功(用于项目汇报列表内部判断) */ const projectOptionsLoaded = ref(false); const visibleTabs = computed>(() => { @@ -194,7 +194,7 @@ function handleCreateConfirm( | { reportType: 'project'; projectId: string; - flag: number; + flag?: number; period: typeof initialPeriod.value extends infer T ? T : never; } ) { @@ -203,7 +203,7 @@ function handleCreateConfirm( currentRow.value = null; initialPeriod.value = payload.period as typeof initialPeriod.value; initialProjectId.value = 'projectId' in payload ? payload.projectId : ''; - initialFlag.value = 'flag' in payload ? payload.flag : 1; + initialFlag.value = 'flag' in payload ? (payload.flag ?? 1) : 1; pageDialogVisible.value = true; } diff --git a/src/views/personal-center/work-report/project/index.vue b/src/views/personal-center/work-report/project/index.vue index 4a90f28..e0d72c9 100644 --- a/src/views/personal-center/work-report/project/index.vue +++ b/src/views/personal-center/work-report/project/index.vue @@ -28,7 +28,7 @@ import { resolveWorkReportStatusTagType, transformWorkReportPage } from '../shared/types'; -import { buildProjectPeriodFromMonth, resolveWorkReportSummaryPeriod } from '../shared/utils'; +import { resolveWorkReportSummaryPeriod } from '../shared/utils'; import ProjectReportSearch from './modules/search-panel.vue'; import IconMdiDeleteOutline from '~icons/mdi/delete-outline'; import IconMdiEyeOutline from '~icons/mdi/eye-outline'; @@ -86,7 +86,7 @@ const normalizedPeriodRange = computed(() => { return periodRange; } - return [start.startOf('month').format('YYYY-MM-DD'), end.endOf('month').format('YYYY-MM-DD')]; + return [start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')]; }); const table = useUIPaginatedTable< @@ -110,8 +110,8 @@ const table = useUIPaginatedTable< ...(isTeamMode.value ? [{ prop: 'projectOwnerName', label: '提交人', minWidth: 100, showOverflowTooltip: true }] : []), - { prop: 'projectName', label: '项目名称', minWidth: 200, showOverflowTooltip: true }, - { prop: 'periodLabel', label: '半月周期', minWidth: 120, formatter: row => formatPeriod(row) }, + { prop: 'projectName', label: '项目名称', minWidth: 210, showOverflowTooltip: true }, + { prop: 'periodLabel', label: '周期', minWidth: 150, formatter: row => formatPeriod(row) }, { prop: 'projectOwnerName', label: '项目负责人', minWidth: 80 }, { prop: 'technicalOwnerName', @@ -145,42 +145,12 @@ const table = useUIPaginatedTable< ] }); -// 团队统计始终使用当前周期(当前半月),不跟随列表第一条数据的周期 const summaryPeriod = computed(() => resolveWorkReportSummaryPeriod('project', { periodRange: normalizedPeriodRange.value }) ); -const summaryPeriodKeys = computed(() => { - const dateRange = normalizedPeriodRange.value; - const fallbackKey = summaryPeriod.value.periodKey; - - if (!dateRange?.length) { - return fallbackKey ? [fallbackKey] : []; - } - - const [startDate, endDate] = dateRange; - const start = dayjs(startDate); - const end = dayjs(endDate || startDate); - - if (!start.isValid() || !end.isValid()) { - return fallbackKey ? [fallbackKey] : []; - } - - const keys: string[] = []; - const endBoundary = end.endOf('month'); - - for ( - let cursor = start.startOf('month'); - cursor.isBefore(endBoundary, 'month') || cursor.isSame(endBoundary, 'month'); - cursor = cursor.add(1, 'month') - ) { - keys.push(buildProjectPeriodFromMonth(cursor, 1).periodKey); - keys.push(buildProjectPeriodFromMonth(cursor, 2).periodKey); - } - - return keys; -}); +const summaryPeriodKeys = computed(() => []); const hasSearchedDateRange = computed(() => searchParams.periodStartDate?.length === 2); function getRowActions(row: Api.WorkReport.Project.ProjectReport): BusinessTableAction[] { diff --git a/src/views/personal-center/work-report/shared/components/create-dialog.vue b/src/views/personal-center/work-report/shared/components/create-dialog.vue index df5db3a..7a98824 100644 --- a/src/views/personal-center/work-report/shared/components/create-dialog.vue +++ b/src/views/personal-center/work-report/shared/components/create-dialog.vue @@ -7,7 +7,7 @@ import BusinessFormDialog from '@/components/custom/business-form-dialog.vue'; import { type WorkReportPeriodOption, buildMonthlyPeriodFromMonth, - buildProjectPeriodFromMonth, + buildProjectPeriodFromDateRange, buildWeeklyPeriodFromDate, formatPeriodDisplayLabel, getReportTypePeriodOptions @@ -40,7 +40,7 @@ const emit = defineEmits<{ | { reportType: 'project'; projectId: string; - flag: number; + flag?: number; period: WorkReportPeriodOption['period']; } ): void; @@ -50,8 +50,7 @@ const selectedPeriodKey = ref(''); const selectedProjectId = ref(''); const rawCustomWeekDate = ref(''); const customMonth = ref(''); -const customProjectMonth = ref(''); -const customProjectFlag = ref(1); +const customProjectDateRange = ref([]); // 自定义周报周期:无论用户点哪一天,都归一到该 ISO 周的周一, // 这样 ElDatePicker 使用内置 dayjs 的 ww(locale week)也能正确显示 ISO 周数。 @@ -74,10 +73,7 @@ const selectedReportType = computed(() => { const periodOptionMap = computed(() => getReportTypePeriodOptions()); const activePeriodOptions = computed(() => periodOptionMap.value[selectedReportType.value]); const dialogTitle = computed(() => `新增${WORK_REPORT_TYPE_LABEL[selectedReportType.value]}`); -const projectHalfOptions = [ - { label: '上半月', value: 1 }, - { label: '下半月', value: 2 } -]; +const isProjectReport = computed(() => selectedReportType.value === 'project'); const defaultCustomMonth = computed(() => { const period = activePeriodOptions.value[0]?.period; @@ -85,7 +81,7 @@ const defaultCustomMonth = computed(() => { }); const customPeriod = computed(() => { - if (selectedPeriodKey.value !== 'custom') return null; + if (!isProjectReport.value && selectedPeriodKey.value !== 'custom') return null; if (selectedReportType.value === 'weekly') { if (!customWeekDate.value) return null; @@ -97,16 +93,15 @@ const customPeriod = computed(() => { return buildMonthlyPeriodFromMonth(customMonth.value); } - if (!customProjectMonth.value) return null; - return buildProjectPeriodFromMonth(customProjectMonth.value, customProjectFlag.value); + if (customProjectDateRange.value.length !== 2) return null; + return buildProjectPeriodFromDateRange(customProjectDateRange.value[0], customProjectDateRange.value[1]); }); const selectedPeriod = computed( () => activePeriodOptions.value.find(item => item.key === selectedPeriodKey.value) ?? activePeriodOptions.value[0] ); - const selectedPeriodValue = computed(() => - selectedPeriodKey.value === 'custom' ? customPeriod.value : selectedPeriod.value?.period + isProjectReport.value || selectedPeriodKey.value === 'custom' ? customPeriod.value : selectedPeriod.value?.period ); const customPeriodPreviewLabel = computed(() => customPeriod.value ? formatPeriodDisplayLabel(customPeriod.value.periodLabel) : '' @@ -114,14 +109,14 @@ const customPeriodPreviewLabel = computed(() => const confirmDisabled = computed(() => { if (!selectedPeriodValue.value) return true; - if (selectedReportType.value === 'project' && !selectedProjectId.value) return true; + if (isProjectReport.value && !selectedProjectId.value) return true; return false; }); watch( selectedReportType, type => { - selectedPeriodKey.value = periodOptionMap.value[type][0]?.key || ''; + selectedPeriodKey.value = type === 'project' ? 'custom' : periodOptionMap.value[type][0]?.key || ''; if (type === 'project' && !selectedProjectId.value) { selectedProjectId.value = props.projectOptions[0]?.id || ''; @@ -134,27 +129,34 @@ watch(visible, isVisible => { if (!isVisible) return; selectedProjectId.value = props.projectOptions[0]?.id || ''; - selectedPeriodKey.value = periodOptionMap.value[selectedReportType.value][0]?.key || ''; + selectedPeriodKey.value = isProjectReport.value + ? 'custom' + : periodOptionMap.value[selectedReportType.value][0]?.key || ''; customWeekDate.value = activePeriodOptions.value[0]?.period.periodStartDate || ''; customMonth.value = defaultCustomMonth.value; - customProjectMonth.value = defaultCustomMonth.value; - customProjectFlag.value = activePeriodOptions.value[0]?.flag || 1; + customProjectDateRange.value = [ + activePeriodOptions.value[0]?.period.periodStartDate || '', + activePeriodOptions.value[0]?.period.periodEndDate || '' + ].filter(Boolean); }); function handleConfirm() { const period = selectedPeriodValue.value; if (!period) return; - if (selectedReportType.value === 'project') { + if (isProjectReport.value) { emit('confirm', { reportType: 'project', projectId: selectedProjectId.value, - flag: selectedPeriodKey.value === 'custom' ? customProjectFlag.value : selectedPeriod.value.flag || 1, + flag: period.flag, period }); } else { + const reportType = selectedReportType.value; + if (reportType !== 'weekly' && reportType !== 'monthly') return; + emit('confirm', { - reportType: selectedReportType.value, + reportType, period }); } @@ -194,7 +196,7 @@ function handleConfirm() {
-
+
-
+
@@ -260,26 +260,18 @@ function handleConfirm() {
-
-
-
选择月份
- -
-
-
选择半月
- -
+
+
选择周期
+
@@ -422,6 +414,10 @@ function handleConfirm() { box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06); } +.work-report-create-dialog__custom-period--project { + margin-top: 0; +} + .work-report-create-dialog__custom-row { display: flex; gap: 12px; @@ -438,13 +434,6 @@ function handleConfirm() { gap: 14px; } -.work-report-create-dialog__custom-project-grid { - display: grid; - grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); - gap: 14px; - align-items: stretch; -} - .work-report-create-dialog__custom-project-item { display: flex; flex-direction: column; @@ -471,24 +460,6 @@ function handleConfirm() { width: 100%; } -.work-report-create-dialog__half-segmented { - width: 100%; - display: flex; -} - -.work-report-create-dialog__half-segmented :deep(.el-segmented__group) { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - width: 100%; - gap: 0; -} - -.work-report-create-dialog__half-segmented :deep(.el-segmented__item) { - flex: 1; - min-width: 0; - justify-content: center; -} - .work-report-create-dialog__period-preview { display: inline-flex; align-items: center; @@ -532,10 +503,8 @@ function handleConfirm() { grid-template-columns: 1fr; } - .work-report-create-dialog__custom-row, - .work-report-create-dialog__custom-project-grid { + .work-report-create-dialog__custom-row { flex-direction: column; - grid-template-columns: 1fr; } .work-report-create-dialog__field--inline { diff --git a/src/views/personal-center/work-report/shared/components/detail-dialog.vue b/src/views/personal-center/work-report/shared/components/detail-dialog.vue index ba151cc..e72b273 100644 --- a/src/views/personal-center/work-report/shared/components/detail-dialog.vue +++ b/src/views/personal-center/work-report/shared/components/detail-dialog.vue @@ -12,7 +12,6 @@ import { formatPeriod, formatPeriodDateRange, formatWeeklyPeriodLabel, - getProjectReportFlagLabel, getWorkReportStatusLabel } from '../types'; @@ -100,9 +99,6 @@ function getPersonalDetail() { {{ getProjectDetail()?.projectName }} - - {{ getProjectReportFlagLabel(getProjectDetail()?.flag) }} - {{ getProjectDetail()?.projectOwnerName }} {{ formatEmptyText(getProjectDetail()?.technicalOwnerName) }} diff --git a/src/views/personal-center/work-report/shared/components/prototype-page-dialog.vue b/src/views/personal-center/work-report/shared/components/prototype-page-dialog.vue index 62c4d46..7534aaa 100644 --- a/src/views/personal-center/work-report/shared/components/prototype-page-dialog.vue +++ b/src/views/personal-center/work-report/shared/components/prototype-page-dialog.vue @@ -91,7 +91,7 @@ const emit = defineEmits<{ const REPORT_TYPE_TEXT: Record = { weekly: '个人周报', monthly: '个人月报', - project: '项目半月报' + project: '研发简报' }; const loading = ref(false); diff --git a/src/views/personal-center/work-report/shared/components/search-panel.vue b/src/views/personal-center/work-report/shared/components/search-panel.vue index 25c3926..0890ddb 100644 --- a/src/views/personal-center/work-report/shared/components/search-panel.vue +++ b/src/views/personal-center/work-report/shared/components/search-panel.vue @@ -58,7 +58,7 @@ const fields = computed(() => { const monthPeriodField: SearchField = { key: 'periodStartDate', - label: props.reportType === 'project' ? '月份' : '月份', + label: '月份', type: 'dateRange', dateRangeType: 'monthrange', valueFormat: 'YYYY-MM-DD', @@ -107,7 +107,7 @@ const fields = computed(() => { if (props.reportType === 'project') { return [ baseFields[0], - monthPeriodField, + { key: 'periodStartDate', label: '周期', type: 'dateRange', placeholder: '请选择周期' }, { key: 'projectId', label: '项目', diff --git a/src/views/personal-center/work-report/shared/components/team-report-summary.vue b/src/views/personal-center/work-report/shared/components/team-report-summary.vue index 927f4fc..e99ae06 100644 --- a/src/views/personal-center/work-report/shared/components/team-report-summary.vue +++ b/src/views/personal-center/work-report/shared/components/team-report-summary.vue @@ -28,7 +28,10 @@ const emit = defineEmits<{ const remindingAll = ref(false); const remindingItemKey = ref(''); -const canRemind = computed(() => props.periodKeys.length > 0); +const canProjectRemind = computed(() => Boolean(props.summary?.unsubmittedReports?.length)); +const canRemind = computed(() => + props.reportType === 'project' ? canProjectRemind.value : props.periodKeys.length > 0 +); function formatSummaryPeriodLabel() { if (!props.summary?.periodStartDate || !props.summary?.periodEndDate) { @@ -42,12 +45,16 @@ function formatSummaryPeriodLabel() { return `${props.summary.periodStartDate} 至 ${props.summary.periodEndDate}`; } - if (props.reportType === 'monthly' || props.reportType === 'project') { + if (props.reportType === 'monthly') { const startMonth = start.format('YYYY-MM'); const endMonth = end.format('YYYY-MM'); return startMonth === endMonth ? startMonth : `${startMonth} 至 ${endMonth}`; } + if (props.reportType === 'project') { + return `${start.format('YYYY-MM-DD')} 至 ${end.format('YYYY-MM-DD')}`; + } + if (props.reportType === 'weekly') { return formatIsoWeekRangeLabel(props.summary.periodStartDate, props.summary.periodEndDate); } @@ -101,9 +108,36 @@ function getSingleRemindSuccessText(remindedCount: number, preciseProjectReminde } async function handleRemind(userIds?: string[]) { - if (!props.periodKeys.length) return; + if (props.reportType === 'project') { + const unsubmittedReports = props.summary?.unsubmittedReports || []; + if (!unsubmittedReports.length) return; - const isSingleUserReminder = userIds?.length === 1; + remindingAll.value = true; + const results = await Promise.all( + unsubmittedReports.map(item => + fetchRemindTeamReport({ + reportType: 'project', + periodKey: item.periodKey, + userIds: userIds?.length ? userIds : [item.userId], + projectId: item.projectId || undefined + }) + ) + ); + remindingAll.value = false; + + const remindedCount = results.reduce((total, result) => { + if (result.error) return total; + return total + (result.data?.remindedCount ?? 0); + }, 0); + + if (!remindedCount) return; + + window.$message?.success('已按当前未提交明细发送催办提醒'); + emit('reminded'); + return; + } + + if (!props.periodKeys.length) return; remindingAll.value = true; @@ -127,9 +161,7 @@ async function handleRemind(userIds?: string[]) { if (!remindedCount) return; window.$message?.success( - props.periodKeys.length > 1 - ? '已按所选区间发送催办提醒' - : getSingleRemindSuccessText(remindedCount, props.reportType === 'project' && isSingleUserReminder) + props.periodKeys.length > 1 ? '已按所选区间发送催办提醒' : getSingleRemindSuccessText(remindedCount, false) ); emit('reminded'); } diff --git a/src/views/personal-center/work-report/shared/types.ts b/src/views/personal-center/work-report/shared/types.ts index 00ec9b4..5d8509c 100644 --- a/src/views/personal-center/work-report/shared/types.ts +++ b/src/views/personal-center/work-report/shared/types.ts @@ -38,13 +38,13 @@ export const WORK_REPORT_PROJECT_OWNER_PERMISSION = 'project:work-report:project export const WORK_REPORT_TYPE_LABEL: Record = { weekly: '个人周报', monthly: '个人月报', - project: '项目半月报' + project: '研发简报' }; export const TEAM_WORK_REPORT_TYPE_LABEL: Record = { weekly: '团队周报', monthly: '团队月报', - project: '团队项目半月报' + project: '团队研发简报' }; export function getWorkReportTypeDisplayLabel(reportType: WorkReportType, isTeamMode = false) { @@ -145,7 +145,7 @@ export function formatWeeklyPeriodLabel( export function formatPeriodLabel(value?: string | null) { return String(value || '') .trim() - .replace(/\s*(周报|月报|项目半月报|半月报)\s*$/u, ''); + .replace(/\s*(周报|月报|项目汇报|项目半月报|半月报|研发简报)\s*$/u, ''); } export function formatPeriod(row: Pick) { @@ -472,7 +472,7 @@ export function createProjectSaveParams( periodLabel: '', periodStartDate: '', periodEndDate: '', - flag: 1, + flag: undefined, projectStatusDesc: '', projectProgressPlan: '', projectKeyPoints: '', diff --git a/src/views/personal-center/work-report/shared/utils.ts b/src/views/personal-center/work-report/shared/utils.ts index 87a2b53..0e0efa9 100644 --- a/src/views/personal-center/work-report/shared/utils.ts +++ b/src/views/personal-center/work-report/shared/utils.ts @@ -17,6 +17,7 @@ export interface WorkReportPeriodOption { periodLabel: string; periodStartDate: string; periodEndDate: string; + flag?: number; }; } @@ -35,7 +36,7 @@ function formatRangeLabel(start: dayjs.Dayjs, end: dayjs.Dayjs) { export function formatPeriodDisplayLabel(label?: string | null) { return String(label || '') .trim() - .replace(/\s*(周报|月报|项目半月报|半月报)\s*$/u, ''); + .replace(/\s*(周报|月报|项目汇报|项目半月报|半月报|研发简报)\s*$/u, ''); } // 使用 ISO 周数,确保与 buildWeeklyPeriodFromDate 的计算一致 @@ -119,12 +120,43 @@ export function buildProjectPeriodFromMonth(month: string | dayjs.Dayjs, flag: n if (flag === 2) { const start = monthStart.date(16); const end = selectedMonth.endOf('month'); - return buildPeriod('project', start, end, `${selectedMonth.format('YYYY-MM')} 下半月`, 2); + return buildProjectPeriodFromDateRange(start, end); } const start = monthStart.startOf('month'); const end = monthStart.date(15); - return buildPeriod('project', start, end, `${selectedMonth.format('YYYY-MM')} 上半月`, 1); + return buildProjectPeriodFromDateRange(start, end); +} + +export function buildProjectPeriodFromDateRange(startDate: string | dayjs.Dayjs, endDate: string | dayjs.Dayjs) { + const start = dayjs(startDate).startOf('day'); + const end = dayjs(endDate).startOf('day'); + + if (!start.isValid() || !end.isValid()) { + return { + periodKey: '', + periodLabel: '', + periodStartDate: '', + periodEndDate: '', + flag: undefined + }; + } + + let flag: number | undefined; + let label = formatRangeLabel(start, end); + + if (start.isSame(end, 'month') && start.date() === 1 && end.date() === 15) { + flag = 1; + label = `${start.format('YYYY-MM')} 上半月`; + } else if (start.isSame(end, 'month') && start.date() === 16 && end.date() === end.daysInMonth()) { + flag = 2; + label = `${start.format('YYYY-MM')} 下半月`; + } + + return { + ...buildPeriod('project', start, end, label, flag), + flag + }; } export function getWeeklyPeriodOptions(now = dayjs()): WorkReportPeriodOption[] { @@ -177,59 +209,30 @@ export function getMonthlyPeriodOptions(now = dayjs()): WorkReportPeriodOption[] } export function getProjectPeriodOptions(now = dayjs()): WorkReportPeriodOption[] { - const currentMonthStart = now.startOf('month'); - const currentMonthEnd = now.endOf('month'); - const currentFirstHalfEnd = currentMonthStart.date(15); - const currentSecondHalfStart = currentMonthStart.date(16); - const previousMonth = now.subtract(1, 'month'); - const previousMonthStart = previousMonth.startOf('month'); - const previousMonthEnd = previousMonth.endOf('month'); - const previousSecondHalfStart = previousMonthStart.date(16); - const isCurrentFirstHalf = now.date() <= 15; + const recent15Start = now.subtract(14, 'day').startOf('day'); + const recent30Start = now.subtract(29, 'day').startOf('day'); + const today = now.startOf('day'); + const recent15Period = buildProjectPeriodFromDateRange(recent15Start, today); + const recent30Period = buildProjectPeriodFromDateRange(recent30Start, today); - const currentOption: WorkReportPeriodOption = isCurrentFirstHalf - ? { - key: 'current-first-half', - label: '上半月', - description: `${now.format('YYYY-MM')} 上半月`, - reportType: 'project', - flag: 1, - period: buildPeriod('project', currentMonthStart, currentFirstHalfEnd, `${now.format('YYYY-MM')} 上半月`, 1) - } - : { - key: 'current-second-half', - label: '下半月', - description: `${now.format('YYYY-MM')} 下半月`, - reportType: 'project', - flag: 2, - period: buildPeriod('project', currentSecondHalfStart, currentMonthEnd, `${now.format('YYYY-MM')} 下半月`, 2) - }; - - const previousOption: WorkReportPeriodOption = isCurrentFirstHalf - ? { - key: 'previous-second-half', - label: '下半月', - description: `${previousMonth.format('YYYY-MM')} 下半月`, - reportType: 'project', - flag: 2, - period: buildPeriod( - 'project', - previousSecondHalfStart, - previousMonthEnd, - `${previousMonth.format('YYYY-MM')} 下半月`, - 2 - ) - } - : { - key: 'previous-first-half', - label: '上半月', - description: `${now.format('YYYY-MM')} 上半月`, - reportType: 'project', - flag: 1, - period: buildPeriod('project', currentMonthStart, currentFirstHalfEnd, `${now.format('YYYY-MM')} 上半月`, 1) - }; - - return [currentOption, previousOption]; + return [ + { + key: 'recent-15-days', + label: '最近15天', + description: formatRangeLabel(recent15Start, today), + reportType: 'project', + flag: recent15Period.flag, + period: recent15Period + }, + { + key: 'recent-30-days', + label: '最近30天', + description: formatRangeLabel(recent30Start, today), + reportType: 'project', + flag: recent30Period.flag, + period: recent30Period + } + ]; } export function getReportTypePeriodOptions(now = dayjs()) { @@ -293,18 +296,18 @@ export function resolveWorkReportSummaryPeriod( return referenceDate ? buildMonthlyPeriodFromMonth(referenceDate) : buildMonthlyPeriodFromMonth(fallbackNow); } - if (referenceDate) { - const resolvedFlag = inferProjectSummaryFlag(referenceDate, flag); - return { - ...buildProjectPeriodFromMonth(referenceDate, resolvedFlag), - flag: resolvedFlag - }; + if (periodRange?.length) { + const [startDate, endDate] = periodRange; + const fallbackEndDate = endDate || startDate; + if (startDate && fallbackEndDate) { + return buildProjectPeriodFromDateRange(startDate, fallbackEndDate); + } } - const fallbackOption = getProjectPeriodOptions(fallbackNow)[0]; + if (referenceDate) { + const resolvedFlag = inferProjectSummaryFlag(referenceDate, flag); + return buildProjectPeriodFromMonth(referenceDate, resolvedFlag); + } - return { - ...fallbackOption.period, - flag: fallbackOption.flag - }; + return buildProjectPeriodFromDateRange(fallbackNow.subtract(29, 'day'), fallbackNow); } diff --git a/src/views/workbench/modules/workbench-todo-panel.vue b/src/views/workbench/modules/workbench-todo-panel.vue index b87f00b..b41ed5e 100644 --- a/src/views/workbench/modules/workbench-todo-panel.vue +++ b/src/views/workbench/modules/workbench-todo-panel.vue @@ -147,7 +147,7 @@ const deadlineFilters: Array<{ key: Exclude; const approvalBizTabs: Array<{ key: ApprovalBizType; label: string }> = [ { key: 'weekly', label: '周报' }, { key: 'monthly', label: '月报' }, - { key: 'project', label: '项目半月报' }, + { key: 'project', label: '研发简报' }, { key: 'overtime_application', label: '业务学习' } ]; const confirmBizTabs: Array<{ key: Extract; label: string }> = [ @@ -261,7 +261,7 @@ const APPROVAL_ENTRY_ICON = markRaw(IconMdiClipboardCheckOutline); function getApprovalCategoryLabel(bizType: ApprovalBizType) { if (bizType === 'weekly') return '周报'; if (bizType === 'monthly') return '月报'; - if (bizType === 'project') return '项目半月报'; + if (bizType === 'project') return '研发简报'; if (bizType === 'performance') return '绩效表'; if (bizType === 'overtime_application') return '业务学习'; return '待审批';