Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
R
risk_case_library
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
risk_case_library
Commits
dd4b6b28
Commit
dd4b6b28
authored
Jun 15, 2026
by
David Yang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 应用切换
parent
dd0aab32
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
279 additions
and
98 deletions
+279
-98
manifest.json
public/manifest.json
+2
-2
ApplicationSwitcherDialog.jsx
src/pages/project_manager_xy/ApplicationSwitcherDialog.jsx
+0
-0
Layout.jsx
src/pages/project_manager_xy/Layout.jsx
+64
-7
audit-material-center.business-flow.md
...project_manager_xy/audit-material-center.business-flow.md
+9
-0
integrity-party.business-flow.md
...pages/project_manager_xy/integrity-party.business-flow.md
+6
-2
index.jsx
src/pages/project_manager_xy/integrity-party/index.jsx
+8
-0
integrityPartyService.js
src/pages/project_manager_xy/integrityPartyService.js
+81
-31
index.jsx
src/pages/project_manager_xy/resource-booking/index.jsx
+16
-22
index.jsx
src/pages/project_manager_xy/resource-stat/index.jsx
+93
-34
No files found.
public/manifest.json
View file @
dd4b6b28
{
"name"
:
"
project_manager_new
"
,
"version"
:
"1.0.
1
"
,
"name"
:
"
risk_case_library
"
,
"version"
:
"1.0.
4
"
,
"build"
:
"abc"
,
"description"
:
"merge master"
,
"date"
:
"2025-07-14 19:27:20"
,
...
...
src/pages/project_manager_xy/ApplicationSwitcherDialog.jsx
0 → 100644
View file @
dd4b6b28
This diff is collapsed.
Click to expand it.
src/pages/project_manager_xy/Layout.jsx
View file @
dd4b6b28
import
ApplicationSwitcherDialog
from
'@/pages/project_manager_xy/ApplicationSwitcherDialog'
;
import
xyLogoSmall
from
'@/assets/xy-logo_small.png'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
SiteContext
}
from
'@/wrapper/Site'
;
import
{
ArchiveBoxIcon
,
Bars3Icon
,
...
...
@@ -180,9 +182,11 @@ function MobileSidebar({ open, close, children }) {
export
default
function
ProjectManagerLayout
()
{
const
location
=
useLocation
();
const
[
isMobile
,
setIsMobile
]
=
useState
(
false
);
const
[
isApplicationDialogOpen
,
setIsApplicationDialogOpen
]
=
useState
(
false
);
const
[
isSidebarCollapsed
,
setIsSidebarCollapsed
]
=
useState
(
false
);
const
[
isMobileSidebarOpen
,
setIsMobileSidebarOpen
]
=
useState
(
false
);
const
userInfo
=
useContext
(
UserContext
);
const
siteInfo
=
useContext
(
SiteContext
);
useEffect
(()
=>
{
if
(
typeof
window
===
'undefined'
)
{
...
...
@@ -224,6 +228,21 @@ export default function ProjectManagerLayout() {
);
},
[
location
.
pathname
]);
const
applicationSwitcherButton
=
(
<
button
type=
"button"
onClick=
{
()
=>
setIsApplicationDialogOpen
(
true
)
}
aria
-
label=
"Open application switcher"
title=
"应用切换"
className=
{
clsx
(
'inline-flex items-center justify-center rounded-[20px] border border-[#f97316]/70 bg-[linear-gradient(180deg,rgba(39,39,42,0.92),rgba(24,24,27,0.96))] text-[#fb923c] shadow-[0_16px_30px_rgba(249,115,22,0.16)] transition duration-200 hover:-translate-y-0.5 hover:border-[#fb923c] hover:text-[#fdba74] hover:shadow-[0_18px_36px_rgba(249,115,22,0.26)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#fb923c]/50'
,
sidebarCollapsed
?
'size-11'
:
'size-14'
,
)
}
>
<
Squares2X2Icon
className=
{
clsx
(
sidebarCollapsed
?
'size-4'
:
'size-5'
)
}
/>
</
button
>
);
return
(
<
div
className=
"flex h-screen overflow-hidden bg-[radial-gradient(circle_at_top_left,_rgba(251,191,188,0.22),_transparent_26%),radial-gradient(circle_at_bottom_right,_rgba(251,207,232,0.2),_transparent_30%),#f8fafc]"
>
<
aside
...
...
@@ -234,22 +253,34 @@ export default function ProjectManagerLayout() {
>
<
div
className=
{
clsx
(
'mb-7 flex items-center'
,
sidebarCollapsed
?
'justify-center'
:
'px-1'
,
'mb-7'
,
sidebarCollapsed
?
'flex flex-col items-center gap-3'
:
'flex items-start justify-between gap-4 px-1'
,
)
}
>
{
sidebarCollapsed
?
(
<>
<
img
src=
{
xyLogoSmall
}
alt=
"XY Logo"
className=
"h-9 w-auto max-w-[52px]
shrink-0 object-contain"
className=
"h-8 w-auto max-w-8
shrink-0 object-contain"
/>
)
:
null
}
{
!
sidebarCollapsed
?
(
{
applicationSwitcherButton
}
</>
)
:
(
<>
<
div
className=
"min-w-0 flex-1"
>
<
h1
className=
"truncate text-sm font-semibold tracking-[0.02em] text-white/95"
>
{
APP_NAME
}
</
h1
>
)
:
null
}
<
div
className=
"mt-1 text-xs text-zinc-400"
>
风险案例与廉洁党建台账工作台
</
div
>
</
div
>
{
applicationSwitcherButton
}
</>
)
}
</
div
>
<
nav
className=
"flex-1 space-y-5 overflow-y-auto"
>
...
...
@@ -275,6 +306,17 @@ export default function ProjectManagerLayout() {
</
div
>
))
}
</
nav
>
{
!
sidebarCollapsed
?
(
<
div
className=
"mt-6 rounded-2xl border border-zinc-800 bg-zinc-950/70 p-4 text-sm text-zinc-300"
>
<
div
className=
"font-medium text-white/90"
>
{
userInfo
?.
display_name
||
userInfo
?.
username
||
'当前用户'
}
</
div
>
<
div
className=
"mt-1 text-xs text-zinc-500"
>
当前正在查看风险案例库
</
div
>
</
div
>
)
:
null
}
</
aside
>
<
div
className=
"relative z-30 hidden lg:flex"
>
...
...
@@ -302,10 +344,11 @@ export default function ProjectManagerLayout() {
close=
{
()
=>
setIsMobileSidebarOpen
(
false
)
}
>
<
div
className=
"px-5 pt-6"
>
<
div
className=
"flex items-center"
>
<
div
>
<
h1
className=
"text-base font-semibold tracking-[0.02em] text-white"
>
{
APP_NAME
}
</
h1
>
<
div
className=
"text-xs text-zinc-500"
>
风险案例库工作台
</
div
>
</
div
>
</
div
>
...
...
@@ -341,12 +384,26 @@ export default function ProjectManagerLayout() {
<
Bars3Icon
className=
"size-5"
/>
</
NavbarItem
>
<
div
className=
"text-sm text-zinc-500"
>
{
currentGroupTitle
}
</
div
>
<
NavbarItem
onClick=
{
()
=>
setIsApplicationDialogOpen
(
true
)
}
aria
-
label=
"Open application switcher"
className=
"border border-rose-200 bg-white text-rose-500 shadow-sm hover:bg-rose-50 hover:text-rose-600"
>
<
Squares2X2Icon
className=
"size-4"
/>
</
NavbarItem
>
</
div
>
<
div
className=
"flex-1 overflow-y-auto rounded-[32px] border border-white/70 bg-white/75 p-4 shadow-[0_24px_80px_rgba(15,23,42,0.06)] backdrop-blur sm:p-6"
>
<
Outlet
/>
</
div
>
</
main
>
<
ApplicationSwitcherDialog
open=
{
isApplicationDialogOpen
}
onClose=
{
()
=>
setIsApplicationDialogOpen
(
false
)
}
userInfo=
{
userInfo
}
siteInfo=
{
siteInfo
}
/>
</
div
>
);
}
src/pages/project_manager_xy/audit-material-center.business-flow.md
View file @
dd4b6b28
...
...
@@ -24,9 +24,16 @@
-
左侧菜单:
`驾驶舱`
-
左侧菜单:
`案例管理`
-
左侧菜单顶部:
`应用切换`
推荐职责划分:
-
`应用切换`
-
桌面端侧栏展开时显示在应用标题右侧,侧栏收起时显示在 Logo 下方
-
移动端显示在顶部菜单栏右侧
-
点击后打开应用切换弹框,优先调用
`/application/info`
读取当前账号可见应用
-
应用列表加载失败时,回退展示当前登录用户信息中的
`applications`
-
点击应用卡片后跳转到对应应用根路径
-
`驾驶舱`
-
展示案例总数、分类数量、标签数量、案例类型数量、待整理数量、最近 7 天更新
-
通过图表展示分类结构、案例类型排行、案例来源分布
...
...
@@ -242,6 +249,7 @@
-
查询按钮
-
筛选项为普通文本输入框,不提供下拉框
-
`Segment`
视图切换(卡片 / 表格)
-
顶部快捷操作区采用紧凑工具条呈现,保留手动新建、Excel 导入、Excel 导出、重置筛选四个入口
-
手动新建按钮
-
Excel 导入按钮
-
导入弹框中的导出模板按钮
...
...
@@ -313,6 +321,7 @@
-
批量导入时,单条失败不应影响其它条目继续导入
-
保存失败时,保留当前表单输入,提示用户重试
-
删除前必须有明确确认,避免误删
-
应用列表加载失败时,应用切换弹框保留打开状态,并提示已回退展示当前账号上的应用信息
---
...
...
src/pages/project_manager_xy/integrity-party.business-flow.md
View file @
dd4b6b28
...
...
@@ -16,7 +16,8 @@
-
左侧菜单:
`驾驶舱`
-
页面内可切换
`案例库统计 / 廉洁党建统计`
-
廉洁党建统计展示台账总数、本年度记录、附件完整率、最近 30 天更新、台账分类分布、年度分布和最近更新台账
-
廉洁党建统计展示台账总数、本年度记录、最近 30 天更新、台账分类分布、年度分布和本年度模块概览
-
本年度模块概览展示培训类 4 个培训类别的开展场次和参加人数、谈话类已谈话人数及投资序列/管理序列人数、调研类/专题会议类/廉洁主题活动/节前廉洁提醒的开展次数
-
左侧菜单:
`廉洁党建建设管理`
-
页面内通过 Tab 切换
`培训类 / 谈话类 / 调研类 / 专题会议类 / 廉洁主题活动 / 节前廉洁提醒`
-
每个 Tab 对应独立台账对象和独立 Excel 模板
...
...
@@ -42,7 +43,7 @@
4.
筛选区统一包含关键词、年份和当前分类关键字段
5.
用户点击
`查询`
后,前端调用
`POST /record/query`
6.
列表按
`updated_at`
、
`created_at`
倒序展示
7.
表格仅展示核心字段
、附件状态、更新时间和操作入口
7.
表格仅展示核心字段
和操作入口;非培训类保留附件状态、更新时间辅助列,培训类外层展示序号、培训类别、时间、授课主题、授课人、参加人数、培训对象、牵头部门,不展示附件状态和更新时间
8.
完整字段在编辑弹窗中维护
### 4.2 手动新建台账
...
...
@@ -114,6 +115,9 @@
-
保存时日期字段转换为 ISO 8601 字符串
-
布尔字段使用三态 UI:是、否、未填写,对应
`true / false / null`
-
培训类别固定为:廉政党课讲授、廉洁合规培训、专题廉洁合规教育培训、新员工廉洁教育培训
-
驾驶舱培训类按
`training_category`
统计本年度场次,并汇总
`participant_count`
为参加人数
-
驾驶舱谈话类仅统计本年度存在
`actual_talk_date`
的已谈话记录;投资序列按
`is_investment_sequence = true`
统计,管理序列按
`is_leader = true`
统计
-
驾驶舱调研类、专题会议类、廉洁主题活动、节前廉洁提醒按本年度记录数统计开展次数
-
重大节假日固定为:元旦、春节、清明、五一、端午、中秋、国庆、其他
-
选择重大节假日
`其他`
时,可填写
`holiday_other`
-
人员、部门、岗位、单位等字段一期全部使用普通文本输入,不做系统引用
...
...
src/pages/project_manager_xy/integrity-party/index.jsx
View file @
dd4b6b28
...
...
@@ -760,8 +760,12 @@ export default function IntegrityPartyPage() {
{
fieldMap
[
fieldName
]?.
label
||
fieldName
}
</
TableHeader
>
))
}
{
activeConfig
.
showAttachmentStatusColumn
===
false
?
null
:
(
<
TableHeader
>
附件状态
</
TableHeader
>
)
}
{
activeConfig
.
showUpdatedAtColumn
===
false
?
null
:
(
<
TableHeader
>
更新时间
</
TableHeader
>
)
}
<
TableHeader
className=
"text-right"
>
操作
</
TableHeader
>
</
TableRow
>
</
TableHead
>
...
...
@@ -790,6 +794,7 @@ export default function IntegrityPartyPage() {
</
span
>
</
TableCell
>
))
}
{
activeConfig
.
showAttachmentStatusColumn
===
false
?
null
:
(
<
TableCell
>
<
span
className=
{
clsx
(
...
...
@@ -802,7 +807,10 @@ export default function IntegrityPartyPage() {
{
record
.
attachmentStatus
}
</
span
>
</
TableCell
>
)
}
{
activeConfig
.
showUpdatedAtColumn
===
false
?
null
:
(
<
TableCell
>
{
formatDateTime
(
record
.
updatedAt
)
}
</
TableCell
>
)
}
<
TableCell
className=
"text-right"
>
<
Button
type=
"button"
plain
onClick=
{
()
=>
openEditDialog
(
record
)
}
>
<
PencilSquareIcon
data
-
slot=
"icon"
/>
...
...
src/pages/project_manager_xy/integrityPartyService.js
View file @
dd4b6b28
...
...
@@ -66,9 +66,13 @@ export const INTEGRITY_LEDGER_CONFIGS = [
'training_category'
,
'training_date'
,
'lecture_topic'
,
'lecturer'
,
'participant_count'
,
'training_target'
,
'lead_department'
,
],
showAttachmentStatusColumn
:
false
,
showUpdatedAtColumn
:
false
,
batchFields
:
[
'training_category'
,
'training_date'
,
'lead_department'
],
fields
:
[
field
(
'序号'
,
'sequence_no'
,
FIELD_TYPES
.
NUMBER
),
...
...
@@ -1235,25 +1239,9 @@ export async function loadIntegrityPartyDashboard() {
const
now
=
dayjs
();
const
currentYear
=
now
.
year
();
const
recentThreshold
=
now
.
subtract
(
30
,
'day'
);
const
totalAttachmentFields
=
records
.
reduce
((
sum
,
record
)
=>
{
const
config
=
getConfig
(
record
.
ledgerKey
);
return
(
sum
+
config
.
fields
.
filter
((
fieldConfig
)
=>
fieldConfig
.
type
===
FIELD_TYPES
.
FILE
)
.
length
);
},
0
);
const
filledAttachmentFields
=
records
.
reduce
((
sum
,
record
)
=>
{
const
config
=
getConfig
(
record
.
ledgerKey
);
return
(
sum
+
config
.
fields
.
filter
((
fieldConfig
)
=>
fieldConfig
.
type
===
FIELD_TYPES
.
FILE
)
.
filter
((
fieldConfig
)
=>
{
return
normalizeFileList
(
record
.
rawMetadata
[
fieldConfig
.
name
]).
length
>
0
;
}).
length
const
currentYearRecords
=
records
.
filter
(
(
record
)
=>
getRecordYear
(
record
)
===
String
(
currentYear
),
);
},
0
);
const
ledgerGroups
=
ledgerResults
.
map
((
result
)
=>
({
key
:
result
.
config
.
key
,
label
:
result
.
config
.
tabLabel
,
...
...
@@ -1266,34 +1254,96 @@ export async function loadIntegrityPartyDashboard() {
(
item
)
=>
item
.
year
||
'未填写年份'
,
(
item
)
=>
item
.
year
||
'未填写年份'
,
);
const
recentRecords
=
[...
records
]
.
sort
((
left
,
right
)
=>
String
(
right
.
updatedAt
||
right
.
createdAt
).
localeCompare
(
String
(
left
.
updatedAt
||
left
.
createdAt
),
),
)
.
slice
(
0
,
8
);
return
{
totalCount
:
ledgerResults
.
reduce
((
sum
,
result
)
=>
sum
+
result
.
total
,
0
),
currentYearCount
:
records
.
filter
((
record
)
=>
getRecordYear
(
record
)
===
String
(
currentYear
))
.
length
,
currentYearCount
:
currentYearRecords
.
length
,
recentCount
:
records
.
filter
((
record
)
=>
{
if
(
!
record
.
updatedAt
)
{
return
false
;
}
return
dayjs
(
record
.
updatedAt
).
isAfter
(
recentThreshold
);
}).
length
,
attachmentCompletionRate
:
totalAttachmentFields
>
0
?
Math
.
round
((
filledAttachmentFields
/
totalAttachmentFields
)
*
100
)
:
0
,
ledgerGroups
,
yearGroups
,
recentRecords
,
moduleOverview
:
buildIntegrityModuleOverview
(
currentYearRecords
,
currentYear
)
,
errors
:
ledgerResults
.
map
((
result
)
=>
result
.
error
).
filter
(
Boolean
),
};
}
function
buildIntegrityModuleOverview
(
currentYearRecords
,
currentYear
)
{
const
trainingRecords
=
currentYearRecords
.
filter
(
(
record
)
=>
record
.
ledgerKey
===
'training'
,
);
const
talkedRecords
=
currentYearRecords
.
filter
((
record
)
=>
{
if
(
record
.
ledgerKey
!==
'talk'
)
{
return
false
;
}
const
actualTalkDate
=
formatDate
(
record
.
rawMetadata
?.
actual_talk_date
??
record
.
metadata
?.
actual_talk_date
,
);
return
actualTalkDate
.
startsWith
(
String
(
currentYear
));
});
return
{
year
:
currentYear
,
trainingCategories
:
TRAINING_CATEGORY_OPTIONS
.
map
((
category
)
=>
{
const
categoryRecords
=
trainingRecords
.
filter
(
(
record
)
=>
String
(
record
.
rawMetadata
?.
training_category
??
record
.
metadata
?.
training_category
??
''
,
).
trim
()
===
category
,
);
return
{
key
:
category
,
label
:
category
,
sessionCount
:
categoryRecords
.
length
,
participantCount
:
categoryRecords
.
reduce
((
sum
,
record
)
=>
{
const
participantCount
=
Number
(
record
.
rawMetadata
?.
participant_count
??
record
.
metadata
?.
participant_count
??
0
,
);
return
sum
+
(
Number
.
isFinite
(
participantCount
)
?
participantCount
:
0
);
},
0
),
};
}),
talkSummary
:
{
totalCount
:
talkedRecords
.
length
,
investmentCount
:
talkedRecords
.
filter
(
(
record
)
=>
normalizeBoolean
(
record
.
rawMetadata
?.
is_investment_sequence
??
record
.
metadata
?.
is_investment_sequence
,
)
===
true
,
).
length
,
managementCount
:
talkedRecords
.
filter
(
(
record
)
=>
normalizeBoolean
(
record
.
rawMetadata
?.
is_leader
??
record
.
metadata
?.
is_leader
)
===
true
,
).
length
,
},
yearlyModuleCounts
:
[
buildYearlyModuleCount
(
currentYearRecords
,
'research'
,
'调研类'
),
buildYearlyModuleCount
(
currentYearRecords
,
'meeting'
,
'专题会议类'
),
buildYearlyModuleCount
(
currentYearRecords
,
'activity'
,
'廉洁主题活动'
),
buildYearlyModuleCount
(
currentYearRecords
,
'holiday_reminder'
,
'节前廉洁提醒'
),
],
};
}
function
buildYearlyModuleCount
(
records
,
ledgerKey
,
label
)
{
return
{
key
:
ledgerKey
,
label
,
count
:
records
.
filter
((
record
)
=>
record
.
ledgerKey
===
ledgerKey
).
length
,
};
}
function
getRecordYear
(
record
)
{
const
config
=
getConfig
(
record
.
ledgerKey
);
const
yearField
=
config
.
fields
.
find
(
...
...
src/pages/project_manager_xy/resource-booking/index.jsx
View file @
dd4b6b28
...
...
@@ -1049,33 +1049,27 @@ export default function ResourceBookingPage() {
return (
<div className="space-y-6">
<Panel className="relative overflow-hidden p-
6 sm:p-8
">
<div className="absolute inset-0 bg-[linear-gradient(135deg,rgba(255,255,255,0.9
6),rgba(255,241,242,0.94)),radial-gradient(circle_at_top_left,rgba(251,191,188,0.2),transparent_36%),radial-gradient(circle_at_top_right,rgba(251,113,133,0.16),transparent_34%
)]" />
<Panel className="relative overflow-hidden p-
4 sm:p-5
">
<div className="absolute inset-0 bg-[linear-gradient(135deg,rgba(255,255,255,0.9
8),rgba(255,247,247,0.94)
)]" />
<div className="relative">
<div className="
mt-5 flex flex-col gap-6
xl:flex-row xl:items-center xl:justify-between">
<div className="m
ax-w-3xl
">
<h1 className="text-
3xl font-semibold tracking-tight text-zinc-950 sm:text-4
xl">
<div className="
flex flex-col gap-4
xl:flex-row xl:items-center xl:justify-between">
<div className="m
in-w-0
">
<h1 className="text-
2xl font-semibold tracking-tight text-zinc-950 sm:text-3
xl">
案例管理
</h1>
</div>
<div className="w-full xl:w-[22rem] xl:shrink-0">
<div className="rounded-[26px] border border-white/70 bg-white/65 p-4 shadow-[0_20px_50px_rgba(148,163,184,0.12)] backdrop-blur-sm">
<div className="flex items-start justify-between gap-4">
<div>
<div className="text-xs font-semibold tracking-[0.18em] text-zinc-500">
<div className="w-full rounded-2xl border border-zinc-200/70 bg-white/80 p-2 shadow-sm backdrop-blur-sm xl:w-auto xl:shrink-0">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
<div className="flex h-10 shrink-0 items-center gap-2 px-2 text-xs font-semibold tracking-[0.16em] text-zinc-500">
<BookOpenIcon className="size-4 text-[var(--pm-brand)]" />
快捷操作
</div>
</div>
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-[linear-gradient(145deg,rgba(180,35,24,0.14),rgba(244,63,94,0.14))] text-[var(--pm-brand)]">
<BookOpenIcon className="size-5" />
</div>
</div>
<div className="
mt-4 space-y-3
">
<div className="
grid flex-1 grid-cols-2 gap-2 sm:flex sm:flex-wrap sm:justify-end
">
<Button
color="blue"
className="w-full justify-center whitespace-nowrap cursor-pointer"
className="w-full justify-center whitespace-nowrap cursor-pointer
sm:w-auto
"
onClick={() => {
setError('');
setNotice('');
...
...
@@ -1087,7 +1081,7 @@ export default function ResourceBookingPage() {
</Button>
<Button
outline
className="w-full justify-center whitespace-nowrap bg-white/80 cursor-pointer"
className="w-full justify-center whitespace-nowrap bg-white/80 cursor-pointer
sm:w-auto
"
onClick={() => {
setError('');
setNotice('');
...
...
@@ -1099,7 +1093,7 @@ export default function ResourceBookingPage() {
</Button>
<Button
outline
className="w-full justify-center whitespace-nowrap bg-white/80 cursor-pointer"
className="w-full justify-center whitespace-nowrap bg-white/80 cursor-pointer
sm:w-auto
"
onClick={handleExportCases}
disabled={exporting || loading}
>
...
...
@@ -1108,7 +1102,7 @@ export default function ResourceBookingPage() {
</Button>
<Button
plain
className="w-full justify-center whitespace-nowrap text-zinc-600 cursor-pointer"
className="w-full justify-center whitespace-nowrap text-zinc-600 cursor-pointer
sm:w-auto
"
onClick={handleResetFilters}
>
<ArrowPathIcon className="size-4" />
...
...
@@ -1122,7 +1116,7 @@ export default function ResourceBookingPage() {
{error ? (
<div
className={clsx(
'mt-
6
rounded-2xl border px-4 py-3 text-sm',
'mt-
4
rounded-2xl border px-4 py-3 text-sm',
isDemoMode
? 'border-amber-200 bg-amber-50 text-amber-700'
: 'border-rose-200 bg-rose-50 text-rose-700',
...
...
@@ -1133,7 +1127,7 @@ export default function ResourceBookingPage() {
) : null}
{!error && notice ? (
<div className="mt-
6
rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700">
<div className="mt-
4
rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700">
{notice}
</div>
) : null}
...
...
src/pages/project_manager_xy/resource-stat/index.jsx
View file @
dd4b6b28
...
...
@@ -4,7 +4,6 @@ import {
ChartPieIcon
,
ClipboardDocumentListIcon
,
DocumentTextIcon
,
PaperClipIcon
,
RectangleStackIcon
,
TagIcon
,
}
from
'@heroicons/react/24/outline'
;
...
...
@@ -404,7 +403,7 @@ export default function ResourceStatPage() {
return
(
<
div
className=
"space-y-6"
>
<
Panel
className=
"relative overflow-hidden p-6 sm:p-8"
>
<
div
className=
"absolute inset-
x-0 top-0 h-40 bg-[radial-gradient(circle_at_top_left,_rgba(245,158,11,0.16),_transparent_40%),radial-gradient(circle_at_top_right,_rgba(14,165,233,0.18),_transparent_35
%)]"
/>
<
div
className=
"absolute inset-
0 bg-[linear-gradient(135deg,rgba(255,247,237,0.94)_0%,rgba(255,255,255,0.9)_48%,rgba(224,242,254,0.86)_100%),radial-gradient(circle_at_top_left,rgba(245,158,11,0.14),transparent_36%),radial-gradient(circle_at_top_right,rgba(14,165,233,0.18),transparent_34
%)]"
/>
<
div
className=
"relative"
>
<
div
className=
"mt-5 flex flex-col gap-6 xl:flex-row xl:items-end xl:justify-between"
>
<
div
className=
"max-w-3xl"
>
...
...
@@ -575,15 +574,23 @@ function IntegrityDashboardView({ dashboard, loading }) {
totalCount
:
0
,
currentYearCount
:
0
,
recentCount
:
0
,
attachmentCompletionRate
:
0
,
ledgerGroups
:
[],
yearGroups
:
[],
recentRecords
:
[],
moduleOverview
:
{
year
:
new
Date
().
getFullYear
(),
trainingCategories
:
[],
talkSummary
:
{
totalCount
:
0
,
investmentCount
:
0
,
managementCount
:
0
,
},
yearlyModuleCounts
:
[],
},
};
return
(
<>
<
div
className=
"grid gap-4 md:grid-cols-
2 xl:grid-cols-4
"
>
<
div
className=
"grid gap-4 md:grid-cols-
3
"
>
<
MetricCard
icon=
{
ClipboardDocumentListIcon
}
label=
"台账总数"
...
...
@@ -596,12 +603,6 @@ function IntegrityDashboardView({ dashboard, loading }) {
value=
{
safeDashboard
.
currentYearCount
}
tone=
"green"
/>
<
MetricCard
icon=
{
PaperClipIcon
}
label=
"附件完整率"
value=
{
`${safeDashboard.attachmentCompletionRate}%`
}
tone=
"amber"
/>
<
MetricCard
icon=
{
ArrowTrendingUpIcon
}
label=
"最近 30 天更新"
...
...
@@ -615,39 +616,97 @@ function IntegrityDashboardView({ dashboard, loading }) {
<
ChartPanel
title=
"年度分布"
groups=
{
safeDashboard
.
yearGroups
}
/>
</
div
>
<
IntegrityModuleOverview
overview=
{
safeDashboard
.
moduleOverview
}
loading=
{
loading
}
/>
</>
);
}
function
IntegrityModuleOverview
({
overview
,
loading
})
{
const
safeOverview
=
overview
||
{
year
:
new
Date
().
getFullYear
(),
trainingCategories
:
[],
talkSummary
:
{
totalCount
:
0
,
investmentCount
:
0
,
managementCount
:
0
,
},
yearlyModuleCounts
:
[],
};
const
talkItems
=
[
[
'已谈话人数'
,
safeOverview
.
talkSummary
.
totalCount
],
[
'投资序列'
,
safeOverview
.
talkSummary
.
investmentCount
],
[
'管理序列'
,
safeOverview
.
talkSummary
.
managementCount
],
];
return
(
<
Panel
className=
"p-6"
>
<
SectionHeader
title=
"最近更新台账"
/>
<
div
className=
"space-y-3"
>
{
safeDashboard
.
recentRecords
.
length
>
0
?
(
safeDashboard
.
recentRecords
.
map
((
item
)
=>
(
<
SectionHeader
title=
{
`${safeOverview.year} 年模块概览`
}
/>
{
loading
?
(
<
div
className=
"text-sm text-zinc-400"
>
正在加载廉洁党建统计...
</
div
>
)
:
(
<
div
className=
"grid gap-6 xl:grid-cols-[1.35fr_0.9fr_1fr]"
>
<
div
>
<
div
className=
"mb-3 text-sm font-semibold text-zinc-900"
>
培训类
</
div
>
<
div
className=
"overflow-hidden rounded-2xl border border-zinc-200"
>
<
div
className=
"grid grid-cols-[1fr_88px_88px] bg-zinc-50 px-4 py-3 text-xs font-semibold text-zinc-500"
>
<
div
>
培训类别
</
div
>
<
div
className=
"text-right"
>
场次
</
div
>
<
div
className=
"text-right"
>
人数
</
div
>
</
div
>
{
safeOverview
.
trainingCategories
.
map
((
item
)
=>
(
<
div
key=
{
item
.
id
}
className=
"flex flex-col gap-3 rounded-[24px] border border-zinc-200 bg-zinc-50/70 px-4 py-4 sm:flex-row sm:items-center sm:justify-between
"
key=
{
item
.
key
}
className=
"grid grid-cols-[1fr_88px_88px] border-t border-zinc-200 px-4 py-3 text-sm
"
>
<
div
className=
"min-w-0"
>
<
div
className=
"flex flex-wrap items-center gap-2"
>
<
div
className=
"truncate text-sm font-semibold text-zinc-950 sm:text-base"
>
{
item
.
title
}
<
div
className=
"min-w-0 truncate font-medium text-zinc-800"
>
{
item
.
label
}
</
div
>
<
Pill
>
{
item
.
ledgerLabel
}
</
Pill
>
<
Pill
tone=
{
item
.
attachmentCount
>
0
?
'green'
:
'amber'
}
>
附件
{
item
.
attachmentCount
}
</
Pill
>
<
div
className=
"text-right tabular-nums text-zinc-950"
>
{
item
.
sessionCount
}
</
div
>
<
div
className=
"
mt-2 text-sm text-zinc-50
0"
>
更新于
{
item
.
updatedAtLabel
}
<
div
className=
"
text-right tabular-nums text-zinc-95
0"
>
{
item
.
participantCount
}
</
div
>
</
div
>
<
div
className=
"text-sm text-zinc-500"
>
{
item
.
attachmentStatus
}
</
div
>
))
}
</
div
>
))
)
:
(
<
div
className=
"text-sm text-zinc-400"
>
{
loading
?
'正在加载廉洁党建统计...'
:
'暂无最近更新台账'
}
</
div
>
)
}
<
div
>
<
div
className=
"mb-3 text-sm font-semibold text-zinc-900"
>
谈话类
</
div
>
<
div
className=
"grid gap-3"
>
{
talkItems
.
map
(([
label
,
value
])
=>
(
<
div
key=
{
label
}
className=
"flex items-center justify-between rounded-2xl border border-zinc-200 bg-zinc-50 px-4 py-3"
>
<
div
className=
"text-sm text-zinc-600"
>
{
label
}
</
div
>
<
div
className=
"text-2xl font-semibold tabular-nums text-zinc-950"
>
{
value
}
</
div
>
</
div
>
))
}
</
div
>
</
div
>
<
div
>
<
div
className=
"mb-3 text-sm font-semibold text-zinc-900"
>
年度开展次数
</
div
>
<
div
className=
"space-y-2"
>
{
safeOverview
.
yearlyModuleCounts
.
map
((
item
)
=>
(
<
div
key=
{
item
.
key
}
className=
"flex items-center justify-between border-b border-zinc-200 py-3 last:border-b-0"
>
<
div
className=
"text-sm font-medium text-zinc-700"
>
{
item
.
label
}
</
div
>
<
div
className=
"text-xl font-semibold tabular-nums text-zinc-950"
>
{
item
.
count
}
</
div
>
</
div
>
))
}
</
div
>
</
div
>
</
div
>
)
}
</
Panel
>
</>
);
}
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