fix(工作报告): 修复工作报告存在的若干问题。

feat(加班申请): 支持批量审批。
This commit is contained in:
dk
2026-06-13 13:06:39 +08:00
parent 5061eced32
commit 80f028bcb9
19 changed files with 1845 additions and 790 deletions

View File

@@ -70,7 +70,7 @@ const table = useUIPaginatedTable<
{ prop: 'periodLabel', label: '月份', minWidth: 80, formatter: row => formatPeriod(row) },
{
prop: 'reporterDeptName',
label: '部门/方向',
label: '部门',
minWidth: 80,
showOverflowTooltip: true,
formatter: row => row.reporterDeptName || '--'

View File

@@ -1,8 +1,9 @@
<script setup lang="ts">
/* eslint-disable vue/no-mutating-props, unicorn/prefer-dom-node-text-content, no-useless-escape, no-nested-ternary, no-plusplus, @typescript-eslint/no-shadow */
import { computed, reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { Plus } from '@element-plus/icons-vue';
import { RDMS_REQ_PRIORITY_DICT_CODE, RDMS_TASK_ITEM_TYPE_DICT_CODE } from '@/constants/dict';
import { fetchGetMyParticipatedProjectPage } from '@/service/api';
import { useDict } from '@/hooks/business/dict';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import DictSelect from '@/components/custom/dict-select.vue';
@@ -59,12 +60,14 @@ interface StructuredTask {
priority?: string;
progress?: number;
hours?: number;
detail?: string;
}
interface PlanTaskDraft {
title: string;
priority?: StructuredTask['priority'];
progress: number;
detail?: string;
}
interface StructuredSection {
@@ -106,6 +109,37 @@ const planForm = ref({
const activePlanSectionIndex = ref(-1);
/** 工作事项下拉里的"我的事项"固定项,用于区分纯个人事务。 */
const MY_AFFAIRS_TITLE = '我的事项';
/** 「我参与的项目」项目名清单,下拉框数据源。 */
const participatedProjectNames = ref<string[]>([]);
async function loadParticipatedProjectNames() {
// pageSize=-1 一次拉全部(不分页),由后端按"进行中 + 创建时间升序"过滤排序。
const { data, error } = await fetchGetMyParticipatedProjectPage({ pageNo: 1, pageSize: -1 });
if (error || !data) {
participatedProjectNames.value = [];
return;
}
participatedProjectNames.value = data.list
.map(project => project.name?.trim())
.filter((name): name is string => Boolean(name));
}
onMounted(loadParticipatedProjectNames);
const reviewWorkItemOptions = computed(() => {
// 下拉数据来自「我参与的项目」API 拉取的项目名 + 「我的事项」固定项,
// 与周报共用同一份数据源fetchGetMyParticipatedProjectPage, pageSize=-1
const names = new Set<string>(participatedProjectNames.value);
return [MY_AFFAIRS_TITLE, ...Array.from(names).filter(name => name !== MY_AFFAIRS_TITLE)];
});
/** 选中"我的事项"时,事项不参与项目优先级,需要隐藏优先级相关展示。 */
const isMyAffairsPlanItem = (workItem: string) => workItem.trim() === MY_AFFAIRS_TITLE;
const DEFAULT_SECTION_CATEGORY = '工作内容';
function createPlanTaskDraft(): PlanTaskDraft {
return { title: '', priority: '2', progress: 0 };
}
@@ -115,7 +149,8 @@ function normalizeTask(task: WorkReportStructuredTask): StructuredTask {
title: task.title || '',
priority: normalizePriorityCode(task.priority),
progress: typeof task.progress === 'number' ? task.progress : undefined,
hours: typeof task.hours === 'number' ? task.hours : undefined
hours: typeof task.hours === 'number' ? task.hours : undefined,
detail: task.detail || ''
};
}
@@ -147,16 +182,14 @@ function resolveTaskItemTypeLabel(value: string) {
return getTaskItemTypeLabel(value, { fallback: value || '工作内容' });
}
function normalizeSection(section: WorkReportStructuredSection): StructuredSection {
return {
category: resolveTaskItemTypeLabel(section.category || '工作内容'),
tasks: section.tasks.map(normalizeTask)
};
function normalizeSectionCategory(value?: string | null, fallback = DEFAULT_SECTION_CATEGORY) {
const category = resolveTaskItemTypeLabel(value || '').trim();
return category || fallback;
}
function normalizeSectionForMerge(section: WorkReportStructuredSection): StructuredSection {
function normalizeSection(section: WorkReportStructuredSection): StructuredSection {
return {
category: section.category || '工作内容',
category: normalizeSectionCategory(section.category),
tasks: section.tasks.map(normalizeTask)
};
}
@@ -165,7 +198,7 @@ function mergeSectionsByCategory(sections: StructuredSection[]) {
const sectionMap = new Map<string, StructuredSection>();
sections.forEach(section => {
const category = section.category || '未分类';
const category = normalizeSectionCategory(section.category, '未分类');
const existing = sectionMap.get(category);
if (existing) {
existing.tasks.push(...section.tasks);
@@ -191,7 +224,10 @@ function resolveTaskMetricsV2(metricsText: string) {
.map(item => item.trim())
.filter(Boolean);
const priority = normalizePriorityCode(parts.find(item => /^P?\d+$/iu.test(item)));
const progressText = metricsText.match(/进度\s*(\d+(?:\.\d+)?)%/u)?.[1];
// 同时支持 "进度 XX%" 与裸 "XX%",避免失焦后丢失进度。
const progressText =
metricsText.match(/进度\s*(\d+(?:\.\d+)?)%/u)?.[1] ||
parts.find(item => /^\d+(?:\.\d+)?%$/u.test(item))?.replace(/%$/u, '');
const hoursText = metricsText.match(/(\d+(?:\.\d+)?)h/u)?.[1];
return {
@@ -215,7 +251,7 @@ function formatStructuredTaskLineV2(task: StructuredTask, showHours = false) {
function createStructuredTextV2(sections: StructuredSection[], showHours = false) {
return sections
.map(section => {
const categoryLabel = resolveTaskItemTypeLabel(section.category).trim();
const categoryLabel = normalizeSectionCategory(section.category);
return [
`#${categoryLabel}`,
...section.tasks.map((task, index) => `${index + 1}${formatStructuredTaskLineV2(task, showHours)}`)
@@ -229,7 +265,7 @@ function createStructuredTextV2(sections: StructuredSection[], showHours = false
function createStructuredHtmlV2(sections: StructuredSection[], showHours = false) {
return sections
.map(section => {
const categoryLabel = resolveTaskItemTypeLabel(section.category.trim());
const categoryLabel = normalizeSectionCategory(section.category);
const tasks = section.tasks
.map(
(task, index) =>
@@ -253,7 +289,7 @@ function createStructuredSectionsFromTextV2(text: string, defaultCategory: strin
const sections: StructuredSection[] = [];
const ensureSection = (categoryText: string) => {
const category = categoryText.trim() || defaultCategory;
const category = normalizeSectionCategory(categoryText, defaultCategory);
let section = sections.find(item => item.category === category);
if (!section) {
section = { category, tasks: [] };
@@ -276,63 +312,110 @@ function createStructuredSectionsFromTextV2(text: string, defaultCategory: strin
const legacyMatch = trimmedLine.match(/^(.+?)\s*-\s*(.+?)(?:[(]([^()]*)[)])?[。.!?]*$/u);
if (legacyMatch) {
const [, rawCategory, rawTitle, metricsText = ''] = legacyMatch;
const [, rawCategory, rawTaskText] = legacyMatch;
const category = rawCategory.trim();
const title = rawTitle.trim();
if (!category || !title) return;
const task = parseStructuredSectionTaskText(rawTaskText);
if (!category || !task) return;
ensureSection(category).tasks.push({
title,
...resolveTaskMetricsV2(metricsText)
});
ensureSection(category).tasks.push(task);
currentCategory = category;
return;
}
const normalizedLine = stripStructuredTaskSuffixV2(stripStructuredTaskPrefixV2(trimmedLine));
if (!normalizedLine) return;
const inlineMatch = normalizedLine.match(/^(.*?)(?:[(]([^()]*)[)])?$/u);
const title = inlineMatch?.[1]?.trim() || '';
const metricsText = inlineMatch?.[2]?.trim() || '';
if (!title) return;
const task = parseStructuredSectionTaskText(trimmedLine);
if (!task) return;
ensureSection(currentCategory || defaultCategory).tasks.push({
title,
...resolveTaskMetricsV2(metricsText)
});
const hasStructuredHint =
/^(\d+[..、]\s*)/u.test(trimmedLine) ||
trimmedLine.includes('') ||
trimmedLine.includes('(') ||
trimmedLine.includes('') ||
trimmedLine.includes(':');
if (!hasStructuredHint && !currentCategory) return;
ensureSection(currentCategory || defaultCategory).tasks.push(task);
});
return sections.filter(section => section.tasks.length);
}
function parseStructuredSectionsFromEditorV2(editor: HTMLElement, defaultCategory = '工作内容'): StructuredSection[] {
return createStructuredSectionsFromTextV2(editor.innerText, defaultCategory);
}
function parseStructuredSectionTaskText(
text: string,
fallback?: Partial<StructuredTask>,
useFallback = false
): StructuredTask | null {
const normalizedText = stripStructuredTaskPrefixV2(text);
if (!normalizedText) return null;
function createStructuredText(sections: StructuredSection[], showHours = false) {
const stripOrderPrefix = (text: string) => text.trim().replace(/^\d+[..、]\s*/, '');
const formatTaskText = (task: StructuredTask, taskIndex: number, showHours: boolean) => {
const rawTitle = stripOrderPrefix(task.title);
const punctuation = rawTitle.match(/[。.!?]$/)?.[0] || '';
const title = punctuation ? rawTitle.slice(0, -1) : rawTitle;
const metrics = [
task.priority ? resolvePriorityLabel(task.priority) : '',
typeof task.progress === 'number' ? `进度${task.progress}%` : '',
showHours && typeof task.hours === 'number' ? `${task.hours}h` : ''
]
.filter(Boolean)
.join('、');
return `${taskIndex + 1}.${title}${metrics ? `${metrics}` : ''}${punctuation}`;
const structuredMatch =
normalizedText.match(/^(.+?)(?:[(]([^()]*)[)])?(?:\s*[:]\s*(.*))?$/u) ||
normalizedText.match(/^(.+?)(?:\(([^()]*)\))?(?::\s*(.*))?$/u);
if (!structuredMatch) return null;
const [, rawTitle, metricsText = '', detail = ''] = structuredMatch;
const title = stripStructuredTaskSuffixV2(rawTitle);
if (!title) return null;
return {
title,
detail: detail.trim(),
...resolveTaskMetrics(metricsText, fallback, useFallback)
};
return sections
.map(section => {
const categoryLabel = resolveTaskItemTypeLabel(section.category);
const tasksText = section.tasks.map((task, taskIndex) => formatTaskText(task, taskIndex, showHours)).join('');
return [categoryLabel, tasksText ? ` ${tasksText}` : ''].filter(Boolean).join('\n');
})
.join('\n');
}
function createFallbackTaskLookup(sections: StructuredSection[]) {
const lookup = new Map<string, StructuredTask[]>();
sections.forEach(section => {
const categoryKey = normalizeSectionCategory(section.category);
section.tasks.forEach(task => {
const titleKey = stripStructuredTaskSuffixV2(stripStructuredTaskPrefixV2(task.title));
const key = `${categoryKey}||${titleKey}`;
const list = lookup.get(key);
if (list) {
list.push(task);
} else {
lookup.set(key, [task]);
}
});
});
return lookup;
}
function parseStructuredSectionsFromEditorV2(
editor: HTMLElement,
fallbackSections: StructuredSection[] = [],
defaultCategory = DEFAULT_SECTION_CATEGORY
): StructuredSection[] {
const parsedSections = createStructuredSectionsFromTextV2(editor.innerText, defaultCategory);
if (!parsedSections.length) return [];
const fallbackLookup = createFallbackTaskLookup(fallbackSections);
return parsedSections.map((section, sectionIndex) => {
const fallbackSection = fallbackSections[sectionIndex];
const categoryKey = normalizeSectionCategory(section.category);
return {
category: section.category,
tasks: section.tasks.map((task, taskIndex) => {
const titleKey = stripStructuredTaskSuffixV2(stripStructuredTaskPrefixV2(task.title));
const matchedByKey = fallbackLookup.get(`${categoryKey}||${titleKey}`)?.shift();
const matchedByIndex = fallbackSection?.tasks[taskIndex];
const fallbackTask = matchedByKey || matchedByIndex;
return {
...task,
detail: fallbackTask?.detail || task.detail || '',
hours: task.hours ?? fallbackTask?.hours
};
})
};
});
}
function escapeHtml(value: string) {
return value
.replace(/&/g, '&amp;')
@@ -482,63 +565,19 @@ function resolveTaskMetrics(metricsText: string, fallback?: Partial<StructuredTa
hours: hoursText === undefined ? (useFallback ? fallback?.hours : undefined) : Number(hoursText)
};
}
function parseStructuredSectionsFromEditor(
editor: HTMLElement,
fallbackSections: StructuredSection[] = [],
defaultCategory = '工作内容'
) {
const sectionElements = Array.from(editor.querySelectorAll<HTMLElement>('.rich-section'));
if (!sectionElements.length) {
return createStructuredSectionsFromText(editor.innerText, defaultCategory);
}
return sectionElements
.map((sectionElement, sectionIndex) => {
const fallbackSection = fallbackSections[sectionIndex];
const category =
getElementText(sectionElement.querySelector('.rich-section-title')) ||
fallbackSection?.category ||
defaultCategory;
const taskElements = Array.from(sectionElement.querySelectorAll<HTMLElement>('.rich-section-task'));
const tasks = taskElements
.map((taskElement, taskIndex) => {
const fallbackTask = fallbackSection?.tasks[taskIndex];
const title = stripTaskOrderPrefix(getElementText(taskElement.querySelector('.rich-section-task-title')));
const metricsElement = taskElement.querySelector('.rich-inline-metrics');
const metrics = metricsElement
? resolveTaskMetrics(getElementText(metricsElement), fallbackTask, false)
: { priority: undefined, progress: undefined, hours: undefined };
if (!title) return null;
return {
title,
...metrics
};
})
.filter(Boolean) as StructuredTask[];
if (!category && !tasks.length) return null;
return {
category,
tasks
};
})
.filter(Boolean) as StructuredSection[];
}
function createSectionsJson(sections: StructuredSection[]) {
return sections.length ? JSON.stringify({ sections }) : null;
}
function toReviewItem(item: Api.WorkReport.Common.PersonalReportReviewItem): ReviewItem {
function getReviewItemSections(item: Api.WorkReport.Common.PersonalReportReviewItem) {
const structuredSections = mergeSectionsByCategory(getStructuredSections(item.contentJson).map(normalizeSection));
const contentSections = structuredSections.length
? structuredSections
: createStructuredSectionsFromTextV2(item.contentText || '', item.itemTitle || '未分类');
if (structuredSections.length) return structuredSections;
return createStructuredSectionsFromTextV2(item.contentText || '', item.itemTitle || DEFAULT_SECTION_CATEGORY);
}
function toReviewItem(item: Api.WorkReport.Common.PersonalReportReviewItem): ReviewItem {
const contentSections = getReviewItemSections(item);
return {
workItem: item.itemTitle || '未命名工作',
@@ -555,11 +594,15 @@ function toReviewItem(item: Api.WorkReport.Common.PersonalReportReviewItem): Rev
};
}
function toPlanItem(item: Api.WorkReport.Common.PersonalReportPlanItem, index: number): PlanItem {
function getPlanItemSections(item: Api.WorkReport.Common.PersonalReportPlanItem) {
const structuredSections = mergeSectionsByCategory(getStructuredSections(item.targetJson).map(normalizeSection));
const targetSections = structuredSections.length
? structuredSections
: createStructuredSectionsFromTextV2(item.targetText || '', item.itemTitle || '未分类');
if (structuredSections.length) return structuredSections;
return createStructuredSectionsFromTextV2(item.targetText || '', item.itemTitle || DEFAULT_SECTION_CATEGORY);
}
function toPlanItem(item: Api.WorkReport.Common.PersonalReportPlanItem, index: number): PlanItem {
const targetSections = getPlanItemSections(item);
return {
workItem: item.itemTitle || '未命名计划',
@@ -569,7 +612,6 @@ function toPlanItem(item: Api.WorkReport.Common.PersonalReportPlanItem, index: n
: escapeHtml(item.targetText || '').replace(/\n/g, '<br>') || EMPTY_TEXT,
targetSections,
supportNeed: item.supportNeed || '',
/* 只有用户新增的计划才可删除,默认带入的不可删除 */
removable: true,
source: item,
sourceIndex: index
@@ -628,11 +670,16 @@ function removePlanSection(index: number) {
function addPlanTask(sectionIndex: number) {
const section = planForm.value.sections[sectionIndex];
if (!section?.draft.title.trim()) return;
section.tasks.push({
const task: StructuredTask = {
title: section.draft.title.trim(),
priority: section.draft.priority,
progress: section.draft.progress
});
progress: section.draft.progress,
detail: section.draft.detail?.trim() || undefined
};
// 我的事项不参与项目优先级,存进任务前显式剔除,结构化展示括号里就不会出现优先级。
if (!isMyAffairsPlanItem(planForm.value.workItem)) {
task.priority = section.draft.priority;
}
section.tasks.push(task);
section.draft = createPlanTaskDraft();
}
@@ -644,25 +691,17 @@ function submitInlinePlan() {
const sections = planForm.value.sections
.filter(section => section.category.trim() && section.tasks.length)
.map(section => ({
category: section.category.trim(),
tasks: section.tasks
category: normalizeSectionCategory(section.category),
tasks: section.tasks.map(task => ({ ...task }))
}));
if (!sections.length) return;
const target = createStructuredTextV2(sections);
const workItem = planForm.value.workItem.trim();
const sameWorkItem = props.model.planItems.find(item => item.itemTitle.trim() === workItem);
if (sameWorkItem) {
const mergedSections = getStructuredSections(sameWorkItem.targetJson).map(normalizeSectionForMerge);
sections.forEach(section => {
const sameCategory = mergedSections.find(item => item.category === section.category);
if (sameCategory) {
sameCategory.tasks.push(...section.tasks);
} else {
mergedSections.push(section);
}
});
const mergedSections = mergeSectionsByCategory([...getPlanItemSections(sameWorkItem), ...sections]);
sameWorkItem.targetText = createStructuredTextV2(mergedSections);
sameWorkItem.targetJson = JSON.stringify({ sections: mergedSections });
sameWorkItem.targetJson = createSectionsJson(mergedSections);
if (planForm.value.supportNeed.trim()) {
sameWorkItem.supportNeed = [sameWorkItem.supportNeed, planForm.value.supportNeed.trim()]
.filter(Boolean)
@@ -673,7 +712,7 @@ function submitInlinePlan() {
itemNumber: props.model.planItems.length + 1,
itemTitle: workItem,
targetText: target,
targetJson: JSON.stringify({ sections }),
targetJson: createSectionsJson(sections),
supportNeed: planForm.value.supportNeed,
_isNew: true
} as Api.WorkReport.Common.PersonalReportPlanItem & { _isNew: boolean });
@@ -698,15 +737,25 @@ function blurEditField(key: string) {
function syncRichContent(item: ReviewItem, event: Event) {
const target = event.currentTarget as HTMLElement;
if (!item.source) return;
const sections = parseStructuredSectionsFromEditorV2(target, item.workItem || '未分类');
const sections = parseStructuredSectionsFromEditorV2(
target,
item.contentSections || [],
item.contentSections?.[0]?.category || DEFAULT_SECTION_CATEGORY
);
item.source.contentJson = createSectionsJson(sections);
item.source.contentText = createStructuredTextV2(sections, true);
}
function syncRichTarget(item: PlanItem, event: Event) {
const target = event.currentTarget as HTMLElement;
if (!item.source) return;
const sections = parseStructuredSectionsFromEditorV2(target, item.workItem || '未分类');
const sections = parseStructuredSectionsFromEditorV2(
target,
item.targetSections || [],
item.targetSections?.[0]?.category || DEFAULT_SECTION_CATEGORY
);
item.source.targetJson = createSectionsJson(sections);
item.source.targetText = createStructuredTextV2(sections);
}
function syncRichReflection(item: ReviewItem, event: Event) {
@@ -732,7 +781,7 @@ function syncRichSupport(item: PlanItem, event: Event) {
<ElInput v-model="mainForm.reporter" disabled />
</div>
<div class="field">
<label>部门/方向</label>
<label>部门</label>
<ElInput v-model="mainForm.deptName" disabled />
</div>
<div class="field">
@@ -1051,14 +1100,18 @@ function syncRichSupport(item: PlanItem, event: Event) {
>
<div class="plan-dialog-form">
<div class="field">
<label>项目名/事项</label>
<ElInput
<label>工作事项</label>
<ElSelect
v-model="planForm.workItem"
class="inline-plan-name-input"
type="textarea"
:rows="2"
placeholder="请输入项目名或我的事项"
/>
filterable
allow-create
default-first-option
clearable
placeholder="请选择项目名/我的事项"
>
<ElOption v-for="item in reviewWorkItemOptions" :key="item" :label="item" :value="item" />
</ElSelect>
</div>
<div class="field">
<label>具体目标</label>
@@ -1070,15 +1123,18 @@ function syncRichSupport(item: PlanItem, event: Event) {
:class="{ active: activePlanSectionIndex === sIdx }"
>
<div class="plan-section-head">
<DictSelect
v-model="section.category"
:dict-code="RDMS_TASK_ITEM_TYPE_DICT_CODE"
size="small"
placeholder="选择类别"
style="width: 100%"
@focus="activePlanSectionIndex = sIdx"
@change="activePlanSectionIndex = sIdx"
/>
<div class="field plan-section-head-field">
<label>类别</label>
<DictSelect
v-model="section.category"
:dict-code="RDMS_TASK_ITEM_TYPE_DICT_CODE"
size="small"
placeholder="选择类别"
style="width: 100%"
@focus="activePlanSectionIndex = sIdx"
@change="activePlanSectionIndex = sIdx"
/>
</div>
<ElButton
v-if="planForm.sections.length > 1"
link
@@ -1093,14 +1149,20 @@ function syncRichSupport(item: PlanItem, event: Event) {
<div class="plan-task-head">
<span>{{ task.title }}</span>
<div class="plan-task-metas">
<em>{{ resolvePriorityLabel(task.priority) }}</em>
<template v-if="!isMyAffairsPlanItem(planForm.workItem)">
<em>{{ resolvePriorityLabel(task.priority) }}</em>
</template>
<em>进度 {{ task.progress }}%</em>
</div>
<ElButton link type="danger" size="small" @click="removePlanTask(sIdx, tIdx)">删除</ElButton>
</div>
<div v-if="task.detail" class="plan-task-detail">{{ task.detail }}</div>
</div>
<div class="plan-task-form">
<div class="inline-task-row">
<div
class="inline-task-row"
:class="{ 'inline-task-row--my-affairs': isMyAffairsPlanItem(planForm.workItem) }"
>
<div class="field">
<label>任务名/事项名</label>
<ElInput
@@ -1110,7 +1172,7 @@ function syncRichSupport(item: PlanItem, event: Event) {
@focus="activePlanSectionIndex = sIdx"
/>
</div>
<div class="field">
<div v-if="!isMyAffairsPlanItem(planForm.workItem)" class="field">
<label>优先级</label>
<DictSelect
v-model="section.draft.priority"
@@ -1135,6 +1197,17 @@ function syncRichSupport(item: PlanItem, event: Event) {
/>
</div>
</div>
<div class="field">
<label>详细内容</label>
<ElInput
v-model="section.draft.detail"
size="small"
type="textarea"
:rows="3"
placeholder="详细内容可选"
@focus="activePlanSectionIndex = sIdx"
/>
</div>
<ElButton
class="dialog-inline-action"
type="primary"
@@ -1150,7 +1223,7 @@ function syncRichSupport(item: PlanItem, event: Event) {
</div>
<ElButton
v-if="!planForm.sections.length || planForm.sections.every(s => s.category.trim())"
class="dialog-inline-action dialog-inline-action--secondary"
class="dialog-inline-action"
type="primary"
plain
size="small"
@@ -1885,6 +1958,10 @@ function syncRichSupport(item: PlanItem, event: Event) {
gap: 12px;
}
.inline-task-row--my-affairs {
grid-template-columns: minmax(0, 1fr) 142px;
}
.plan-sections {
display: grid;
gap: 10px;
@@ -1910,7 +1987,7 @@ function syncRichSupport(item: PlanItem, event: Event) {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 8px;
align-items: center;
align-items: end;
}
.plan-section-head :deep(.el-select__wrapper) {
@@ -1932,6 +2009,14 @@ function syncRichSupport(item: PlanItem, event: Event) {
margin-top: 0;
}
.plan-task-detail {
color: #475569;
font-size: 12px;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
}
.plan-task-head {
display: flex;
justify-content: space-between;
@@ -2042,10 +2127,6 @@ function syncRichSupport(item: PlanItem, event: Event) {
border-radius: 999px;
}
.dialog-inline-action--secondary {
justify-self: flex-end;
}
.dialog-inline-action :deep(.el-icon) {
font-size: 13px;
}
@@ -2130,12 +2211,20 @@ function syncRichSupport(item: PlanItem, event: Event) {
}
.form-actions {
position: sticky;
z-index: 5;
bottom: 0;
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: auto;
margin-bottom: 0;
padding: 14px 20px;
border-top: 1px solid #d8e0e8;
background: #fff;
border-bottom-left-radius: 18px;
border-bottom-right-radius: 18px;
background: #f5f7fa;
box-shadow: 0 -8px 18px rgba(15, 23, 42, 0.06);
}
.btn-submit {