Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
A
audit_project_manager
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Yang
audit_project_manager
Commits
f2f393a4
Commit
f2f393a4
authored
Sep 15, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 审计项目驾驶舱支持年份切换
parent
5ba517d6
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
97 additions
and
12 deletions
+97
-12
AuditProjectDashboardPage.jsx
src/pages/project_manager_xy/AuditProjectDashboardPage.jsx
+89
-12
project-management.business-flow.md
...es/project_manager_xy/project-management.business-flow.md
+8
-0
No files found.
src/pages/project_manager_xy/AuditProjectDashboardPage.jsx
View file @
f2f393a4
...
@@ -23,6 +23,7 @@ import {
...
@@ -23,6 +23,7 @@ import {
isFutureAuditProjectView
,
isFutureAuditProjectView
,
}
from
'./AuditProjectService'
;
}
from
'./AuditProjectService'
;
import
{
queryAllSubjectRecords
}
from
'./SubjectWorkbenchService'
;
import
{
queryAllSubjectRecords
}
from
'./SubjectWorkbenchService'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
const
STATUS_TONES
=
{
const
STATUS_TONES
=
{
待启动
:
'default'
,
待启动
:
'default'
,
...
@@ -34,12 +35,39 @@ const STATUS_TONES = {
...
@@ -34,12 +35,39 @@ const STATUS_TONES = {
};
};
const
STATUS_CHART_ORDER
=
[
'计划'
,
'执行'
,
'归档'
,
'整改'
,
'待启动'
,
'完成'
,
'未设置'
];
const
STATUS_CHART_ORDER
=
[
'计划'
,
'执行'
,
'归档'
,
'整改'
,
'待启动'
,
'完成'
,
'未设置'
];
const
YEAR_SOURCE_FIELDS
=
[
'planned_start_up_time'
,
'actual_implementation_time'
,
'project_completion_date'
,
'report_date'
,
];
function
getStatusChartOrder
(
status
)
{
function
getStatusChartOrder
(
status
)
{
const
index
=
STATUS_CHART_ORDER
.
indexOf
(
status
);
const
index
=
STATUS_CHART_ORDER
.
indexOf
(
status
);
return
index
===
-
1
?
Number
.
MAX_SAFE_INTEGER
:
index
;
return
index
===
-
1
?
Number
.
MAX_SAFE_INTEGER
:
index
;
}
}
function
extractYear
(
value
)
{
if
(
!
value
)
return
null
;
const
parsed
=
dayjs
(
value
);
if
(
parsed
.
isValid
())
return
parsed
.
year
();
const
yearMatch
=
String
(
value
).
match
(
/
(?:
19|20
)\d{2}
/
);
return
yearMatch
?
Number
(
yearMatch
[
0
])
:
null
;
}
function
extractRecordYear
(
record
)
{
const
metadata
=
record
?.
metadata
||
{};
for
(
const
field
of
YEAR_SOURCE_FIELDS
)
{
const
year
=
extractYear
(
metadata
[
field
]);
if
(
year
)
return
year
;
}
return
extractYear
(
record
?.
created_at
)
||
extractYear
(
record
?.
updated_at
);
}
function
MetricCard
({
label
,
value
,
icon
:
Icon
,
tone
,
onPress
})
{
function
MetricCard
({
label
,
value
,
icon
:
Icon
,
tone
,
onPress
})
{
const
numericValue
=
Number
.
parseFloat
(
String
(
value
).
replace
(
/
[
% ,
]
/g
,
''
));
const
numericValue
=
Number
.
parseFloat
(
String
(
value
).
replace
(
/
[
% ,
]
/g
,
''
));
const
content
=
(
const
content
=
(
...
@@ -272,6 +300,7 @@ export default function AuditProjectDashboardPage() {
...
@@ -272,6 +300,7 @@ export default function AuditProjectDashboardPage() {
const
[
records
,
setRecords
]
=
useState
([]);
const
[
records
,
setRecords
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
activeYear
,
setActiveYear
]
=
useState
(
'all'
);
const
[
selectedRecordName
,
setSelectedRecordName
]
=
useState
(
''
);
const
[
selectedRecordName
,
setSelectedRecordName
]
=
useState
(
''
);
const
[
projectGroup
,
setProjectGroup
]
=
useState
(
null
);
const
[
projectGroup
,
setProjectGroup
]
=
useState
(
null
);
...
@@ -291,32 +320,45 @@ export default function AuditProjectDashboardPage() {
...
@@ -291,32 +320,45 @@ export default function AuditProjectDashboardPage() {
useEffect
(()
=>
{
loadDashboard
();
},
[
loadDashboard
]);
useEffect
(()
=>
{
loadDashboard
();
},
[
loadDashboard
]);
const
dashboard
=
useMemo
(()
=>
{
const
dashboard
=
useMemo
(()
=>
{
const
completed
=
records
.
filter
((
record
)
=>
[
'整改'
,
'归档'
].
includes
(
record
?.
metadata
?.
project_status
));
const
yearRecords
=
records
.
map
((
record
)
=>
({
const
unfinished
=
records
.
filter
((
record
)
=>
!
[
'整改'
,
'归档'
].
includes
(
record
?.
metadata
?.
project_status
));
record
,
const
planned
=
records
.
filter
((
record
)
=>
record
?.
metadata
?.
project_status
===
'计划'
);
recordYear
:
extractRecordYear
(
record
),
const
executing
=
records
.
filter
((
record
)
=>
record
?.
metadata
?.
project_status
===
'执行'
);
}));
const
finished
=
records
.
filter
((
record
)
=>
record
?.
metadata
?.
project_status
===
'完成'
);
const
yearOptions
=
Array
.
from
(
const
overdue
=
records
.
filter
((
record
)
=>
{
new
Set
(
yearRecords
.
map
(({
recordYear
})
=>
recordYear
).
filter
(
Number
.
isInteger
)),
).
sort
((
left
,
right
)
=>
right
-
left
);
const
filteredRecords
=
(
activeYear
===
'all'
?
yearRecords
:
yearRecords
.
filter
(({
recordYear
})
=>
recordYear
===
Number
(
activeYear
)))
.
map
(({
record
})
=>
record
);
const
completed
=
filteredRecords
.
filter
((
record
)
=>
[
'整改'
,
'归档'
].
includes
(
record
?.
metadata
?.
project_status
));
const
unfinished
=
filteredRecords
.
filter
((
record
)
=>
!
[
'整改'
,
'归档'
].
includes
(
record
?.
metadata
?.
project_status
));
const
planned
=
filteredRecords
.
filter
((
record
)
=>
record
?.
metadata
?.
project_status
===
'计划'
);
const
executing
=
filteredRecords
.
filter
((
record
)
=>
record
?.
metadata
?.
project_status
===
'执行'
);
const
finished
=
filteredRecords
.
filter
((
record
)
=>
record
?.
metadata
?.
project_status
===
'完成'
);
const
overdue
=
filteredRecords
.
filter
((
record
)
=>
{
const
metadata
=
record
?.
metadata
||
{};
const
metadata
=
record
?.
metadata
||
{};
return
!
[
'整改'
,
'归档'
].
includes
(
metadata
.
project_status
)
return
!
[
'整改'
,
'归档'
].
includes
(
metadata
.
project_status
)
&&
metadata
.
project_completion_date
&&
metadata
.
project_completion_date
&&
dayjs
(
metadata
.
project_completion_date
).
isBefore
(
dayjs
(),
'day'
);
&&
dayjs
(
metadata
.
project_completion_date
).
isBefore
(
dayjs
(),
'day'
);
});
});
const
active
=
r
ecords
.
filter
((
record
)
=>
[
'计划'
,
'执行'
,
'完成'
].
includes
(
record
?.
metadata
?.
project_status
));
const
active
=
filteredR
ecords
.
filter
((
record
)
=>
[
'计划'
,
'执行'
,
'完成'
].
includes
(
record
?.
metadata
?.
project_status
));
const
upcoming
=
r
ecords
.
filter
((
record
)
=>
{
const
upcoming
=
filteredR
ecords
.
filter
((
record
)
=>
{
const
metadata
=
record
?.
metadata
||
{};
const
metadata
=
record
?.
metadata
||
{};
if
(
metadata
.
project_status
!==
'待启动'
||
!
metadata
.
planned_start_up_time
)
return
false
;
if
(
metadata
.
project_status
!==
'待启动'
||
!
metadata
.
planned_start_up_time
)
return
false
;
const
daysUntilStart
=
dayjs
(
metadata
.
planned_start_up_time
).
startOf
(
'day'
).
diff
(
dayjs
().
startOf
(
'day'
),
'day'
);
const
daysUntilStart
=
dayjs
(
metadata
.
planned_start_up_time
).
startOf
(
'day'
).
diff
(
dayjs
().
startOf
(
'day'
),
'day'
);
return
daysUntilStart
>
0
&&
daysUntilStart
<=
30
;
return
daysUntilStart
>
0
&&
daysUntilStart
<=
30
;
});
});
const
statusCounts
=
r
ecords
.
reduce
((
counts
,
record
)
=>
{
const
statusCounts
=
filteredR
ecords
.
reduce
((
counts
,
record
)
=>
{
const
status
=
record
?.
metadata
?.
project_status
||
'未设置'
;
const
status
=
record
?.
metadata
?.
project_status
||
'未设置'
;
counts
[
status
]
=
(
counts
[
status
]
||
0
)
+
1
;
counts
[
status
]
=
(
counts
[
status
]
||
0
)
+
1
;
return
counts
;
return
counts
;
},
{});
},
{});
return
{
return
{
yearOptions
,
recordCount
:
filteredRecords
.
length
,
completed
,
unfinished
,
planned
,
executing
,
finished
,
overdue
,
active
,
upcoming
,
completed
,
unfinished
,
planned
,
executing
,
finished
,
overdue
,
active
,
upcoming
,
completionRate
:
records
.
length
?
Math
.
round
((
completed
.
length
/
r
ecords
.
length
)
*
100
)
:
0
,
completionRate
:
filteredRecords
.
length
?
Math
.
round
((
completed
.
length
/
filteredR
ecords
.
length
)
*
100
)
:
0
,
statusData
:
Object
.
entries
(
statusCounts
).
map
(([
name
,
value
])
=>
({
name
,
value
})),
statusData
:
Object
.
entries
(
statusCounts
).
map
(([
name
,
value
])
=>
({
name
,
value
})),
stageData
:
[
stageData
:
[
{
name
:
'计划'
,
value
:
planned
.
length
},
{
name
:
'计划'
,
value
:
planned
.
length
},
...
@@ -324,7 +366,15 @@ export default function AuditProjectDashboardPage() {
...
@@ -324,7 +366,15 @@ export default function AuditProjectDashboardPage() {
{
name
:
'完成'
,
value
:
finished
.
length
},
{
name
:
'完成'
,
value
:
finished
.
length
},
],
],
};
};
},
[
records
]);
},
[
activeYear
,
records
]);
useEffect
(()
=>
{
if
(
activeYear
!==
'all'
&&
!
dashboard
.
yearOptions
.
includes
(
Number
(
activeYear
)))
{
setActiveYear
(
'all'
);
}
},
[
activeYear
,
dashboard
.
yearOptions
]);
const
yearLabel
=
activeYear
===
'all'
?
'全部年份'
:
`
${
activeYear
}
年`
;
const
openRecord
=
(
record
)
=>
setSelectedRecordName
(
record
?.
name
||
record
?.
_id
||
''
);
const
openRecord
=
(
record
)
=>
setSelectedRecordName
(
record
?.
name
||
record
?.
_id
||
''
);
const
openGroup
=
(
title
,
groupRecords
)
=>
setProjectGroup
({
title
,
records
:
groupRecords
});
const
openGroup
=
(
title
,
groupRecords
)
=>
setProjectGroup
({
title
,
records
:
groupRecords
});
...
@@ -346,8 +396,35 @@ export default function AuditProjectDashboardPage() {
...
@@ -346,8 +396,35 @@ export default function AuditProjectDashboardPage() {
return
(
return
(
<
div
className=
"rc-page-stack"
>
<
div
className=
"rc-page-stack"
>
<
div
className=
"flex justify-end"
>
<
XinyuanAutocomplete
aria
-
label=
"统计年份"
className=
"w-36"
fullWidth=
{
false
}
options=
{
[
{
id
:
'all'
,
label
:
'全部年份'
},
...
dashboard
.
yearOptions
.
map
((
year
)
=>
({
id
:
String
(
year
),
label
:
`${year} 年`
,
})),
]
}
placement=
"bottom end"
placeholder=
"全部年份"
selectedKey=
{
activeYear
}
shouldFilter=
{
false
}
showSearch=
{
false
}
valueText=
{
yearLabel
}
variant=
"primary"
onSelectionChange=
{
(
key
)
=>
{
if
(
key
!==
null
&&
key
!==
undefined
)
{
setActiveYear
(
String
(
key
));
}
}
}
/>
</
div
>
<
section
className=
"rc-kpi-grid"
data
-
count=
"4"
>
<
section
className=
"rc-kpi-grid"
data
-
count=
"4"
>
<
MetricCard
label=
"项目总数"
value=
{
records
.
length
}
icon=
{
FolderOpenIcon
}
tone=
"indigo"
/>
<
MetricCard
label=
{
`${yearLabel}项目总数`
}
value=
{
dashboard
.
recordCount
}
icon=
{
FolderOpenIcon
}
tone=
"indigo"
/>
<
MetricCard
label=
"已完成"
value=
{
dashboard
.
completed
.
length
}
icon=
{
CheckCircleIcon
}
tone=
"success"
onPress=
{
()
=>
openGroup
(
'已完成项目'
,
dashboard
.
completed
)
}
/>
<
MetricCard
label=
"已完成"
value=
{
dashboard
.
completed
.
length
}
icon=
{
CheckCircleIcon
}
tone=
"success"
onPress=
{
()
=>
openGroup
(
'已完成项目'
,
dashboard
.
completed
)
}
/>
<
MetricCard
label=
"未完成"
value=
{
dashboard
.
unfinished
.
length
}
icon=
{
ClockIcon
}
tone=
"warning"
onPress=
{
()
=>
openGroup
(
'未完成项目'
,
dashboard
.
unfinished
)
}
/>
<
MetricCard
label=
"未完成"
value=
{
dashboard
.
unfinished
.
length
}
icon=
{
ClockIcon
}
tone=
"warning"
onPress=
{
()
=>
openGroup
(
'未完成项目'
,
dashboard
.
unfinished
)
}
/>
<
MetricCard
label=
"完成率"
value=
{
`${dashboard.completionRate}%`
}
icon=
{
ChartPieIcon
}
tone=
"teal"
/>
<
MetricCard
label=
"完成率"
value=
{
`${dashboard.completionRate}%`
}
icon=
{
ChartPieIcon
}
tone=
"teal"
/>
...
...
src/pages/project_manager_xy/project-management.business-flow.md
View file @
f2f393a4
...
@@ -85,6 +85,14 @@ Word 导出分别读取 `audit_plain_template`、`audit_verification_template`
...
@@ -85,6 +85,14 @@ Word 导出分别读取 `audit_plain_template`、`audit_verification_template`
## 6. 驾驶舱口径
## 6. 驾驶舱口径
-
驾驶舱顶部提供“统计年份”选择器,默认“全部年份”;年份选项从当前已加载的
`audit_project`
记录自动提取并倒序展示。
-
记录年份优先依次读取
`metadata.planned_start_up_time`
、
`metadata.actual_implementation_time`
、
`metadata.project_completion_date`
、
`metadata.report_date`
;均无法识别时回退到记录
`created_at`
/
`updated_at`
。
-
页面通过既有
`POST /record/query`
全量加载记录后,在前端完成年份过滤,并计算项目总数、
完成率、状态分布、阶段分布和项目提醒。
-
记录详情刷新后重新加载全量记录;若当前所选年份已无对应记录,则自动回退“全部年份”。
-
已完成:状态为
`整改`
或
`归档`
。
-
已完成:状态为
`整改`
或
`归档`
。
-
未完成:状态不是
`整改`
且不是
`归档`
。
-
未完成:状态不是
`整改`
且不是
`归档`
。
-
完成率:已完成项目数除以项目总数,四舍五入为整数百分比。
-
完成率:已完成项目数除以项目总数,四舍五入为整数百分比。
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment