refactor(work-report): 将项目报告周期从月份半月改为自由的日期范围

- 移除项目报告中的 flag 字段必填限制,改为可选
- 将项目报告创建对话框中的月份和半月选择改为日期范围选择器
- 更新项目报告详情对话框,移除半月周期显示字段
- 修改项目报告搜索面板,调整周期字段标签和类型
- 更新团队报告汇总组件中的项目报告催办逻辑
- 调整项目报告表格列宽和标签显示
- 重构项目报告周期构建逻辑,支持灵活的日期范围输入
- 更新相关类型定义和 API 接口中的 flag 字段为可选
- 统一将"项目半月报"更名为"研发简报"以反映新的日期范围特性
This commit is contained in:
dk
2026-07-24 10:55:14 +08:00
parent aa683f892b
commit a9c6ddccba
12 changed files with 175 additions and 205 deletions

View File

@@ -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': '业务学习',

View File

@@ -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;

View File

@@ -111,7 +111,7 @@ const teamContext = computed<TeamViewContext | null>(() => {
};
});
/** 项目选项是否加载成功(用于项目半月报列表内部判断) */
/** 项目选项是否加载成功(用于项目报列表内部判断) */
const projectOptionsLoaded = ref(false);
const visibleTabs = computed<Array<{ label: string; name: WorkReportType }>>(() => {
@@ -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;
}

View File

@@ -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[] {

View File

@@ -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<string[]>([]);
// 自定义周报周期:无论用户点哪一天,都归一到该 ISO 周的周一,
// 这样 ElDatePicker 使用内置 dayjs 的 wwlocale week也能正确显示 ISO 周数。
@@ -74,10 +73,7 @@ const selectedReportType = computed<WorkReportType>(() => {
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<WorkReportPeriodOption['period'] | null>(() => {
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<WorkReportPeriodOption['period'] | null>(() => {
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() {
</div>
<div class="work-report-create-dialog__section">
<div class="work-report-create-dialog__grid is-period">
<div v-if="!isProjectReport" class="work-report-create-dialog__grid is-period">
<button
v-for="item in activePeriodOptions"
:key="item.key"
@@ -214,18 +216,16 @@ function handleConfirm() {
>
<div class="work-report-create-dialog__choice-title">自定义周期</div>
<div class="work-report-create-dialog__choice-desc">
{{
selectedReportType === 'weekly'
? '选择某一周作为周报周期。'
: selectedReportType === 'monthly'
? '选择某一月作为月报周期。'
: '选择某个月的上半月或下半月。'
}}
{{ selectedReportType === 'weekly' ? '选择某一周作为周报周期。' : '选择某一月作为月报周期。' }}
</div>
</button>
</div>
<div v-if="selectedPeriodKey === 'custom'" class="work-report-create-dialog__custom-period">
<div
v-if="isProjectReport || selectedPeriodKey === 'custom'"
class="work-report-create-dialog__custom-period"
:class="{ 'work-report-create-dialog__custom-period--project': isProjectReport }"
>
<div v-if="selectedReportType === 'weekly'" class="work-report-create-dialog__custom-row">
<div class="work-report-create-dialog__field work-report-create-dialog__field--inline">
<label class="work-report-create-dialog__label">周报周期</label>
@@ -260,26 +260,18 @@ function handleConfirm() {
</div>
<div v-else class="work-report-create-dialog__custom-project">
<div class="work-report-create-dialog__custom-project-grid">
<div class="work-report-create-dialog__custom-project-item">
<div class="work-report-create-dialog__custom-project-item-label">选择月份</div>
<ElDatePicker
v-model="customProjectMonth"
class="w-full"
type="month"
value-format="YYYY-MM"
popper-class="work-report-create-date-popper"
placeholder="请选择月份"
/>
</div>
<div class="work-report-create-dialog__custom-project-item">
<div class="work-report-create-dialog__custom-project-item-label">选择半月</div>
<ElSegmented
v-model="customProjectFlag"
:options="projectHalfOptions"
class="work-report-create-dialog__half-segmented"
/>
</div>
<div class="work-report-create-dialog__custom-project-item">
<div class="work-report-create-dialog__custom-project-item-label">选择周期</div>
<ElDatePicker
v-model="customProjectDateRange"
class="w-full"
type="daterange"
value-format="YYYY-MM-DD"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
popper-class="work-report-create-date-popper"
/>
</div>
<div v-if="customPeriodPreviewLabel" class="work-report-create-dialog__period-preview">
<ElIcon class="work-report-create-dialog__period-preview-icon"><Calendar /></ElIcon>
@@ -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 {

View File

@@ -12,7 +12,6 @@ import {
formatPeriod,
formatPeriodDateRange,
formatWeeklyPeriodLabel,
getProjectReportFlagLabel,
getWorkReportStatusLabel
} from '../types';
@@ -100,9 +99,6 @@ function getPersonalDetail() {
<BusinessFormSection title="项目信息">
<ElDescriptions :column="2" border size="small">
<ElDescriptionsItem label="项目名称">{{ getProjectDetail()?.projectName }}</ElDescriptionsItem>
<ElDescriptionsItem label="半月周期">
{{ getProjectReportFlagLabel(getProjectDetail()?.flag) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="项目负责人">{{ getProjectDetail()?.projectOwnerName }}</ElDescriptionsItem>
<ElDescriptionsItem label="技术负责人">
{{ formatEmptyText(getProjectDetail()?.technicalOwnerName) }}

View File

@@ -91,7 +91,7 @@ const emit = defineEmits<{
const REPORT_TYPE_TEXT: Record<WorkReportType, string> = {
weekly: '个人周报',
monthly: '个人月报',
project: '项目半月报'
project: '研发简报'
};
const loading = ref(false);

View File

@@ -58,7 +58,7 @@ const fields = computed<SearchField[]>(() => {
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<SearchField[]>(() => {
if (props.reportType === 'project') {
return [
baseFields[0],
monthPeriodField,
{ key: 'periodStartDate', label: '周期', type: 'dateRange', placeholder: '请选择周期' },
{
key: 'projectId',
label: '项目',

View File

@@ -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');
}

View File

@@ -38,13 +38,13 @@ export const WORK_REPORT_PROJECT_OWNER_PERMISSION = 'project:work-report:project
export const WORK_REPORT_TYPE_LABEL: Record<WorkReportType, string> = {
weekly: '个人周报',
monthly: '个人月报',
project: '项目半月报'
project: '研发简报'
};
export const TEAM_WORK_REPORT_TYPE_LABEL: Record<WorkReportType, string> = {
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<WorkReportRow, 'periodLabel' | 'periodStartDate' | 'periodEndDate'>) {
@@ -472,7 +472,7 @@ export function createProjectSaveParams(
periodLabel: '',
periodStartDate: '',
periodEndDate: '',
flag: 1,
flag: undefined,
projectStatusDesc: '',
projectProgressPlan: '',
projectKeyPoints: '',

View File

@@ -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);
}

View File

@@ -147,7 +147,7 @@ const deadlineFilters: Array<{ key: Exclude<WorkbenchTodoDeadlineFilter, null>;
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<ApprovalBizType, 'performance' | 'monthly'>; 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 '待审批';