Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
P
problem_rectification
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
problem_rectification
Commits
2d3757b2
Commit
2d3757b2
authored
Jul 16, 2026
by
David Yang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 驾驶舱拆分
parent
c536e180
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
589 additions
and
426 deletions
+589
-426
.umirc.ts
.umirc.ts
+5
-0
Layout.jsx
src/pages/problem_rectification/Layout.jsx
+6
-3
BusinessDashboard.jsx
...lem_rectification/businessDashboard/BusinessDashboard.jsx
+5
-0
index.jsx
src/pages/problem_rectification/dashboard/index.jsx
+457
-358
problem-rectification.business-flow.md
...blem_rectification/problem-rectification.business-flow.md
+39
-16
problemRectificationDashboardService.js
...lem_rectification/problemRectificationDashboardService.js
+77
-49
No files found.
.umirc.ts
View file @
2d3757b2
...
@@ -8,6 +8,7 @@ function resolvePackageRoot(packageName: string) {
...
@@ -8,6 +8,7 @@ function resolvePackageRoot(packageName: string) {
const
ROUTE_PATHS
=
{
const
ROUTE_PATHS
=
{
login
:
'/login'
,
login
:
'/login'
,
dashboard
:
'/integrity_risk_table/dashboard'
,
dashboard
:
'/integrity_risk_table/dashboard'
,
businessDashboard
:
'/integrity_risk_table/business_dashboard'
,
problemRectification
:
'/problem_rectification'
,
problemRectification
:
'/problem_rectification'
,
issueRectificationArchive
:
'/issue_rectification_archive'
,
issueRectificationArchive
:
'/issue_rectification_archive'
,
problemRectificationForm
:
'/problem_rectification_form'
,
problemRectificationForm
:
'/problem_rectification_form'
,
...
@@ -41,6 +42,10 @@ export default defineConfig({
...
@@ -41,6 +42,10 @@ export default defineConfig({
path
:
ROUTE_PATHS
.
dashboard
,
path
:
ROUTE_PATHS
.
dashboard
,
component
:
'./problem_rectification/dashboard/index'
,
component
:
'./problem_rectification/dashboard/index'
,
},
},
{
path
:
ROUTE_PATHS
.
businessDashboard
,
component
:
'./problem_rectification/businessDashboard/BusinessDashboard'
,
},
{
{
path
:
ROUTE_PATHS
.
problemRectification
,
path
:
ROUTE_PATHS
.
problemRectification
,
component
:
'./problem_rectification/list/index'
,
component
:
'./problem_rectification/list/index'
,
...
...
src/pages/problem_rectification/Layout.jsx
View file @
2d3757b2
...
@@ -5,6 +5,7 @@ import { SiteContext } from '@/wrapper/Site';
...
@@ -5,6 +5,7 @@ import { SiteContext } from '@/wrapper/Site';
import
{
import
{
ArchiveBoxIcon
,
ArchiveBoxIcon
,
Bars3Icon
,
Bars3Icon
,
ChartPieIcon
,
ClipboardDocumentCheckIcon
,
ClipboardDocumentCheckIcon
,
HomeModernIcon
,
HomeModernIcon
,
Squares2X2Icon
,
Squares2X2Icon
,
...
@@ -25,6 +26,11 @@ const menuGroups = [
...
@@ -25,6 +26,11 @@ const menuGroups = [
path
:
'/integrity_risk_table/dashboard'
,
path
:
'/integrity_risk_table/dashboard'
,
icon
:
HomeModernIcon
,
icon
:
HomeModernIcon
,
},
},
{
name
:
'业务驾驶舱'
,
path
:
'/integrity_risk_table/business_dashboard'
,
icon
:
ChartPieIcon
,
},
{
{
name
:
'问题整改'
,
name
:
'问题整改'
,
path
:
'/problem_rectification'
,
path
:
'/problem_rectification'
,
...
@@ -280,9 +286,6 @@ export default function ProblemRectificationLayout() {
...
@@ -280,9 +286,6 @@ export default function ProblemRectificationLayout() {
<
h1
className=
"truncate text-sm font-semibold tracking-[0.02em] text-white/95"
>
<
h1
className=
"truncate text-sm font-semibold tracking-[0.02em] text-white/95"
>
{
APP_NAME
}
{
APP_NAME
}
</
h1
>
</
h1
>
<
div
className=
"mt-1 text-xs text-zinc-400"
>
替代旧多维表格版问题整改应用
</
div
>
</
div
>
</
div
>
{
applicationSwitcherButton
}
{
applicationSwitcherButton
}
</>
</>
...
...
src/pages/problem_rectification/businessDashboard/BusinessDashboard.jsx
0 → 100644
View file @
2d3757b2
import
{
ProblemRectificationDashboardPage
}
from
'../dashboard'
;
export
default
function
BusinessDashboard
()
{
return
<
ProblemRectificationDashboardPage
dashboardMode=
"business"
/>;
}
src/pages/problem_rectification/dashboard/index.jsx
View file @
2d3757b2
import
{
Button
}
from
'@/catalyst-ui-kit/javascript/button'
;
import
{
Dialog
,
DialogActions
,
DialogBody
,
DialogDescription
,
DialogTitle
,
}
from
'@/catalyst-ui-kit/javascript/dialog'
;
import
{
Select
}
from
'@/catalyst-ui-kit/javascript/select'
;
import
{
Table
,
TableBody
,
TableCell
,
TableHead
,
TableHeader
,
TableRow
,
}
from
'@/catalyst-ui-kit/javascript/table'
;
import
ReactECharts
from
'echarts-for-react'
;
import
ReactECharts
from
'echarts-for-react'
;
import
clsx
from
'clsx'
;
import
clsx
from
'clsx'
;
import
dayjs
from
'dayjs'
;
import
dayjs
from
'dayjs'
;
...
@@ -11,6 +28,32 @@ const CATEGORY_LEVEL_TABS = [
...
@@ -11,6 +28,32 @@ const CATEGORY_LEVEL_TABS = [
{
key
:
'level3'
,
label
:
'三级分类'
},
{
key
:
'level3'
,
label
:
'三级分类'
},
{
key
:
'level4'
,
label
:
'四级分类'
},
{
key
:
'level4'
,
label
:
'四级分类'
},
];
];
const
FOCUS_ISSUE_TYPES
=
[
{
key
:
'economicLoss'
,
label
:
'经济损失问题'
,
shortLabel
:
'经济损失'
,
description
:
'造成经济损失且严重程度较高的问题。'
,
color
:
'#e11d48'
,
badgeClass
:
'bg-rose-50 text-rose-700'
,
},
{
key
:
'externalImpact'
,
label
:
'外部影响问题'
,
shortLabel
:
'外部影响'
,
description
:
'涉及处罚机构或外部影响的问题。'
,
color
:
'#f59e0b'
,
badgeClass
:
'bg-amber-50 text-amber-700'
,
},
{
key
:
'accountability'
,
label
:
'员工问责问题'
,
shortLabel
:
'员工问责'
,
description
:
'已触发员工问责后果的问题。'
,
color
:
'#0ea5e9'
,
badgeClass
:
'bg-sky-50 text-sky-700'
,
},
];
function
formatPercent
(
value
)
{
function
formatPercent
(
value
)
{
const
numericValue
=
Number
(
value
||
0
);
const
numericValue
=
Number
(
value
||
0
);
...
@@ -73,16 +116,6 @@ function StatCard({ label, value, hint, tone = 'default' }) {
...
@@ -73,16 +116,6 @@ function StatCard({ label, value, hint, tone = 'default' }) {
);
);
}
}
function
NumberCard
({
label
,
value
,
hint
})
{
return
(
<
div
className=
"rounded-[24px] border border-zinc-200 bg-zinc-50/70 p-5"
>
<
div
className=
"text-sm font-medium text-slate-500"
>
{
label
}
</
div
>
<
div
className=
"mt-3 text-3xl font-semibold tracking-tight text-slate-950"
>
{
value
}
</
div
>
{
hint
?
<
div
className=
"mt-2 text-sm text-slate-500"
>
{
hint
}
</
div
>
:
null
}
</
div
>
);
}
function
Panel
({
title
,
description
,
children
})
{
function
Panel
({
title
,
description
,
children
})
{
return
(
return
(
<
section
className=
"rounded-[30px] border border-zinc-200 bg-white/95 shadow-[0_20px_50px_rgba(15,23,42,0.06)]"
>
<
section
className=
"rounded-[30px] border border-zinc-200 bg-white/95 shadow-[0_20px_50px_rgba(15,23,42,0.06)]"
>
...
@@ -103,48 +136,57 @@ function EmptyBlock({ title }) {
...
@@ -103,48 +136,57 @@ function EmptyBlock({ title }) {
);
);
}
}
function
FocusIssueList
({
title
,
description
,
items
=
[],
tone
})
{
function
FocusIssueDetailDialog
({
issueType
,
items
=
[],
onClose
})
{
const
toneClass
=
tone
===
'rose'
?
'bg-rose-50 text-rose-700'
:
tone
===
'amber'
?
'bg-amber-50 text-amber-700'
:
'bg-sky-50 text-sky-700'
;
return
(
return
(
<
Panel
title=
{
title
}
description=
{
description
}
>
<
Dialog
open=
{
Boolean
(
issueType
)
}
onClose=
{
onClose
}
size=
"5xl"
>
{
items
.
length
>
0
?
(
<
DialogTitle
>
{
issueType
?.
label
||
'重点问题详情'
}
</
DialogTitle
>
<
div
className=
"space-y-3"
>
<
DialogDescription
>
{
items
.
map
((
item
)
=>
(
{
issueType
?.
description
||
'当前重点问题详情'
}
共
{
items
.
length
}
条
<
article
</
DialogDescription
>
key=
{
item
.
id
}
<
DialogBody
>
className=
"rounded-[22px] border border-zinc-200 bg-[linear-gradient(180deg,#ffffff,rgba(248,250,252,0.92))] p-4 shadow-sm"
{
items
.
length
>
0
?
(
>
<
Table
dense
striped
className=
"[--gutter:--spacing(2)]"
>
<
div
className=
"flex items-start justify-between gap-3"
>
<
TableHead
>
<
div
className=
"min-w-0"
>
<
TableRow
>
<
div
className=
"truncate text-base font-semibold text-slate-900"
>
{
item
.
title
}
</
div
>
<
TableHeader
>
问题
</
TableHeader
>
<
div
className=
"mt-1 text-sm text-slate-500"
>
{
item
.
feedbackUnit
}
</
div
>
<
TableHeader
>
反馈单位
</
TableHeader
>
</
div
>
<
TableHeader
>
标记
</
TableHeader
>
<
div
className=
{
clsx
(
'rounded-full px-3 py-1 text-xs font-medium'
,
toneClass
)
}
>
<
TableHeader
>
严重程度
</
TableHeader
>
{
item
.
extraLabel
}
<
TableHeader
>
截止日期
</
TableHeader
>
</
div
>
<
TableHeader
>
问题简述
</
TableHeader
>
</
div
>
</
TableRow
>
<
div
className=
"mt-3 line-clamp-2 text-sm leading-6 text-slate-600"
>
{
item
.
brief
}
</
div
>
</
TableHead
>
<
div
className=
"mt-4 flex flex-wrap gap-2"
>
<
TableBody
>
<
span
className=
"rounded-full bg-zinc-100 px-3 py-1 text-xs font-medium text-zinc-700"
>
{
items
.
map
((
item
)
=>
(
严重程度
{
item
.
severityLevel
}
<
TableRow
key=
{
item
.
id
}
className=
"align-top"
>
</
span
>
<
TableCell
className=
"min-w-56 font-medium text-slate-950"
>
<
span
className=
"rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-700"
>
{
item
.
title
||
'-'
}
截止
{
item
.
dueDateLabel
}
</
TableCell
>
</
span
>
<
TableCell
className=
"min-w-36 text-slate-600"
>
</
div
>
{
item
.
feedbackUnit
||
'-'
}
</
article
>
</
TableCell
>
))
}
<
TableCell
>
</
div
>
<
span
className=
{
clsx
(
'rounded-full px-2.5 py-1 text-xs font-medium'
,
issueType
?.
badgeClass
)
}
>
)
:
(
{
item
.
extraLabel
||
'-'
}
<
EmptyBlock
title=
"当前没有相关重点问题"
/>
</
span
>
)
}
</
TableCell
>
</
Panel
>
<
TableCell
>
{
item
.
severityLevel
||
'-'
}
</
TableCell
>
<
TableCell
>
{
item
.
dueDateLabel
||
'-'
}
</
TableCell
>
<
TableCell
className=
"min-w-80 max-w-[32rem] whitespace-normal text-slate-600"
>
{
item
.
brief
||
'-'
}
</
TableCell
>
</
TableRow
>
))
}
</
TableBody
>
</
Table
>
)
:
(
<
EmptyBlock
title=
"当前没有相关重点问题"
/>
)
}
</
DialogBody
>
<
DialogActions
>
<
Button
outline
onClick=
{
onClose
}
>
关闭
</
Button
>
</
DialogActions
>
</
Dialog
>
);
);
}
}
...
@@ -354,6 +396,71 @@ function buildHorizontalBarOption(data, color) {
...
@@ -354,6 +396,71 @@ function buildHorizontalBarOption(data, color) {
};
};
}
}
function buildFocusIssueBarOption(data = []) {
return {
color: data.map((item) => item.color),
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params) => {
const item = params[0];
return `
$
{
item
?.
axisValueLabel
||
''
}:
$
{
item
?.
value
||
0
}
条
<
br
/>
点击查看详情列表
`;
},
backgroundColor: 'rgba(15,23,42,0.9)',
borderWidth: 0,
textStyle: { color: '#fff' },
},
grid: {
left: 8,
right: 20,
top: 18,
bottom: 8,
containLabel: true,
},
xAxis: {
type: 'value',
minInterval: 1,
splitLine: { lineStyle: { color: 'rgba(148,163,184,0.18)' } },
axisLabel: { color: '#64748b' },
},
yAxis: {
type: 'category',
data: data.map((item) => item.label),
axisTick: { show: false },
axisLine: { show: false },
axisLabel: { color: '#334155', fontSize: 12 },
},
series: [
{
type: 'bar',
cursor: 'pointer',
data: data.map((item) => ({
name: item.label,
value: item.value,
issueTypeKey: item.key,
itemStyle: {
color: item.color,
borderRadius: [0, 99, 99, 0],
},
})),
barMaxWidth: 34,
showBackground: true,
backgroundStyle: {
color: 'rgba(148,163,184,0.08)',
borderRadius: 99,
},
label: {
show: true,
position: 'right',
formatter: '{c}',
color: '#0f172a',
fontWeight: 700,
},
},
],
};
}
function buildBubbleMatrixOption(data = []) {
function buildBubbleMatrixOption(data = []) {
const maxTotal = Math.max(...data.map((item) => item.totalCount), 1);
const maxTotal = Math.max(...data.map((item) => item.totalCount), 1);
const maxOverdue = Math.max(...data.map((item) => item.overdueCount), 1);
const maxOverdue = Math.max(...data.map((item) => item.overdueCount), 1);
...
@@ -437,79 +544,6 @@ function buildBubbleMatrixOption(data = []) {
...
@@ -437,79 +544,6 @@ function buildBubbleMatrixOption(data = []) {
};
};
}
}
function buildTimelinessScatterOption(data = [])
{
const
maxValue
=
Math
.
max
(
...
data
.
flatMap
((
item
)
=>
[
item
.
value
?.[
0
]
||
0
,
item
.
value
?.[
1
]
||
0
]),
1
,
);
return
{
tooltip
:
{
formatter
:
(
params
)
=>
{
const
item
=
params
.
data
;
return
[
`<strong>${item.name}</strong>`
,
`预计整改时长:${item.value[0]} 天`
,
`实际整改时长:${item.value[1]} 天`
,
`严重程度:${item.value[2]}`
,
item
.
isDelayed
?
'判定:已逾期'
:
'判定:按期或提前完成'
,
].
join
(
'<br/>'
);
},
backgroundColor
:
'rgba(15,23,42,0.9)'
,
borderWidth
:
0
,
textStyle
:
{
color
:
'#fff'
},
},
grid
:
{
left
:
8
,
right
:
16
,
top
:
16
,
bottom
:
10
,
containLabel
:
true
,
},
xAxis
:
{
type
:
'value'
,
name
:
'预计整改时长(天)'
,
nameTextStyle
:
{
color
:
'#64748b'
},
splitLine
:
{
lineStyle
:
{
color
:
'rgba(148,163,184,0.18)'
}
},
axisLabel
:
{
color
:
'#64748b'
},
},
yAxis
:
{
type
:
'value'
,
name
:
'实际整改时长(天)'
,
nameTextStyle
:
{
color
:
'#64748b'
},
splitLine
:
{
lineStyle
:
{
color
:
'rgba(148,163,184,0.18)'
}
},
axisLabel
:
{
color
:
'#64748b'
},
},
series
:
[
{
type
:
'scatter'
,
data
:
data
.
map
((
item
)
=>
({
...
item
,
symbolSize
:
item
.
value
[
2
]
===
'高'
?
18
:
item
.
value
[
2
]
===
'中'
?
14
:
10
,
itemStyle
:
{
color
:
item
.
isDelayed
?
'#e11d48'
:
'#0ea5e9'
,
opacity
:
0.82
,
},
})),
},
{
type
:
'line'
,
data
:
[
[
0
,
0
],
[
maxValue
,
maxValue
],
],
silent
:
true
,
symbol
:
'none'
,
lineStyle
:
{
color
:
'#94a3b8'
,
type
:
'dashed'
,
width
:
1.5
,
},
},
],
};
}
function buildQualityRatingOption(data)
{
function buildQualityRatingOption(data)
{
return
{
return
{
color
:
[
'#22c55e'
,
'#f59e0b'
,
'#e11d48'
],
color
:
[
'#22c55e'
,
'#f59e0b'
,
'#e11d48'
],
...
@@ -552,12 +586,16 @@ function buildQualityRatingOption(data) {
...
@@ -552,12 +586,16 @@ function buildQualityRatingOption(data) {
};
};
}
}
export
default function ProblemRectificationDashboardPage(
)
{
export
function ProblemRectificationDashboardPage(
{
dashboardMode
=
'overview'
}
=
{}
)
{
const
[
activeGroupKey
,
setActiveGroupKey
]
=
useState
(
'all'
);
const
[
activeGroupKey
,
setActiveGroupKey
]
=
useState
(
'all'
);
const
[
activeCategoryLevel
,
setActiveCategoryLevel
]
=
useState
(
'level1'
);
const
[
activeCategoryLevel
,
setActiveCategoryLevel
]
=
useState
(
'level1'
);
const
[
activeYear
,
setActiveYear
]
=
useState
(
'all'
);
const
[
activeFocusIssueTypeKey
,
setActiveFocusIssueTypeKey
]
=
useState
(
''
);
const
[
dashboardData
,
setDashboardData
]
=
useState
({
const
[
dashboardData
,
setDashboardData
]
=
useState
({
groups
:
[],
groups
:
[],
summaries
:
{},
summaries
:
{},
yearOptions
:
[],
yearScopes
:
{},
error
:
''
,
error
:
''
,
isEmpty
:
false
,
isEmpty
:
false
,
});
});
...
@@ -582,7 +620,15 @@ export default function ProblemRectificationDashboardPage() {
...
@@ -582,7 +620,15 @@ export default function ProblemRectificationDashboardPage() {
},
[]);
},
[]);
const
groups
=
dashboardData
.
groups
||
[];
const
groups
=
dashboardData
.
groups
||
[];
const
summary
=
dashboardData
.
summaries
?.[
activeGroupKey
]
||
{
const
yearOptions
=
dashboardData
.
yearOptions
||
[];
const
activeScope
=
activeYear
===
'all'
?
dashboardData
:
dashboardData
.
yearScopes
?.[
activeYear
]
||
{
summaries
:
{},
departmentBuckets
:
{},
isEmpty
:
true
,
};
const
summary
=
activeScope
.
summaries
?.[
activeGroupKey
]
||
{
totalCount
:
0
,
totalCount
:
0
,
fixedCount
:
0
,
fixedCount
:
0
,
completionRate
:
0
,
completionRate
:
0
,
...
@@ -604,7 +650,6 @@ export default function ProblemRectificationDashboardPage() {
...
@@ -604,7 +650,6 @@ export default function ProblemRectificationDashboardPage() {
},
},
overdueRanking
:
[],
overdueRanking
:
[],
departmentMatrix
:
[],
departmentMatrix
:
[],
timelinessScatter
:
[],
qualityRatingDistribution
:
[],
qualityRatingDistribution
:
[],
};
};
...
@@ -633,16 +678,44 @@ export default function ProblemRectificationDashboardPage() {
...
@@ -633,16 +678,44 @@ export default function ProblemRectificationDashboardPage() {
()
=>
buildBubbleMatrixOption
(
summary
.
departmentMatrix
),
()
=>
buildBubbleMatrixOption
(
summary
.
departmentMatrix
),
[
summary
.
departmentMatrix
],
[
summary
.
departmentMatrix
],
);
);
const
timelinessScatterOption
=
useMemo
(
()
=>
buildTimelinessScatterOption
(
summary
.
timelinessScatter
),
[
summary
.
timelinessScatter
],
);
const
qualityRatingOption
=
useMemo
(
const
qualityRatingOption
=
useMemo
(
()
=>
buildQualityRatingOption
(
summary
.
qualityRatingDistribution
),
()
=>
buildQualityRatingOption
(
summary
.
qualityRatingDistribution
),
[
summary
.
qualityRatingDistribution
],
[
summary
.
qualityRatingDistribution
],
);
);
const
focusIssueChartData
=
useMemo
(
()
=>
FOCUS_ISSUE_TYPES
.
map
((
type
)
=>
({
...
type
,
value
:
summary
.
focusIssueGroups
?.[
type
.
key
]?.
length
||
0
,
})),
[
summary
.
focusIssueGroups
],
);
const
focusIssueOption
=
useMemo
(
()
=>
buildFocusIssueBarOption
(
focusIssueChartData
),
[
focusIssueChartData
],
);
const
focusIssueChartEvents
=
useMemo
(
()
=>
({
click
:
(
params
)
=>
{
const
issueTypeKey
=
params
?.
data
?.
issueTypeKey
;
if
(
issueTypeKey
)
{
setActiveFocusIssueTypeKey
(
issueTypeKey
);
}
},
}),
[],
);
const
activeGroup
=
groups
.
find
((
item
)
=>
item
.
key
===
activeGroupKey
);
const
activeGroup
=
groups
.
find
((
item
)
=>
item
.
key
===
activeGroupKey
);
const
activeFocusIssueType
=
FOCUS_ISSUE_TYPES
.
find
((
type
)
=>
type
.
key
===
activeFocusIssueTypeKey
)
||
null
;
const
activeFocusIssueItems
=
activeFocusIssueType
?
summary
.
focusIssueGroups
?.[
activeFocusIssueType
.
key
]
||
[]
:
[];
const
isBusinessDashboard
=
dashboardMode
===
'business'
;
const
dashboardTitle
=
isBusinessDashboard
?
'业务驾驶舱'
:
'问题整改驾驶舱'
;
const
dashboardSubtitle
=
isBusinessDashboard
?
'围绕问题风险定位、部门责任追踪和整改质效评估,沉淀业务分析与督办依据。'
:
'围绕整改完成进度、问题风险定位、部门责任压实和整改质效评估,统一展示今年问题整改全景。'
;
const
yearLabel
=
activeYear
===
'all'
?
'全部年份'
:
`${activeYear} 年`
;
return
(
return
(
<
div
className=
"space-y-8"
>
<
div
className=
"space-y-8"
>
...
@@ -655,37 +728,55 @@ export default function ProblemRectificationDashboardPage() {
...
@@ -655,37 +728,55 @@ export default function ProblemRectificationDashboardPage() {
Integrity Risk Dashboard
Integrity Risk Dashboard
</
div
>
</
div
>
<
h1
className=
"mt-3 text-3xl font-semibold tracking-tight md:text-[2.55rem]"
>
<
h1
className=
"mt-3 text-3xl font-semibold tracking-tight md:text-[2.55rem]"
>
问题整改驾驶舱
{
dashboardTitle
}
</
h1
>
</
h1
>
<
div
className=
"mt-4 max-w-3xl text-sm leading-6 text-white/78"
>
<
div
className=
"mt-4 max-w-3xl text-sm leading-6 text-white/78"
>
围绕整改完成进度、问题风险定位、部门责任压实和整改质效评估,统一展示今年问题整改全景。
{
dashboardSubtitle
}
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
"rounded-[28px] border border-white/15 bg-white/10 px-5 py-4 backdrop-blur"
>
<
div
className=
"rounded-[28px] border border-white/15 bg-white/10 px-5 py-4 backdrop-blur"
>
<
div
className=
"text-xs uppercase tracking-[0.22em] text-white/65"
>
当前分组
</
div
>
<
div
className=
"text-xs uppercase tracking-[0.22em] text-white/65"
>
当前分组
</
div
>
<
div
className=
"mt-2 text-2xl font-semibold"
>
{
activeGroup
?.
label
||
'全部'
}
</
div
>
<
div
className=
"mt-2 text-2xl font-semibold"
>
{
activeGroup
?.
label
||
'全部'
}
</
div
>
<
div
className=
"mt-1 text-sm text-white/72"
>
{
dayjs
().
format
(
'YYYY 年 MM 月 DD 日'
)
}
</
div
>
<
div
className=
"mt-1 text-sm text-white/72"
>
{
yearLabel
}
·
{
dayjs
().
format
(
'YYYY 年 MM 月 DD 日'
)
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
section
>
</
section
>
<
section
className=
"overflow-x-auto"
>
<
section
className=
"rounded-[28px] border border-zinc-200 bg-white/90 p-2 shadow-sm"
>
<
div
className=
"inline-flex min-w-full gap-3 rounded-[28px] border border-zinc-200 bg-white/90 p-2 shadow-sm"
>
<
div
className=
"flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between"
>
{
groups
.
map
((
group
)
=>
(
<
div
className=
"overflow-x-auto"
>
<
button
<
div
className=
"inline-flex min-w-full gap-3"
>
key=
{
group
.
key
}
{
groups
.
map
((
group
)
=>
(
type=
"button"
<
button
onClick=
{
()
=>
setActiveGroupKey
(
group
.
key
)
}
key=
{
group
.
key
}
className=
{
clsx
(
type=
"button"
'rounded-[20px] px-4 py-3 text-sm font-medium transition-all duration-200'
,
onClick=
{
()
=>
setActiveGroupKey
(
group
.
key
)
}
activeGroupKey
===
group
.
key
className=
{
clsx
(
?
'bg-[linear-gradient(135deg,#7f1d1d,#be123c)] text-white shadow-lg'
'rounded-[20px] px-4 py-3 text-sm font-medium transition-all duration-200'
,
:
'text-slate-600 hover:bg-rose-50 hover:text-rose-700'
,
activeGroupKey
===
group
.
key
)
}
?
'bg-[linear-gradient(135deg,#7f1d1d,#be123c)] text-white shadow-lg'
:
'text-slate-600 hover:bg-rose-50 hover:text-rose-700'
,
)
}
>
{
group
.
label
}
</
button
>
))
}
</
div
>
</
div
>
<
label
className=
"flex shrink-0 items-center gap-3 rounded-[20px] border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm text-slate-600"
>
<
span
className=
"font-medium text-slate-700"
>
统计年份
</
span
>
<
Select
value=
{
activeYear
}
onChange=
{
(
event
)
=>
setActiveYear
(
event
.
target
.
value
)
}
>
>
{
group
.
label
}
<
option
value=
"all"
>
全部年份
</
option
>
</
button
>
{
yearOptions
.
map
((
year
)
=>
(
))
}
<
option
key=
{
year
}
value=
{
String
(
year
)
}
>
{
year
}
年
</
option
>
))
}
</
Select
>
</
label
>
</
div
>
</
div
>
</
section
>
</
section
>
...
@@ -697,7 +788,7 @@ export default function ProblemRectificationDashboardPage() {
...
@@ -697,7 +788,7 @@ export default function ProblemRectificationDashboardPage() {
{
loading
?
(
{
loading
?
(
<
div
className=
"rounded-[26px] border border-sky-200 bg-sky-50 px-5 py-4 text-sm text-sky-800"
>
<
div
className=
"rounded-[26px] border border-sky-200 bg-sky-50 px-5 py-4 text-sm text-sky-800"
>
正在加载
问题整改驾驶舱
...
正在加载
{
dashboardTitle
}
...
</
div
>
</
div
>
)
:
null
}
)
:
null
}
...
@@ -708,221 +799,229 @@ export default function ProblemRectificationDashboardPage() {
...
@@ -708,221 +799,229 @@ export default function ProblemRectificationDashboardPage() {
</
div
>
</
div
>
)
:
null
}
)
:
null
}
{
!
loading
&&
!
dashboardData
.
isEmpty
?
(
{
!
loading
&&
!
dashboardData
.
isEmpty
&&
activeScope
.
isEmpty
?
(
<
div
className=
"rounded-[30px] border border-dashed border-zinc-300 bg-white/80 px-6 py-12 text-center"
>
<
div
className=
"text-xl font-semibold text-slate-900"
>
{
yearLabel
}
暂无问题整改数据
</
div
>
<
div
className=
"mt-3 text-sm leading-6 text-slate-500"
>
请选择其它年份或切回全部年份查看统计。
</
div
>
</
div
>
)
:
null
}
{
!
loading
&&
!
dashboardData
.
isEmpty
&&
!
activeScope
.
isEmpty
?
(
<>
<>
<
section
className=
"space-y-5"
>
{
!
isBusinessDashboard
?
(
<
SectionHeading
<
section
className=
"space-y-5"
>
eyebrow=
"Overview"
<
SectionHeading
title=
"宏观总览"
eyebrow=
"Overview"
description=
"先看整体整改健康度,快速判断问题总量、闭环情况和逾期风险。"
title=
"宏观总览"
/>
description=
"先看整体整改健康度,快速判断问题总量、闭环情况和逾期风险。"
<
div
className=
"grid gap-4 md:grid-cols-2 xl:grid-cols-5"
>
<
StatCard
label=
"问题总数"
value=
{
formatCount
(
summary
.
totalCount
)
}
hint=
"当前分组内全部问题记录"
tone=
"rose"
/>
<
StatCard
label=
"已完成整改数"
value=
{
formatCount
(
summary
.
fixedCount
)
}
hint=
"整改进度为已整改或已优化"
tone=
"emerald"
/>
<
StatCard
label=
"整改完成率"
value=
{
formatPercent
(
summary
.
completionRate
)
}
hint=
"已完成整改数 / 总问题数"
tone=
"sky"
/>
/>
<
StatCard
<
div
className=
"grid gap-4 md:grid-cols-2 xl:grid-cols-5"
>
label=
"逾期未完成数"
<
StatCard
value=
{
formatCount
(
summary
.
overdueCount
)
}
label=
"问题总数"
hint=
"截止今天已超期且未闭环"
value=
{
formatCount
(
summary
.
totalCount
)
}
tone=
"amber"
hint=
"当前分组内全部问题记录"
/>
tone=
"rose"
<
StatCard
/>
label=
"逾期率"
<
StatCard
value=
{
formatPercent
(
summary
.
overdueRate
)
}
label=
"已完成整改数"
hint=
"逾期未完成数 / 总问题数"
value=
{
formatCount
(
summary
.
fixedCount
)
}
tone=
"amber"
hint=
"整改进度为已整改或已优化"
/>
tone=
"emerald"
</
div
>
/>
<
div
className=
"grid gap-6 xl:grid-cols-[0.9fr_1.1fr]"
>
<
StatCard
<
Panel
label=
"整改完成率"
title=
"整改进度分布"
value=
{
formatPercent
(
summary
.
completionRate
)
}
description=
"按整改进度统计数量与占比,直观看当前积压主要落在哪个状态。"
hint=
"已完成整改数 / 总问题数"
>
tone=
"sky"
{
summary
.
progressDistribution
.
length
>
0
?
(
/>
<
ReactECharts
option=
{
progressOption
}
style=
{
{
height
:
340
}
}
/>
<
StatCard
)
:
(
label=
"逾期未完成数"
<
EmptyBlock
title=
"暂无整改进度分布数据"
/>
value=
{
formatCount
(
summary
.
overdueCount
)
}
)
}
hint=
"截止今天已超期且未闭环"
</
Panel
>
tone=
"amber"
<
Panel
/>
title=
"进度口径说明"
<
StatCard
description=
"当前逾期判定规则采用“预计整改完成时间早于今天且尚未闭环”。如果后续上线显式“是否延期”字段,可直接切换为配置化口径。"
label=
"逾期率"
>
value=
{
formatPercent
(
summary
.
overdueRate
)
}
<
div
className=
"grid gap-4 md:grid-cols-2"
>
hint=
"逾期未完成数 / 总问题数"
<
NumberCard
tone=
"amber"
label=
"已闭环问题"
/>
value=
{
formatCount
(
summary
.
fixedCount
)
}
</
div
>
hint=
"适合用于经营层快速判断整体闭环能力"
</
section
>
/>
)
:
null
}
<
NumberCard
label=
"待重点督办"
{
isBusinessDashboard
?
(
value=
{
formatCount
(
summary
.
overdueCount
)
}
<>
hint=
"适合作为督办清单和预警入口"
<
section
className=
"space-y-5"
>
/>
<
SectionHeading
eyebrow=
"Progress"
title=
"整改进度"
description=
"按整改进度统计分布与口径,辅助判断当前积压状态和督办入口。"
/>
<
div
className=
"grid gap-6"
>
<
Panel
title=
"整改进度分布"
description=
"按整改进度统计数量与占比,直观看当前积压主要落在哪个状态。"
>
{
summary
.
progressDistribution
.
length
>
0
?
(
<
ReactECharts
option=
{
progressOption
}
style=
{
{
height
:
340
}
}
/>
)
:
(
<
EmptyBlock
title=
"暂无整改进度分布数据"
/>
)
}
</
Panel
>
</
div
>
</
div
>
</
Panel
>
</
section
>
</
div
>
</
section
>
<
section
className=
"space-y-5"
>
<
SectionHeading
<
section
className=
"space-y-5"
>
eyebrow=
"Risk"
<
SectionHeading
title=
"问题分析与风险定位"
eyebrow=
"Risk"
description=
"把问题集中区域、根因和来源差异拉出来,支撑制度修订和内审效能判断。"
title=
"问题分析与风险定位"
/>
description=
"把问题集中区域、根因和来源差异拉出来,支撑制度修订和内审效能判断。"
<
div
className=
"grid gap-6 xl:grid-cols-[1.05fr_0.95fr]"
>
/>
<
Panel
<
div
className=
"grid gap-6 xl:grid-cols-[1.05fr_0.95fr]"
>
title=
"问题类型 / 业务领域分布"
<
Panel
description=
"切换查看问题一级至四级分类数量分布,帮助定位高发领域。"
title=
"问题类型 / 业务领域分布"
>
description=
"切换查看问题一级至四级分类数量分布,帮助定位高发领域。"
<
div
className=
"mb-5 flex flex-wrap gap-2"
>
>
{
CATEGORY_LEVEL_TABS
.
map
((
tab
)
=>
{
<
div
className=
"mb-5 flex flex-wrap gap-2"
>
const
isActive
=
activeCategoryLevel
===
tab
.
key
;
{
CATEGORY_LEVEL_TABS
.
map
((
tab
)
=>
{
const
isActive
=
activeCategoryLevel
===
tab
.
key
;
return
(
<
button
return
(
key=
{
tab
.
key
}
<
button
type=
"button"
key=
{
tab
.
key
}
aria
-
pressed=
{
isActive
}
type=
"button"
className=
{
clsx
(
aria
-
pressed=
{
isActive
}
'min-h-11 rounded-full border px-4 text-sm font-medium transition'
,
className=
{
clsx
(
isActive
'min-h-11 rounded-full border px-4 text-sm font-medium transition'
,
?
'border-rose-500 bg-rose-500 text-white shadow-sm'
isActive
:
'border-zinc-200 bg-white text-slate-600 hover:border-rose-200 hover:bg-rose-50 hover:text-rose-700'
,
?
'border-rose-500 bg-rose-500 text-white shadow-sm'
)
}
:
'border-zinc-200 bg-white text-slate-600 hover:border-rose-200 hover:bg-rose-50 hover:text-rose-700'
,
onClick=
{
()
=>
setActiveCategoryLevel
(
tab
.
key
)
}
)
}
>
onClick=
{
()
=>
setActiveCategoryLevel
(
tab
.
key
)
}
{
tab
.
label
}
>
</
button
>
{
tab
.
label
}
);
</
button
>
})
}
);
</
div
>
})
}
{
activeCategoryData
.
length
>
0
?
(
<
ReactECharts
option=
{
categoryOption
}
style=
{
{
height
:
360
}
}
/>
)
:
(
<
EmptyBlock
title=
"暂无问题领域统计数据"
/>
)
}
</
Panel
>
<
Panel
title=
"问题根本原因分析"
description=
"按问题原因分类统计,便于识别制度、流程或执行层面的共性成因。"
>
{
summary
.
rootCauseDistribution
.
length
>
0
?
(
<
ReactECharts
option=
{
rootCauseOption
}
style=
{
{
height
:
360
}
}
/>
)
:
(
<
EmptyBlock
title=
"暂无问题原因统计数据"
/>
)
}
</
Panel
>
</
div
>
</
div
>
{
activeCategoryData
.
length
>
0
?
(
<
ReactECharts
option=
{
categoryOption
}
style=
{
{
height
:
360
}
}
/>
)
:
(
<
EmptyBlock
title=
"暂无问题领域统计数据"
/>
)
}
</
Panel
>
<
Panel
title=
"问题根本原因分析"
description=
"按问题原因分类统计,便于识别制度、流程或执行层面的共性成因。"
>
{
summary
.
rootCauseDistribution
.
length
>
0
?
(
<
ReactECharts
option=
{
rootCauseOption
}
style=
{
{
height
:
360
}
}
/>
)
:
(
<
EmptyBlock
title=
"暂无问题原因统计数据"
/>
)
}
</
Panel
>
</
div
>
<
Panel
<
Panel
title=
"问题来源对比"
title=
"问题来源对比"
description=
"横轴为问题来源,堆叠展示检查实施单位,观察内外部检查发现问题的结构差异。"
description=
"横轴为问题来源,堆叠展示检查实施单位,观察内外部检查发现问题的结构差异。"
>
>
{
summary
.
sourceComparison
.
categories
.
length
>
0
?
(
{
summary
.
sourceComparison
.
categories
.
length
>
0
?
(
<
ReactECharts
option=
{
sourceComparisonOption
}
style=
{
{
height
:
360
}
}
/>
<
ReactECharts
option=
{
sourceComparisonOption
}
style=
{
{
height
:
360
}
}
/>
)
:
(
)
:
(
<
EmptyBlock
title=
"暂无问题来源对比数据"
/>
<
EmptyBlock
title=
"暂无问题来源对比数据"
/>
)
}
)
}
</
Panel
>
</
Panel
>
<
div
className=
"grid gap-6 xl:grid-cols-3"
>
<
FocusIssueList
title=
"经济损失问题"
description=
"优先展示造成经济损失且严重程度更高的问题。"
items=
{
summary
.
focusIssueGroups
.
economicLoss
}
tone=
"rose"
/>
<
FocusIssueList
title=
"外部影响问题"
description=
"直观看到涉及处罚机构或外部影响的问题。"
items=
{
summary
.
focusIssueGroups
.
externalImpact
}
tone=
"amber"
/>
<
FocusIssueList
title=
"员工问责问题"
description=
"展示已触发员工问责后果的问题。"
items=
{
summary
.
focusIssueGroups
.
accountability
}
tone=
"sky"
/>
</
div
>
</
section
>
<
section
className=
"space-y-5"
>
<
SectionHeading
eyebrow=
"Execution"
title=
"问题责任追踪与执行"
description=
"围绕责任部门看执行难点,快速识别逾期堆积、完成率偏低和预估时长失真的对象。"
/>
<
div
className=
"grid gap-6 xl:grid-cols-[0.9fr_1.1fr]"
>
<
Panel
title=
"逾期问题排行"
description=
"按责任部门统计逾期未完成问题数,用于识别长期挂账和重点督办对象。"
>
{
summary
.
overdueRanking
.
length
>
0
?
(
<
ReactECharts
option=
{
overdueRankingOption
}
style=
{
{
height
:
340
}
}
/>
)
:
(
<
EmptyBlock
title=
"当前没有逾期问题排行数据"
/>
)
}
</
Panel
>
<
Panel
title=
"部门整改责任矩阵"
description=
"X 轴为问题总数,Y 轴为整改完成率,气泡大小表示逾期问题数。"
>
{
summary
.
departmentMatrix
.
length
>
0
?
(
<
ReactECharts
option=
{
departmentMatrixOption
}
style=
{
{
height
:
340
}
}
/>
)
:
(
<
EmptyBlock
title=
"暂无部门责任矩阵数据"
/>
)
}
</
Panel
>
</
div
>
<
Panel
<
Panel
title=
"整改时效分析"
title=
"重点问题分布"
description=
"X 轴为预计整改时长,Y 轴为实际整改时长。虚线右上方的点代表实际时长高于预估。"
description=
"按经济损失、外部影响和员工问责统计重点问题数量,点击柱状图查看对应详情列表。"
>
>
{
summary
.
timelinessScatter
.
length
>
0
?
(
{
focusIssueChartData
.
some
((
item
)
=>
item
.
value
>
0
)
?
(
<
ReactECharts
option=
{
timelinessScatterOption
}
style=
{
{
height
:
380
}
}
/>
<
div
className=
"space-y-4"
>
)
:
(
<
ReactECharts
<
EmptyBlock
title=
"缺少预计整改时间、实际整改完成时间或检查完成日期,暂无法形成时效散点图"
/>
option=
{
focusIssueOption
}
)
}
onEvents=
{
focusIssueChartEvents
}
</
Panel
>
style=
{
{
height
:
320
}
}
</
section
>
/>
<
div
className=
"flex flex-wrap gap-2"
>
<
section
className=
"space-y-5"
>
{
focusIssueChartData
.
map
((
item
)
=>
(
<
SectionHeading
<
Button
eyebrow=
"Quality"
key=
{
item
.
key
}
title=
"审计整改质效"
outline
description=
"驾驶舱只展示综合整改质效结果,具体评估维度在维护页补录和校验。"
onClick=
{
()
=>
setActiveFocusIssueTypeKey
(
item
.
key
)
}
/>
>
<
Panel
{
item
.
shortLabel
}
{
formatCount
(
item
.
value
)
}
条
title=
"综合整改质效结果"
</
Button
>
description=
"按高、中、低展示最终整改质效等级;暂按“两个及以上高为高、出现低则低、一个高则中”的初步规则计算,具体维度和规则后续确认。"
))
}
>
</
div
>
{
summary
.
qualityRatingDistribution
.
length
>
0
?
(
</
div
>
<
ReactECharts
option=
{
qualityRatingOption
}
style=
{
{
height
:
380
}
}
/>
)
:
(
)
:
(
<
EmptyBlock
title=
"当前没有相关重点问题"
/>
<
EmptyBlock
title=
"暂无综合整改质效结果数据"
/>
)
}
)
}
</
Panel
>
</
Panel
>
</
section
>
</
section
>
<
section
className=
"space-y-5"
>
<
SectionHeading
eyebrow=
"Execution"
title=
"问题责任追踪与执行"
description=
"围绕责任部门看执行难点,快速识别逾期堆积、完成率偏低和预估时长失真的对象。"
/>
<
div
className=
"grid gap-6 xl:grid-cols-[0.9fr_1.1fr]"
>
<
Panel
title=
"逾期问题排行"
description=
"按责任部门统计逾期未完成问题数,用于识别长期挂账和重点督办对象。"
>
{
summary
.
overdueRanking
.
length
>
0
?
(
<
ReactECharts
option=
{
overdueRankingOption
}
style=
{
{
height
:
340
}
}
/>
)
:
(
<
EmptyBlock
title=
"当前没有逾期问题排行数据"
/>
)
}
</
Panel
>
<
Panel
title=
"部门整改责任矩阵"
description=
"X 轴为问题总数,Y 轴为整改完成率,气泡大小表示逾期问题数。"
>
{
summary
.
departmentMatrix
.
length
>
0
?
(
<
ReactECharts
option=
{
departmentMatrixOption
}
style=
{
{
height
:
340
}
}
/>
)
:
(
<
EmptyBlock
title=
"暂无部门责任矩阵数据"
/>
)
}
</
Panel
>
</
div
>
</
section
>
<
section
className=
"space-y-5"
>
<
SectionHeading
eyebrow=
"Quality"
title=
"审计整改质效"
description=
"驾驶舱只展示综合整改质效结果,具体评估维度在维护页补录和校验。"
/>
<
Panel
title=
"综合整改质效结果"
description=
"按高、中、低展示最终整改质效等级;暂按“两个及以上高为高、出现低则低、一个高则中”的初步规则计算,具体维度和规则后续确认。"
>
{
summary
.
qualityRatingDistribution
.
length
>
0
?
(
<
ReactECharts
option=
{
qualityRatingOption
}
style=
{
{
height
:
380
}
}
/>
)
:
(
<
EmptyBlock
title=
"暂无综合整改质效结果数据"
/>
)
}
</
Panel
>
</
section
>
</>
)
:
null
}
</>
</>
)
:
null
}
)
:
null
}
<
FocusIssueDetailDialog
issueType=
{
activeFocusIssueType
}
items=
{
activeFocusIssueItems
}
onClose=
{
()
=>
setActiveFocusIssueTypeKey
(
''
)
}
/>
</
div
>
</
div
>
);
);
}
}
export default ProblemRectificationDashboardPage;
src/pages/problem_rectification/problem-rectification.business-flow.md
View file @
2d3757b2
...
@@ -38,15 +38,17 @@
...
@@ -38,15 +38,17 @@
## 3. 功能入口
## 3. 功能入口
建议在当前项目中保留以下
三
个一级入口:
建议在当前项目中保留以下
四
个一级入口:
-
驾驶舱:
`/integrity_risk_table/dashboard`
-
驾驶舱:
`/integrity_risk_table/dashboard`
-
业务驾驶舱:
`/integrity_risk_table/business_dashboard`
-
问题整改:
`/problem_rectification`
-
问题整改:
`/problem_rectification`
-
往期材料归档:
`/issue_rectification_archive`
-
往期材料归档:
`/issue_rectification_archive`
建议交互形态:
建议交互形态:
-
驾驶舱使用轻量卡片、统计区块、分组标签页和列表卡片
-
驾驶舱仅展示顶部横幅、部门分组切换与宏观总览 KPI 卡片
-
业务驾驶舱承接整改进度分布、问题风险定位、责任追踪与整改质效图表
-
问题整改主列表使用普通数据表格,不再依赖多维表格编辑器
-
问题整改主列表使用普通数据表格,不再依赖多维表格编辑器
-
新增、编辑、查看详情优先采用抽屉或弹窗,不走重型嵌套表格交互
-
新增、编辑、查看详情优先采用抽屉或弹窗,不走重型嵌套表格交互
-
批量操作放在顶部工具栏,不再依赖右键菜单作为唯一入口
-
批量操作放在顶部工具栏,不再依赖右键菜单作为唯一入口
...
@@ -79,20 +81,23 @@
...
@@ -79,20 +81,23 @@
### 3.1 目标页面拆分
### 3.1 目标页面拆分
为便于当前项目直接重构,建议把模块拆成以下
4
个页面入口:
为便于当前项目直接重构,建议把模块拆成以下
5
个页面入口:
1.
`integrity_risk_table/dashboard`
1.
`integrity_risk_table/dashboard`
问题整改驾驶舱
问题整改驾驶舱
2.
`problem_rectification`
2.
`integrity_risk_table/business_dashboard`
业务驾驶舱
3.
`problem_rectification`
问题整改主列表
问题整改主列表
3
.
`issue_rectification_archive`
4
.
`issue_rectification_archive`
归档批次列表
归档批次列表
4
.
`problem_rectification_form`
5
.
`problem_rectification_form`
外链填写 / 复核 / 最终审核页
外链填写 / 复核 / 最终审核页
对应推荐页面职责:
对应推荐页面职责:
-
驾驶舱:只做统计、提醒、分组切换,不承接编辑
-
驾驶舱:展示管理层首屏关注的宏观总览,保留部门分组切换,不承接编辑
-
业务驾驶舱:展示后续业务分析图表、责任追踪、重点问题清单与质效分布,保留部门分组切换,不承接编辑
-
主列表:承接查询、筛选、批量操作、详情入口
-
主列表:承接查询、筛选、批量操作、详情入口
-
归档页:承接归档批次查看与明细追溯
-
归档页:承接归档批次查看与明细追溯
-
外链流程页:承接填写、复核、最终审核,不与主列表混杂
-
外链流程页:承接填写、复核、最终审核,不与主列表混杂
...
@@ -403,7 +408,12 @@
...
@@ -403,7 +408,12 @@
## 9. 驾驶舱展示规则
## 9. 驾驶舱展示规则
当前驾驶舱页面以
`problem_rectification`
为唯一数据源,围绕 2026 年整改驾驶舱需求分为四层:
当前驾驶舱能力以
`problem_rectification`
为唯一数据源,并拆分为两个菜单入口:
-
`integrity_risk_table/dashboard`
:问题整改驾驶舱,仅展示顶部横幅、部门分组切换与宏观总览 KPI
-
`integrity_risk_table/business_dashboard`
:业务驾驶舱,展示原宏观总览之后的业务分析内容,并继续支持部门分组切换
围绕 2026 年整改驾驶舱需求,整体统计能力分为四层:
1.
宏观总览层
1.
宏观总览层
2.
问题分析与风险定位层
2.
问题分析与风险定位层
...
@@ -435,11 +445,24 @@
...
@@ -435,11 +445,24 @@
当前实现约定:
当前实现约定:
-
顶部继续保留按
`feedback_unit`
分组的轻量 tabs
-
两个驾驶舱入口顶部均保留按
`feedback_unit`
分组的轻量 tabs
-
tabs 下方分为 KPI 卡片、ECharts 图表区和重点问题清单区
-
两个驾驶舱入口顶部均提供“统计年份”选择器,默认“全部年份”,选择具体年份后仅统计该年份内的问题记录
-
驾驶舱仅负责统计展示,不承接编辑动作
-
问题整改驾驶舱的 tabs 下方只展示宏观总览 KPI 卡片
-
业务驾驶舱的 tabs 下方展示整改进度分布、业务风险分析、责任追踪和整改质效图表
-
两个驾驶舱均仅负责统计展示,不承接编辑动作
-
如果线上 Subject 后续把新增字段从
`text`
升级为
`select`
/
`date`
/
`boolean`
,驾驶舱统计口径无需调整,只需复用标准字段值
-
如果线上 Subject 后续把新增字段从
`text`
升级为
`select`
/
`date`
/
`boolean`
,驾驶舱统计口径无需调整,只需复用标准字段值
### 9.1.1 统计年份筛选
年份筛选用于支持管理层按年度查看整改概览和业务分析:
-
默认选项为“全部年份”,不做年度过滤,按当前数据全量统计
-
年份选项从当前已加载的
`problem_rectification`
记录自动提取,按年份倒序展示
-
记录年份优先从检查类日期和业务发生时间推导,依次读取:
`inspection_report_date`
、
`check_the_implementation_time`
、
`inspection_completed_at`
、
`the_time_of_rectification`
、
`actual_rectification_completed_at`
-
如果上述字段都无法识别年份,则回退到记录的
`created_at`
/
`updated_at`
-
选择年份后,部门分组、宏观 KPI、整改进度、问题风险定位、责任追踪和整改质效统计均基于该年份内记录重新计算
-
如果某年份下没有可展示记录,页面展示空状态,并提示切换其它年份或全部年份
### 9.2 宏观总览层
### 9.2 宏观总览层
当前 KPI 统计口径:
当前 KPI 统计口径:
...
@@ -454,6 +477,7 @@
...
@@ -454,6 +477,7 @@
-
以
`rectification_progress`
为维度统计数量和占比
-
以
`rectification_progress`
为维度统计数量和占比
-
当前页面使用环形图表达,不再沿用旧版折线趋势表达
-
当前页面使用环形图表达,不再沿用旧版折线趋势表达
-
业务驾驶舱仅保留整改进度分布图,不再展示单独的“进度口径说明”卡片
### 9.3 问题分析与风险定位层
### 9.3 问题分析与风险定位层
...
@@ -466,7 +490,8 @@
...
@@ -466,7 +490,8 @@
-
经济损失问题:
`has_economic_loss`
+
`economic_loss_amount`
-
经济损失问题:
`has_economic_loss`
+
`economic_loss_amount`
-
外部影响问题:
`has_external_impact`
+
`penalty_organization`
-
外部影响问题:
`has_external_impact`
+
`penalty_organization`
-
员工问责问题:
`has_employee_accountability`
+
`accountability_person_count`
-
员工问责问题:
`has_employee_accountability`
+
`accountability_person_count`
-
重点问题清单中同时展示
`problem_severity_level`
-
页面以柱状图展示三类重点问题数量;点击柱状图或下方按钮后,通过弹框展示对应详情列表
-
重点问题详情列表中同时展示
`problem_severity_level`
、截止日期、反馈单位、标记信息和问题简述
### 9.4 问题责任追踪与执行层
### 9.4 问题责任追踪与执行层
...
@@ -477,10 +502,7 @@
...
@@ -477,10 +502,7 @@
-
X 轴:责任部门问题总数
-
X 轴:责任部门问题总数
-
Y 轴:整改完成率
-
Y 轴:整改完成率
-
气泡大小:逾期问题数
-
气泡大小:逾期问题数
-
整改时效分析散点图:
-
业务驾驶舱不再展示单独的“整改时效分析”散点图
-
X 轴:
`the_time_of_rectification - inspection_completed_at`
-
Y 轴:
`actual_rectification_completed_at - inspection_completed_at`
-
仅统计同时填写了预计整改时间、实际整改完成时间、实际检查完成日期且天数非负的问题
当前逾期 / 延期判定说明:
当前逾期 / 延期判定说明:
...
@@ -701,6 +723,7 @@ const result = await queryUsers(0, 20, {
...
@@ -701,6 +723,7 @@ const result = await queryUsers(0, 20, {
-
`src/pages/problem_rectification/Layout.jsx`
-
`src/pages/problem_rectification/Layout.jsx`
-
`src/pages/problem_rectification/dashboard/index.jsx`
-
`src/pages/problem_rectification/dashboard/index.jsx`
-
`src/pages/problem_rectification/businessDashboard/BusinessDashboard.jsx`
-
`src/pages/problem_rectification/list/index.jsx`
-
`src/pages/problem_rectification/list/index.jsx`
-
`src/pages/problem_rectification/archive/index.jsx`
-
`src/pages/problem_rectification/archive/index.jsx`
-
`src/pages/problem_rectification/form/index.jsx`
-
`src/pages/problem_rectification/form/index.jsx`
...
...
src/pages/problem_rectification/problemRectificationDashboardService.js
View file @
2d3757b2
...
@@ -35,6 +35,13 @@ const FIXED_PROGRESS_VALUES = new Set(['已整改', '已优化']);
...
@@ -35,6 +35,13 @@ const FIXED_PROGRESS_VALUES = new Set(['已整改', '已优化']);
const
POSITIVE_FLAGS
=
new
Set
([
'是'
,
'有'
,
'涉及'
,
'TRUE'
,
'1'
,
'Y'
,
'YES'
]);
const
POSITIVE_FLAGS
=
new
Set
([
'是'
,
'有'
,
'涉及'
,
'TRUE'
,
'1'
,
'Y'
,
'YES'
]);
const
SEVERITY_LEVELS
=
[
'低'
,
'中'
,
'高'
];
const
SEVERITY_LEVELS
=
[
'低'
,
'中'
,
'高'
];
const
RATING_LEVELS
=
[
'低'
,
'中'
,
'高'
];
const
RATING_LEVELS
=
[
'低'
,
'中'
,
'高'
];
const
YEAR_SOURCE_FIELDS
=
[
'inspection_report_date'
,
'check_the_implementation_time'
,
'inspection_completed_at'
,
'the_time_of_rectification'
,
'actual_rectification_completed_at'
,
];
function
normalizeText
(
value
)
{
function
normalizeText
(
value
)
{
return
String
(
value
??
''
).
trim
();
return
String
(
value
??
''
).
trim
();
...
@@ -81,6 +88,36 @@ function formatDateText(value) {
...
@@ -81,6 +88,36 @@ function formatDateText(value) {
return
/^
\d{4}
-
\d{2}
-
\d{2}
/
.
test
(
text
)
?
text
.
slice
(
0
,
10
)
:
text
;
return
/^
\d{4}
-
\d{2}
-
\d{2}
/
.
test
(
text
)
?
text
.
slice
(
0
,
10
)
:
text
;
}
}
function
extractYear
(
value
)
{
const
text
=
normalizeText
(
value
);
if
(
!
text
)
{
return
null
;
}
const
parsed
=
parseDate
(
text
);
if
(
parsed
)
{
return
parsed
.
year
();
}
const
yearMatch
=
text
.
match
(
/
(?:
19|20
)\d{2}
/
);
if
(
!
yearMatch
)
{
return
null
;
}
return
Number
(
yearMatch
[
0
]);
}
function
extractRecordYear
(
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
isAffirmative
(
value
)
{
function
isAffirmative
(
value
)
{
return
POSITIVE_FLAGS
.
has
(
normalizeText
(
value
).
toUpperCase
());
return
POSITIVE_FLAGS
.
has
(
normalizeText
(
value
).
toUpperCase
());
}
}
...
@@ -161,14 +198,6 @@ function sortByCountDesc(items = []) {
...
@@ -161,14 +198,6 @@ function sortByCountDesc(items = []) {
return
[...
items
].
sort
((
left
,
right
)
=>
right
.
value
-
left
.
value
);
return
[...
items
].
sort
((
left
,
right
)
=>
right
.
value
-
left
.
value
);
}
}
function
diffDays
(
later
,
earlier
)
{
if
(
!
later
||
!
earlier
)
{
return
null
;
}
return
later
.
startOf
(
'day'
).
diff
(
earlier
.
startOf
(
'day'
),
'day'
);
}
function
buildDistributionMap
(
records
,
getKey
)
{
function
buildDistributionMap
(
records
,
getKey
)
{
const
map
=
new
Map
();
const
map
=
new
Map
();
...
@@ -274,19 +303,16 @@ function buildFocusIssueGroups(records) {
...
@@ -274,19 +303,16 @@ function buildFocusIssueGroups(records) {
economicLoss
:
records
economicLoss
:
records
.
filter
((
record
)
=>
record
.
hasEconomicLoss
)
.
filter
((
record
)
=>
record
.
hasEconomicLoss
)
.
sort
(
sortBySeverityAndDueDate
)
.
sort
(
sortBySeverityAndDueDate
)
.
slice
(
0
,
6
)
.
map
((
record
)
=>
.
map
((
record
)
=>
formatItem
(
record
,
record
.
economicLossAmount
?
`损失
${
record
.
economicLossAmount
}
`
:
'已标记经济损失'
),
formatItem
(
record
,
record
.
economicLossAmount
?
`损失
${
record
.
economicLossAmount
}
`
:
'已标记经济损失'
),
),
),
externalImpact
:
records
externalImpact
:
records
.
filter
((
record
)
=>
record
.
hasExternalImpact
)
.
filter
((
record
)
=>
record
.
hasExternalImpact
)
.
sort
(
sortBySeverityAndDueDate
)
.
sort
(
sortBySeverityAndDueDate
)
.
slice
(
0
,
6
)
.
map
((
record
)
=>
formatItem
(
record
,
record
.
penaltyOrganization
||
'已标记外部影响'
)),
.
map
((
record
)
=>
formatItem
(
record
,
record
.
penaltyOrganization
||
'已标记外部影响'
)),
accountability
:
records
accountability
:
records
.
filter
((
record
)
=>
record
.
hasEmployeeAccountability
)
.
filter
((
record
)
=>
record
.
hasEmployeeAccountability
)
.
sort
(
sortBySeverityAndDueDate
)
.
sort
(
sortBySeverityAndDueDate
)
.
slice
(
0
,
6
)
.
map
((
record
)
=>
.
map
((
record
)
=>
formatItem
(
formatItem
(
record
,
record
,
...
@@ -359,34 +385,6 @@ function buildOverdueRanking(departmentStats) {
...
@@ -359,34 +385,6 @@ function buildOverdueRanking(departmentStats) {
).
slice
(
0
,
10
);
).
slice
(
0
,
10
);
}
}
function
buildTimelinessScatter
(
records
)
{
return
records
.
map
((
record
)
=>
{
const
plannedDays
=
diffDays
(
record
.
dueDate
,
record
.
inspectionCompletedAt
);
const
actualDays
=
diffDays
(
record
.
actualRectificationCompletedAt
,
record
.
inspectionCompletedAt
,
);
if
(
plannedDays
===
null
||
actualDays
===
null
||
plannedDays
<
0
||
actualDays
<
0
)
{
return
null
;
}
return
{
name
:
record
.
title
,
value
:
[
plannedDays
,
actualDays
,
record
.
severityLevel
||
'未评定'
],
isDelayed
:
actualDays
>
plannedDays
,
};
})
.
filter
(
Boolean
)
.
slice
(
0
,
120
);
}
function
buildQualityRatingDistribution
(
records
)
{
function
buildQualityRatingDistribution
(
records
)
{
const
counts
=
{
const
counts
=
{
高
:
0
,
高
:
0
,
...
@@ -429,7 +427,6 @@ function summarizeRecords(records) {
...
@@ -429,7 +427,6 @@ function summarizeRecords(records) {
focusIssueGroups
:
buildFocusIssueGroups
(
records
),
focusIssueGroups
:
buildFocusIssueGroups
(
records
),
overdueRanking
:
buildOverdueRanking
(
departmentStats
),
overdueRanking
:
buildOverdueRanking
(
departmentStats
),
departmentMatrix
:
buildDepartmentMatrix
(
departmentStats
),
departmentMatrix
:
buildDepartmentMatrix
(
departmentStats
),
timelinessScatter
:
buildTimelinessScatter
(
records
),
qualityRatingDistribution
:
buildQualityRatingDistribution
(
records
),
qualityRatingDistribution
:
buildQualityRatingDistribution
(
records
),
};
};
}
}
...
@@ -454,6 +451,7 @@ function normalizeProblemRecord(record) {
...
@@ -454,6 +451,7 @@ function normalizeProblemRecord(record) {
const
normalizedRecord
=
{
const
normalizedRecord
=
{
id
:
record
?.
_id
||
record
?.
name
||
''
,
id
:
record
?.
_id
||
record
?.
name
||
''
,
title
:
normalizeText
(
record
?.
title
)
||
normalizeText
(
metadata
.
project_name
)
||
'-'
,
title
:
normalizeText
(
record
?.
title
)
||
normalizeText
(
metadata
.
project_name
)
||
'-'
,
recordYear
:
extractRecordYear
(
record
,
metadata
),
projectName
:
normalizeText
(
metadata
.
project_name
),
projectName
:
normalizeText
(
metadata
.
project_name
),
inspectUnit
:
normalizeText
(
metadata
.
inspect_the_implementing_unit
),
inspectUnit
:
normalizeText
(
metadata
.
inspect_the_implementing_unit
),
feedbackUnit
:
normalizeText
(
metadata
.
feedback_unit
),
feedbackUnit
:
normalizeText
(
metadata
.
feedback_unit
),
...
@@ -527,6 +525,32 @@ function buildDepartmentBuckets(records) {
...
@@ -527,6 +525,32 @@ function buildDepartmentBuckets(records) {
return
buckets
;
return
buckets
;
}
}
function
buildDashboardScope
(
records
)
{
const
departmentBuckets
=
buildDepartmentBuckets
(
records
);
const
summaries
=
Object
.
fromEntries
(
DEPARTMENT_GROUPS
.
map
((
group
)
=>
[
group
.
key
,
summarizeRecords
(
departmentBuckets
[
group
.
key
]
||
[]),
]),
);
return
{
departmentBuckets
,
summaries
,
isEmpty
:
records
.
length
===
0
,
};
}
function
buildYearOptions
(
records
)
{
return
Array
.
from
(
new
Set
(
records
.
map
((
record
)
=>
record
.
recordYear
)
.
filter
((
year
)
=>
Number
.
isInteger
(
year
)),
),
).
sort
((
left
,
right
)
=>
right
-
left
);
}
async
function
fetchAllProblemRectificationRecords
()
{
async
function
fetchAllProblemRectificationRecords
()
{
const
firstPage
=
await
queryRecords
({
const
firstPage
=
await
queryRecords
({
subject
:
PROBLEM_RECTIFICATION_SUBJECT
,
subject
:
PROBLEM_RECTIFICATION_SUBJECT
,
...
@@ -558,27 +582,31 @@ export async function loadProblemRectificationDashboardData() {
...
@@ -558,27 +582,31 @@ export async function loadProblemRectificationDashboardData() {
try
{
try
{
const
rawRecords
=
await
fetchAllProblemRectificationRecords
();
const
rawRecords
=
await
fetchAllProblemRectificationRecords
();
const
records
=
rawRecords
.
map
(
normalizeProblemRecord
);
const
records
=
rawRecords
.
map
(
normalizeProblemRecord
);
const
departmentBuckets
=
buildDepartmentBucket
s
(
records
);
const
yearOptions
=
buildYearOption
s
(
records
);
const
allScope
=
buildDashboardScope
(
records
);
const
summari
es
=
Object
.
fromEntries
(
const
yearScop
es
=
Object
.
fromEntries
(
DEPARTMENT_GROUPS
.
map
((
group
)
=>
[
yearOptions
.
map
((
year
)
=>
[
group
.
key
,
String
(
year
)
,
summarizeRecords
(
departmentBuckets
[
group
.
key
]
||
[]
),
buildDashboardScope
(
records
.
filter
((
record
)
=>
record
.
recordYear
===
year
)
),
]),
]),
);
);
return
{
return
{
groups
:
DEPARTMENT_GROUPS
,
groups
:
DEPARTMENT_GROUPS
,
records
,
records
,
departmentBuckets
,
yearOptions
,
summaries
,
yearScopes
,
departmentBuckets
:
allScope
.
departmentBuckets
,
summaries
:
allScope
.
summaries
,
error
:
''
,
error
:
''
,
isEmpty
:
records
.
length
===
0
,
isEmpty
:
allScope
.
isEmpty
,
};
};
}
catch
(
error
)
{
}
catch
(
error
)
{
return
{
return
{
groups
:
DEPARTMENT_GROUPS
,
groups
:
DEPARTMENT_GROUPS
,
records
:
[],
records
:
[],
yearOptions
:
[],
yearScopes
:
{},
departmentBuckets
:
{},
departmentBuckets
:
{},
summaries
:
{},
summaries
:
{},
error
:
error
?.
message
||
'驾驶舱数据加载失败,请稍后重试。'
,
error
:
error
?.
message
||
'驾驶舱数据加载失败,请稍后重试。'
,
...
...
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