refactor(work-report): 将项目报告周期从月份半月改为自由的日期范围
- 移除项目报告中的 flag 字段必填限制,改为可选 - 将项目报告创建对话框中的月份和半月选择改为日期范围选择器 - 更新项目报告详情对话框,移除半月周期显示字段 - 修改项目报告搜索面板,调整周期字段标签和类型 - 更新团队报告汇总组件中的项目报告催办逻辑 - 调整项目报告表格列宽和标签显示 - 重构项目报告周期构建逻辑,支持灵活的日期范围输入 - 更新相关类型定义和 API 接口中的 flag 字段为可选 - 统一将"项目半月报"更名为"研发简报"以反映新的日期范围特性
This commit is contained in:
@@ -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': '业务学习',
|
||||
|
||||
4
src/typings/api/work-report.d.ts
vendored
4
src/typings/api/work-report.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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 的 ww(locale 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 {
|
||||
|
||||
@@ -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) }}
|
||||
|
||||
@@ -91,7 +91,7 @@ const emit = defineEmits<{
|
||||
const REPORT_TYPE_TEXT: Record<WorkReportType, string> = {
|
||||
weekly: '个人周报',
|
||||
monthly: '个人月报',
|
||||
project: '项目半月报'
|
||||
project: '研发简报'
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -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: '项目',
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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: '',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 '待审批';
|
||||
|
||||
Reference in New Issue
Block a user