feat(工作报告定时生成): 工作报告现在可以定时生成,并且可以刷新当前报告。

This commit is contained in:
dk
2026-06-13 22:13:44 +08:00
parent 80f028bcb9
commit 030dc737fc
10 changed files with 294 additions and 152 deletions

View File

@@ -310,7 +310,9 @@ function createStructuredSectionsFromTextV2(text: string, defaultCategory: strin
return;
}
const legacyMatch = trimmedLine.match(/^(.+?)\s*-\s*(.+?)(?:[(]([^()]*)[)])?[。.!?]*$/u);
// 旧格式数据是“分类 - 事项(指标)”,这里需要把括号里的指标一并交给任务解析器,
// 否则月报默认稿中的优先级/进度/工时会在这一层被截掉。
const legacyMatch = trimmedLine.match(/^(?!\d+[、.]\s*)(.+?)\s*[-]\s*(.+)$/u);
if (legacyMatch) {
const [, rawCategory, rawTaskText] = legacyMatch;
const category = rawCategory.trim();
@@ -774,6 +776,14 @@ function syncRichSupport(item: PlanItem, event: Event) {
<div class="section">
<div class="section-title">
<span>基础信息</span>
<div v-if="mode === 'edit' && !isReadonly" class="section-title-right">
<ElButton size="small" plain type="primary" @click="emit('pullDefaultDraft')">
<template #icon>
<icon-mdi-refresh class="text-icon" />
</template>
刷新
</ElButton>
</div>
</div>
<div class="compose-grid">
<div class="field">

View File

@@ -299,6 +299,14 @@ function notifyTitleSaved(item: WorkItem) {
<div class="section">
<div class="section-title">
<span>基础信息</span>
<div v-if="mode === 'edit' && !isReadonly" class="section-title-right">
<ElButton size="small" plain type="primary" @click="emit('pullDefaultDraft')">
<template #icon>
<icon-mdi-refresh class="text-icon" />
</template>
刷新
</ElButton>
</div>
</div>
<div class="compose-grid">
<div class="field">

View File

@@ -14,6 +14,9 @@ import {
fetchPreviewMonthlyReportDefaultDraft,
fetchPreviewProjectReportDefaultDraft,
fetchPreviewWeeklyReportDefaultDraft,
fetchRefreshMonthlyReportDraft,
fetchRefreshProjectReportDraft,
fetchRefreshWeeklyReportDraft,
fetchUpdateMonthlyReport,
fetchUpdateProjectReport,
fetchUpdateWeeklyReport
@@ -146,6 +149,110 @@ function patchProject(report?: Partial<Api.WorkReport.Project.ProjectReport>) {
patchPeriod(projectModel);
}
function applyWeeklyEditableFields(draft: Api.WorkReport.Weekly.WeeklyReport) {
weeklyModel.reviewItems = normalizeReviewItems(draft.reviewItems);
weeklyModel.planItems = normalizePlanItems(draft.planItems);
weeklyModel.travelSegments = draft.travelSegments || [];
}
function applyMonthlyEditableFields(draft: Api.WorkReport.Monthly.MonthlyReport) {
monthlyModel.reviewItems = normalizeReviewItems(draft.reviewItems);
monthlyModel.planItems = normalizePlanItems(draft.planItems);
}
function applyProjectEditableFields(draft: Api.WorkReport.Project.ProjectReport) {
projectModel.projectStatusDesc = draft.projectStatusDesc || '';
projectModel.projectProgressPlan = draft.projectProgressPlan || '';
projectModel.projectKeyPoints = draft.projectKeyPoints || '';
projectModel.projectProblems = draft.projectProblems || '';
projectModel.currentItems = normalizeProjectItems(draft.currentItems);
projectModel.nextItems = normalizeProjectItems(draft.nextItems);
}
function applyEditableFieldsByReportType(
draft:
| Api.WorkReport.Weekly.WeeklyReport
| Api.WorkReport.Monthly.MonthlyReport
| Api.WorkReport.Project.ProjectReport
) {
if (props.reportType === 'weekly') {
applyWeeklyEditableFields(draft as Api.WorkReport.Weekly.WeeklyReport);
return;
}
if (props.reportType === 'monthly') {
applyMonthlyEditableFields(draft as Api.WorkReport.Monthly.MonthlyReport);
return;
}
applyProjectEditableFields(draft as Api.WorkReport.Project.ProjectReport);
}
function createCurrentPeriodPayload(): PeriodPayload {
return {
periodKey: activeModel.value.periodKey,
periodLabel: activeModel.value.periodLabel,
periodStartDate: activeModel.value.periodStartDate,
periodEndDate: activeModel.value.periodEndDate
};
}
async function confirmDraftOverwrite(confirmOverwrite: boolean) {
if (!confirmOverwrite || props.operateType === 'edit') return true;
try {
await ElMessageBox.confirm('重新拉取默认稿会覆盖当前已编辑内容,是否继续?', '覆盖确认', {
type: 'warning',
confirmButtonText: '继续',
cancelButtonText: '取消'
});
return true;
} catch {
return false;
}
}
async function fetchEditDraftRefresh() {
if (props.reportType === 'weekly') {
return fetchRefreshWeeklyReportDraft(weeklyModel);
}
if (props.reportType === 'monthly') {
return fetchRefreshMonthlyReportDraft(monthlyModel);
}
return fetchRefreshProjectReportDraft(projectModel.projectId, {
periodKey: projectModel.periodKey,
periodLabel: projectModel.periodLabel,
periodStartDate: projectModel.periodStartDate,
periodEndDate: projectModel.periodEndDate,
flag: projectModel.flag,
projectStatusDesc: projectModel.projectStatusDesc,
projectProgressPlan: projectModel.projectProgressPlan,
projectKeyPoints: projectModel.projectKeyPoints,
projectProblems: projectModel.projectProblems,
currentItems: projectModel.currentItems,
nextItems: projectModel.nextItems
});
}
async function fetchDefaultDraftPreview() {
const period = createCurrentPeriodPayload();
if (props.reportType === 'weekly') {
return fetchPreviewWeeklyReportDefaultDraft(period);
}
if (props.reportType === 'monthly') {
return fetchPreviewMonthlyReportDefaultDraft(period);
}
return fetchPreviewProjectReportDefaultDraft(projectModel.projectId, {
...period,
flag: projectModel.flag
});
}
async function loadDetail() {
if (!props.rowData?.id) return;
@@ -194,56 +301,25 @@ async function loadInitAndDraft() {
}
async function pullDefaultDraft(confirmOverwrite = true) {
if (confirmOverwrite) {
try {
await ElMessageBox.confirm('重新拉取默认稿会覆盖当前已编辑内容,是否继续?', '覆盖确认', {
type: 'warning',
confirmButtonText: '继续',
cancelButtonText: '取消'
});
} catch {
return;
}
const confirmed = await confirmDraftOverwrite(confirmOverwrite);
if (!confirmed) return;
if (props.operateType === 'edit') {
const refreshResult = await fetchEditDraftRefresh();
if (refreshResult.error || !refreshResult.data) return;
applyEditableFieldsByReportType(refreshResult.data);
window.$message?.success('最新数据已刷新');
return;
}
const period = {
periodKey: activeModel.value.periodKey,
periodLabel: activeModel.value.periodLabel,
periodStartDate: activeModel.value.periodStartDate,
periodEndDate: activeModel.value.periodEndDate
};
let result;
if (props.reportType === 'weekly') {
result = await fetchPreviewWeeklyReportDefaultDraft(period);
} else if (props.reportType === 'monthly') {
result = await fetchPreviewMonthlyReportDefaultDraft(period);
} else {
result = await fetchPreviewProjectReportDefaultDraft(projectModel.projectId, {
...period,
flag: projectModel.flag
});
}
const result = await fetchDefaultDraftPreview();
if (result.error || !result.data) return;
if (props.reportType === 'weekly') {
weeklyModel.reviewItems = normalizeReviewItems((result.data as Api.WorkReport.Weekly.WeeklyReport).reviewItems);
weeklyModel.planItems = normalizePlanItems((result.data as Api.WorkReport.Weekly.WeeklyReport).planItems);
}
if (props.reportType === 'monthly') {
monthlyModel.reviewItems = normalizeReviewItems((result.data as Api.WorkReport.Monthly.MonthlyReport).reviewItems);
monthlyModel.planItems = normalizePlanItems((result.data as Api.WorkReport.Monthly.MonthlyReport).planItems);
}
if (props.reportType === 'project') {
projectModel.currentItems = normalizeProjectItems(
(result.data as Api.WorkReport.Project.ProjectReport).currentItems
);
projectModel.nextItems = normalizeProjectItems((result.data as Api.WorkReport.Project.ProjectReport).nextItems);
}
applyEditableFieldsByReportType(result.data);
}
watch(visible, isVisible => {
@@ -346,7 +422,7 @@ async function handleSubmit() {
<template #icon>
<icon-mdi-refresh class="text-icon" />
</template>
重新拉取默认稿
刷新
</ElButton>
</div>
</BusinessFormSection>

View File

@@ -19,6 +19,9 @@ import {
fetchPreviewMonthlyReportDefaultDraft,
fetchPreviewProjectReportDefaultDraft,
fetchPreviewWeeklyReportDefaultDraft,
fetchRefreshMonthlyReportDraft,
fetchRefreshProjectReportDraft,
fetchRefreshWeeklyReportDraft,
fetchRejectMonthlyReport,
fetchRejectProjectReport,
fetchRejectWeeklyReport,
@@ -243,6 +246,27 @@ function patchProject(report?: Partial<Api.WorkReport.Project.ProjectReport>) {
if (props.mode === 'add') patchPeriod(projectModel);
}
function applyWeeklyEditableFields(draft: Api.WorkReport.Weekly.WeeklyReport) {
weeklyModel.isBusinessTrip = draft.isBusinessTrip;
weeklyModel.reviewItems = draft.reviewItems?.length ? normalizeReviewItems(draft.reviewItems) : [];
weeklyModel.planItems = draft.planItems?.length ? normalizePlanItems(draft.planItems) : [];
weeklyModel.travelSegments = draft.travelSegments || [];
}
function applyMonthlyEditableFields(draft: Api.WorkReport.Monthly.MonthlyReport) {
monthlyModel.reviewItems = draft.reviewItems?.length ? normalizeReviewItems(draft.reviewItems) : [];
monthlyModel.planItems = draft.planItems?.length ? normalizePlanItems(draft.planItems) : [];
}
function applyProjectEditableFields(draft: Api.WorkReport.Project.ProjectReport) {
projectModel.projectStatusDesc = draft.projectStatusDesc || '';
projectModel.projectProgressPlan = draft.projectProgressPlan || '';
projectModel.projectKeyPoints = draft.projectKeyPoints || '';
projectModel.projectProblems = draft.projectProblems || '';
projectModel.currentItems = draft.currentItems?.length ? normalizeProjectItems(draft.currentItems) : [];
projectModel.nextItems = draft.nextItems?.length ? normalizeProjectItems(draft.nextItems) : [];
}
function firstMeaningfulValue<T>(...values: Array<T | null | undefined | ''>) {
return values.find(value => value !== null && value !== undefined && value !== '') as T | undefined;
}
@@ -346,26 +370,64 @@ async function pullDefaultDraft(confirmOverwrite = false) {
if (props.reportType === 'weekly') {
const data = result.data as Api.WorkReport.Weekly.WeeklyReport;
weeklyModel.reviewItems = data.reviewItems?.length ? normalizeReviewItems(data.reviewItems) : [];
weeklyModel.planItems = data.planItems?.length ? normalizePlanItems(data.planItems) : [];
applyWeeklyEditableFields(data);
if (confirmOverwrite) {
weeklyModel.travelSegments = data.travelSegments || [];
}
}
if (props.reportType === 'monthly') {
const data = result.data as Api.WorkReport.Monthly.MonthlyReport;
monthlyModel.reviewItems = data.reviewItems?.length ? normalizeReviewItems(data.reviewItems) : [];
monthlyModel.planItems = data.planItems?.length ? normalizePlanItems(data.planItems) : [];
applyMonthlyEditableFields(result.data as Api.WorkReport.Monthly.MonthlyReport);
}
if (props.reportType === 'project') {
const data = result.data as Api.WorkReport.Project.ProjectReport;
projectModel.currentItems = data.currentItems?.length ? normalizeProjectItems(data.currentItems) : [];
projectModel.nextItems = data.nextItems?.length ? normalizeProjectItems(data.nextItems) : [];
applyProjectEditableFields(result.data as Api.WorkReport.Project.ProjectReport);
}
}
async function refreshDraft() {
if (props.mode !== 'edit') {
await pullDefaultDraft(true);
return;
}
loading.value = true;
let result;
if (props.reportType === 'weekly') {
result = await fetchRefreshWeeklyReportDraft(weeklyModel);
} else if (props.reportType === 'monthly') {
result = await fetchRefreshMonthlyReportDraft(monthlyModel);
} else {
result = await fetchRefreshProjectReportDraft(projectModel.projectId, {
periodKey: projectModel.periodKey,
periodLabel: projectModel.periodLabel,
periodStartDate: projectModel.periodStartDate,
periodEndDate: projectModel.periodEndDate,
flag: projectModel.flag,
projectStatusDesc: projectModel.projectStatusDesc,
projectProgressPlan: projectModel.projectProgressPlan,
projectKeyPoints: projectModel.projectKeyPoints,
projectProblems: projectModel.projectProblems,
currentItems: projectModel.currentItems,
nextItems: projectModel.nextItems
});
}
loading.value = false;
if (result.error || !result.data) return;
if (props.reportType === 'weekly') {
applyWeeklyEditableFields(result.data as Api.WorkReport.Weekly.WeeklyReport);
} else if (props.reportType === 'monthly') {
applyMonthlyEditableFields(result.data as Api.WorkReport.Monthly.MonthlyReport);
} else {
applyProjectEditableFields(result.data as Api.WorkReport.Project.ProjectReport);
}
window.$message?.success('最新数据已刷新');
}
async function loadInitData() {
loading.value = true;
let result;
@@ -665,7 +727,7 @@ function handleRequestReject() {
}
function handlePullDefaultDraft() {
pullDefaultDraft(true);
refreshDraft();
}
function handleMonthlyApprovalChange(payload: Api.WorkReport.Monthly.MonthlyReportApproveParams) {

View File

@@ -1105,6 +1105,14 @@ function syncRichSupport(item: PlanItem, event: Event) {
<div class="section">
<div class="section-title">
<span>基础信息</span>
<div v-if="mode === 'edit' && !isReadonly" class="section-title-right">
<ElButton size="small" plain type="primary" @click="emit('pullDefaultDraft')">
<template #icon>
<icon-mdi-refresh class="text-icon" />
</template>
刷新
</ElButton>
</div>
</div>
<div class="compose-grid">
<div class="field">
@@ -1206,7 +1214,7 @@ function syncRichSupport(item: PlanItem, event: Event) {
class="rich-editor"
:contenteditable="!isReadonly"
spellcheck="false"
:data-placeholder="isReadonly ? undefined : '璇疯緭鍏ュ伐浣滃唴瀹瑰強鎴愭灉鎻忚堪'"
:data-placeholder="isReadonly ? undefined : '请输入具体工作内容及成果描述'"
@focus="focusEditField(`content-${index}`)"
@blur="
syncRichContent(item, $event);
@@ -1305,7 +1313,7 @@ function syncRichSupport(item: PlanItem, event: Event) {
class="rich-editor"
:contenteditable="!isReadonly"
spellcheck="false"
:data-placeholder="isReadonly ? undefined : '璇疯緭鍏ュ叿浣撶洰鏍?'"
:data-placeholder="isReadonly ? undefined : '请输入具体目标'"
@focus="focusEditField(`target-${index}`)"
@blur="
syncRichTarget(item, $event);
@@ -2099,81 +2107,6 @@ function syncRichSupport(item: PlanItem, event: Event) {
cursor: pointer;
}
.structured-preview--readonly {
display: grid;
gap: 8px;
cursor: default;
}
.structured-preview__section {
display: grid;
gap: 6px;
}
.structured-preview__section + .structured-preview__section {
padding-top: 8px;
border-top: 1px dashed #cbd5e1;
}
.structured-preview__category {
position: relative;
padding-left: 14px;
color: #0f766e;
font-size: 13px;
font-weight: 800;
line-height: 1.6;
}
.structured-preview__category::before {
content: '';
position: absolute;
left: 0;
top: 4px;
width: 4px;
height: 16px;
border-radius: 999px;
background: #0f766e;
}
.structured-preview__tasks {
display: grid;
gap: 6px;
}
.structured-preview__task-line {
display: inline-flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
color: #334155;
line-height: 1.6;
cursor: pointer;
}
.structured-preview__task-title {
color: #334155;
overflow-wrap: anywhere;
word-break: break-word;
}
.structured-preview__task-metric {
display: inline-flex;
align-items: center;
height: 20px;
padding: 0 6px;
border-radius: 999px;
background: #f3f7f9;
color: #475467;
font-size: 11px;
font-weight: 800;
white-space: nowrap;
}
.structured-preview__task-metric:first-of-type {
background: #fff7ed;
color: #c2410c;
}
.structured-preview__popover {
max-width: 100%;
color: #334155;