Commit 3963f7ff authored by drigle's avatar drigle

fix: 优化项目视图、任务状态与导入日期处理

parent 75369fa1
...@@ -93,7 +93,7 @@ Layout route/path change + 60s timer + notification-state event ...@@ -93,7 +93,7 @@ Layout route/path change + 60s timer + notification-state event
- HeroUI Pro 当前没有 Gantt 组件;甘特图继续复用项目既有日期窗口、列计算和任务区间算法,不误用垂直事件 `Timeline`。 - HeroUI Pro 当前没有 Gantt 组件;甘特图继续复用项目既有日期窗口、列计算和任务区间算法,不误用垂直事件 `Timeline`。
- 可见结构使用 HeroUI 原生组合:L1 `Card` 承载分析模块,L2 `Surface secondary` 承载时间轴,L3 `Surface tertiary` 承载刻度和任务行。 - 可见结构使用 HeroUI 原生组合:L1 `Card` 承载分析模块,L2 `Surface secondary` 承载时间轴,L3 `Surface tertiary` 承载刻度和任务行。
- 实际进度与时间进度使用 `ProgressBar` compound API;结果状态使用语义 `Chip`,空范围使用 Pro `EmptyState`。 - 实际进度与时间进度使用 `ProgressBar` compound API;项目业务状态与日期派生结果在甘特图展示层合并为唯一语义状态标签,空范围使用 Pro `EmptyState`。
- 共享 CSS 只控制网格、宽度、间距、对齐与横向滚动;组件颜色、圆角、描边和交互状态由 HeroUI variant 与鑫元共享 Tokens 负责。 - 共享 CSS 只控制网格、宽度、间距、对齐与横向滚动;组件颜色、圆角、描边和交互状态由 HeroUI variant 与鑫元共享 Tokens 负责。
- 图表颜色由 `tokens.css` 的共享 `--rc-chart-*` 序列和 `XinyuanEChart` 适配器统一提供,优先使用品牌红深浅、暖色、中性和灰蓝层级;业务页面不得添加绿色、荧光色或页面私有图表色。 - 图表颜色由 `tokens.css` 的共享 `--rc-chart-*` 序列和 `XinyuanEChart` 适配器统一提供,优先使用品牌红深浅、暖色、中性和灰蓝层级;业务页面不得添加绿色、荧光色或页面私有图表色。
...@@ -102,6 +102,7 @@ Layout route/path change + 60s timer + notification-state event ...@@ -102,6 +102,7 @@ Layout route/path change + 60s timer + notification-state event
- 个人日历直接使用 HeroUI Pro `Agenda` 的 month compound API:`Header`、`Navigation`、`Body`、`MonthGrid`、`MonthRow`、`MonthCell`、`MonthSpanningEvent` 与 `MonthEvent`。 - 个人日历直接使用 HeroUI Pro `Agenda` 的 month compound API:`Header`、`Navigation`、`Body`、`MonthGrid`、`MonthRow`、`MonthCell`、`MonthSpanningEvent` 与 `MonthEvent`。
- Agenda Header 的月份导航由官方 `NavButton`、受控 `heading` 和 `TodayButton` 组成,顺序固定为上月、当前年月、下月、今天;Header 水平与垂直 inset 均与甘特图 Card Header 对齐。 - Agenda Header 的月份导航由官方 `NavButton`、受控 `heading` 和 `TodayButton` 组成,顺序固定为上月、当前年月、下月、今天;Header 水平与垂直 inset 均与甘特图 Card Header 对齐。
- 页面只把 `pm_project_task` 的计划开始/结束日期转换为只读全天事件;Agenda 持有月份矩阵、跨周分段、非本月日期、今日状态和键盘交互。 - 页面只把 `pm_project_task` 的计划开始/结束日期转换为只读全天事件;Agenda 持有月份矩阵、跨周分段、非本月日期、今日状态和键盘交互。
- 页面按 Record `_id` 对日历输入去重,并在月视图的第一个可见事件片段展示完整标题;同一事件的后续跨周片段仅展示“持续中”,同时保留可访问名称和点击编辑能力。
- `onEventSelect` 继续映射到既有项目编辑回调;不传入创建、移动、缩放或删除回调,日历不会修改业务数据。 - `onEventSelect` 继续映射到既有项目编辑回调;不传入创建、移动、缩放或删除回调,日历不会修改业务数据。
- 外层业务模块使用批准的 HeroUI Card contract;窄视口只在 Agenda Body 内横向滚动,不允许形成页面级横向溢出。 - 外层业务模块使用批准的 HeroUI Card contract;窄视口只在 Agenda Body 内横向滚动,不允许形成页面级横向溢出。
......
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
- 图表:ECharts + `echarts-for-react` 通过共享 `XinyuanEChart` 适配器渲染;应用扩展 `--rc-chart-*` 使用 `#DA241A` 品牌红及低饱和红、暖色、中性、灰蓝层级,禁止绿色系、荧光色和彩虹式相邻对撞。适配器读取扩展色板后再向 ECharts 注入具体颜色;Tooltip、坐标轴和饼图间隙由适配器统一处理。 - 图表:ECharts + `echarts-for-react` 通过共享 `XinyuanEChart` 适配器渲染;应用扩展 `--rc-chart-*` 使用 `#DA241A` 品牌红及低饱和红、暖色、中性、灰蓝层级,禁止绿色系、荧光色和彩虹式相邻对撞。适配器读取扩展色板后再向 ECharts 注入具体颜色;Tooltip、坐标轴和饼图间隙由适配器统一处理。
- 模块头部只保留功能标题;图表、甘特图、日历和清单不显示重复性副标题或日期说明,必要的范围和解释由控件、坐标轴或弹窗上下文承载。 - 模块头部只保留功能标题;图表、甘特图、日历和清单不显示重复性副标题或日期说明,必要的范围和解释由控件、坐标轴或弹窗上下文承载。
- 字段标题旁的持久性规则说明使用小型 HeroUI Tooltip 信息图标承载;以项目状态为例,工作流说明不再占用字段下方垂直空间。 - 字段标题旁的持久性规则说明使用小型 HeroUI Tooltip 信息图标承载;以项目状态为例,工作流说明不再占用字段下方垂直空间。
- 个人日历:HeroUI Pro Agenda 月视图;跨日期项目使用 MonthSpanningEvent,单日项目使用 MonthEvent,导航和日期状态使用 Agenda 官方 compound API。 - 个人日历:HeroUI Pro Agenda 月视图;跨日期项目使用 MonthSpanningEvent,单日项目使用 MonthEvent,导航和日期状态使用 Agenda 官方 compound API;同一项目在当月首次可见片段展示完整标题,后续跨周片段统一展示“持续中”,避免重复标题干扰日期阅读。
- 个人日历 Header 导航按“上月、当前年月、下月、今天”顺序排列,当前年月使用 Agenda 的 `heading` 受控显示,不把“本月”按钮置于箭头之间;Agenda Header 移除额外水平和垂直 inset,与甘特图标题共享 Card 内容边缘。 - 个人日历 Header 导航按“上月、当前年月、下月、今天”顺序排列,当前年月使用 Agenda 的 `heading` 受控显示,不把“本月”按钮置于箭头之间;Agenda Header 移除额外水平和垂直 inset,与甘特图标题共享 Card 内容边缘。
- 消息通知:Navbar 直接使用 HeroUI `Badge.Anchor + Badge + Button + Tooltip`;36px Bell 位于右侧动作区。独立通知页使用 HeroUI Tabs、SearchField、Card、Pagination 和 HeroUI Pro EmptyState,侧栏不增加通知入口。 - 消息通知:Navbar 直接使用 HeroUI `Badge.Anchor + Badge + Button + Tooltip`;36px Bell 位于右侧动作区。独立通知页使用 HeroUI Tabs、SearchField、Card、Pagination 和 HeroUI Pro EmptyState,侧栏不增加通知入口。
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
"name": "project_manager_xy", "name": "project_manager_xy",
"version": "1.0.4", "version": "1.0.4",
"build": "abc", "build": "abc",
"description": "merge master", "description": "部门任务管理",
"date": "2025-07-14 19:27:20", "date": "2025-07-14 19:27:20",
"author": "yang" "author": "yang",
"spa": true
} }
...@@ -36,7 +36,6 @@ export async function loadNotificationPage({ ...@@ -36,7 +36,6 @@ export async function loadNotificationPage({
if (normalizedKeyword) { if (normalizedKeyword) {
filter.content = { filter.content = {
$regex: escapeRegExp(normalizedKeyword), $regex: escapeRegExp(normalizedKeyword),
$options: 'i',
}; };
} }
......
...@@ -407,6 +407,18 @@ export const PROJECT_TASK_IMPORT_EXAMPLE_ROW = [ ...@@ -407,6 +407,18 @@ export const PROJECT_TASK_IMPORT_EXAMPLE_ROW = [
'牟文博', '牟文博',
]; ];
const PROJECT_TASK_IMPORT_DATE_HEADERS = [
'计划启动时间',
'计划开始日期',
'计划完成时间',
'计划结束日期',
'实际开始日期',
'实际完成日期',
'周进度开始日期',
'周进度结束日期',
'周进度记录日期',
];
export function buildProjectTaskImportWorkbook() { export function buildProjectTaskImportWorkbook() {
const workbook = XLSX.utils.book_new(); const workbook = XLSX.utils.book_new();
const templateSheet = XLSX.utils.aoa_to_sheet([ const templateSheet = XLSX.utils.aoa_to_sheet([
...@@ -465,10 +477,28 @@ export async function readProjectTaskImportRows(file) { ...@@ -465,10 +477,28 @@ export async function readProjectTaskImportRows(file) {
return []; return [];
} }
return XLSX.utils const formattedRows = XLSX.utils.sheet_to_json(sheet, {
.sheet_to_json(sheet, {
defval: '', defval: '',
raw: false, raw: false,
});
const rawRows = XLSX.utils.sheet_to_json(sheet, {
defval: '',
raw: true,
});
return formattedRows
.map((row, rowIndex) => {
const rawRow = rawRows[rowIndex] || {};
const normalizedRow = { ...row };
PROJECT_TASK_IMPORT_DATE_HEADERS.forEach((header) => {
const rawValue = rawRow[header];
if (typeof rawValue === 'number' && Number.isFinite(rawValue)) {
normalizedRow[header] = rawValue;
}
});
return normalizedRow;
}) })
.filter((row) => .filter((row) =>
Object.values(row || {}).some((value) => String(value ?? '').trim() !== ''), Object.values(row || {}).some((value) => String(value ?? '').trim() !== ''),
...@@ -619,31 +649,26 @@ export function buildTaskFilter({ ...@@ -619,31 +649,26 @@ export function buildTaskFilter({
{ {
'metadata.project_name': { 'metadata.project_name': {
$regex: keyword, $regex: keyword,
$options: 'i',
}, },
}, },
{ {
'metadata.big_category': { 'metadata.big_category': {
$regex: keyword, $regex: keyword,
$options: 'i',
}, },
}, },
{ {
'metadata.project': { 'metadata.project': {
$regex: keyword, $regex: keyword,
$options: 'i',
}, },
}, },
{ {
'metadata.task_source': { 'metadata.task_source': {
$regex: keyword, $regex: keyword,
$options: 'i',
}, },
}, },
{ {
'metadata.task_nature': { 'metadata.task_nature': {
$regex: keyword, $regex: keyword,
$options: 'i',
}, },
}, },
], ],
...@@ -1063,9 +1088,11 @@ export function buildTaskPayload(values) { ...@@ -1063,9 +1088,11 @@ export function buildTaskPayload(values) {
const projectCollaborator = normalizeUserReferenceList(values.project_collaborator); const projectCollaborator = normalizeUserReferenceList(values.project_collaborator);
const plannedStartDate = formatDateInput(values.planned_start_date); const plannedStartDate = formatDateInput(values.planned_start_date);
const plannedEndDate = formatDateInput(values.planned_end_date); const plannedEndDate = formatDateInput(values.planned_end_date);
const projectStatus = values.project_status || 'draft';
const actualStartDateInput = formatDateInput(values.actual_start_date); const actualStartDateInput = formatDateInput(values.actual_start_date);
const actualFinishDateInput = formatDateInput(values.actual_finish_date); const actualFinishDateInput = formatDateInput(values.actual_finish_date);
const projectStatus = actualFinishDateInput
? 'completed'
: values.project_status || 'draft';
const today = new Date().toISOString().slice(0, 10); const today = new Date().toISOString().slice(0, 10);
const serializedWeeklyUpdates = serializeWeeklyUpdatesValue(values.weekly_updates, { const serializedWeeklyUpdates = serializeWeeklyUpdatesValue(values.weekly_updates, {
throwOnInvalid: true, throwOnInvalid: true,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment