Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
I
incorrupt_party_build
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
incorrupt_party_build
Commits
31252990
Commit
31252990
authored
Sep 30, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: refine ledger workflows and dashboard year ordering
parent
b202a652
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
187 additions
and
24 deletions
+187
-24
manifest.json
public/manifest.json
+1
-1
XinyuanAutocomplete.jsx
src/components/XinyuanAutocomplete.jsx
+3
-3
XinyuanModal.jsx
src/components/XinyuanModal.jsx
+9
-1
integrity-party.business-flow.md
...pages/project_manager_xy/integrity-party.business-flow.md
+9
-8
index.jsx
src/pages/project_manager_xy/integrity-party/index.jsx
+0
-0
integrityPartyService.js
src/pages/project_manager_xy/integrityPartyService.js
+107
-10
index.jsx
src/pages/project_manager_xy/resource-stat/index.jsx
+19
-1
resource-stat.business-flow.md
...t_manager_xy/resource-stat/resource-stat.business-flow.md
+39
-0
No files found.
public/manifest.json
View file @
31252990
{
{
"name"
:
"incorrupt_party_build"
,
"name"
:
"incorrupt_party_build"
,
"version"
:
"1.0.
6
"
,
"version"
:
"1.0.
3
"
,
"build"
:
"abc"
,
"build"
:
"abc"
,
"description"
:
"党风廉政建设"
,
"description"
:
"党风廉政建设"
,
"date"
:
"2025-07-14 19:27:20"
,
"date"
:
"2025-07-14 19:27:20"
,
...
...
src/components/XinyuanAutocomplete.jsx
View file @
31252990
...
@@ -10,8 +10,8 @@ const optionKey = (option) => option?.id ?? option?.value;
...
@@ -10,8 +10,8 @@ const optionKey = (option) => option?.id ?? option?.value;
const
toSelectionKey
=
(
value
)
=>
String
(
value
??
''
);
const
toSelectionKey
=
(
value
)
=>
String
(
value
??
''
);
export
default
function
XinyuanAutocomplete
({
export
default
function
XinyuanAutocomplete
({
'aria-label'
:
ariaLabelProp
,
ariaLabel
,
className
,
emptyText
=
'暂无可选项'
,
errorText
=
''
,
filter
,
'aria-
describedby'
:
ariaDescribedBy
,
'aria-
label'
:
ariaLabelProp
,
ariaLabel
,
className
,
emptyText
=
'暂无可选项'
,
errorText
=
''
,
filter
,
fullWidth
=
true
,
i
nputValue
,
isDisable
d
=
false
,
isLoading
=
false
,
isOpen
,
isRequired
=
false
,
name
,
fullWidth
=
true
,
i
d
,
inputValue
,
isDisabled
=
false
,
isInvali
d
=
false
,
isLoading
=
false
,
isOpen
,
isRequired
=
false
,
name
,
onInputChange
,
onOpenChange
,
onSelectionChange
,
options
=
[],
placeholder
=
'请选择'
,
placement
=
'bottom start'
,
onInputChange
,
onOpenChange
,
onSelectionChange
,
options
=
[],
placeholder
=
'请选择'
,
placement
=
'bottom start'
,
popoverHeader
,
renderOption
,
searchPlaceholder
=
'输入关键词搜索'
,
selectedKey
,
selectedKeys
,
popoverHeader
,
renderOption
,
searchPlaceholder
=
'输入关键词搜索'
,
selectedKey
,
selectedKeys
,
selectionMode
=
'single'
,
shouldFilter
=
true
,
valueText
,
variant
=
'secondary'
,
selectionMode
=
'single'
,
shouldFilter
=
true
,
valueText
,
variant
=
'secondary'
,
...
@@ -44,7 +44,7 @@ export default function XinyuanAutocomplete({
...
@@ -44,7 +44,7 @@ export default function XinyuanAutocomplete({
};
};
return
(
return
(
<
Autocomplete
{
...
rootSelectionProps
}
aria
-
label=
{
effectiveAriaLabel
}
className=
{
className
}
fullWidth=
{
fullWidth
}
isDisabled=
{
isDisable
d
}
isOpen=
{
isOpen
}
isRequired=
{
isRequired
}
name=
{
name
}
onClear=
{
onClear
}
selectionMode=
{
selectionMode
}
variant=
{
variant
}
onOpenChange=
{
onOpenChange
}
onSelectionChange=
{
onSelectionChange
}
>
<
Autocomplete
{
...
rootSelectionProps
}
aria
-
describedby=
{
ariaDescribedBy
}
aria
-
label=
{
effectiveAriaLabel
}
className=
{
className
}
fullWidth=
{
fullWidth
}
id=
{
id
}
isDisabled=
{
isDisabled
}
isInvalid=
{
isInvali
d
}
isOpen=
{
isOpen
}
isRequired=
{
isRequired
}
name=
{
name
}
onClear=
{
onClear
}
selectionMode=
{
selectionMode
}
variant=
{
variant
}
onOpenChange=
{
onOpenChange
}
onSelectionChange=
{
onSelectionChange
}
>
<
Autocomplete
.
Trigger
>
<
Autocomplete
.
Trigger
>
<
Autocomplete
.
Value
><
span
className=
"block min-w-0 truncate"
title=
{
resolvedValueText
}
>
{
resolvedValueText
}
</
span
></
Autocomplete
.
Value
>
<
Autocomplete
.
Value
><
span
className=
"block min-w-0 truncate"
title=
{
resolvedValueText
}
>
{
resolvedValueText
}
</
span
></
Autocomplete
.
Value
>
{
isClearable
&&
selectionMode
===
'single'
&&
selectedKey
!==
null
&&
selectedKey
!==
undefined
&&
selectedKey
!==
''
?
<
Autocomplete
.
ClearButton
aria
-
label=
"清空选择"
/>
:
null
}
{
isClearable
&&
selectionMode
===
'single'
&&
selectedKey
!==
null
&&
selectedKey
!==
undefined
&&
selectedKey
!==
''
?
<
Autocomplete
.
ClearButton
aria
-
label=
"清空选择"
/>
:
null
}
...
...
src/components/XinyuanModal.jsx
View file @
31252990
import
{
Modal
}
from
'@heroui/react'
;
import
{
Alert
,
Modal
}
from
'@heroui/react'
;
import
clsx
from
'clsx'
;
import
clsx
from
'clsx'
;
const
DIALOG_WIDTH_CLASS
=
{
const
DIALOG_WIDTH_CLASS
=
{
...
@@ -15,6 +15,7 @@ export default function XinyuanModal({
...
@@ -15,6 +15,7 @@ export default function XinyuanModal({
isDismissable
,
isDismissable
,
title
,
title
,
description
,
description
,
error
,
size
=
'lg'
,
size
=
'lg'
,
dialogClassName
,
dialogClassName
,
bodyClassName
,
bodyClassName
,
...
@@ -50,6 +51,13 @@ export default function XinyuanModal({
...
@@ -50,6 +51,13 @@ export default function XinyuanModal({
{
description
?
<
div
className=
"text-sm text-muted"
>
{
description
}
</
div
>
:
null
}
{
description
?
<
div
className=
"text-sm text-muted"
>
{
description
}
</
div
>
:
null
}
</
Modal
.
Header
>
</
Modal
.
Header
>
<
Modal
.
Body
className=
{
bodyClassName
}
style=
{
{
marginTop
:
'0.75rem'
}
}
>
<
Modal
.
Body
className=
{
bodyClassName
}
style=
{
{
marginTop
:
'0.75rem'
}
}
>
{
error
?
(
<
Alert
className=
"sticky top-0 z-10 mb-5"
role=
"alert"
status=
"danger"
>
<
Alert
.
Content
>
<
Alert
.
Description
>
{
error
}
</
Alert
.
Description
>
</
Alert
.
Content
>
</
Alert
>
)
:
null
}
{
children
}
{
children
}
</
Modal
.
Body
>
</
Modal
.
Body
>
{
footer
?
<
Modal
.
Footer
style=
{
{
marginTop
:
'1.5rem'
}
}
>
{
footer
}
</
Modal
.
Footer
>
:
null
}
{
footer
?
<
Modal
.
Footer
style=
{
{
marginTop
:
'1.5rem'
}
}
>
{
footer
}
</
Modal
.
Footer
>
:
null
}
...
...
src/pages/project_manager_xy/integrity-party.business-flow.md
View file @
31252990
...
@@ -42,14 +42,14 @@
...
@@ -42,14 +42,14 @@
1.
用户进入
`党风廉政建设`
1.
用户进入
`党风廉政建设`
2.
通过顶部 Tab 选择台账分类
2.
通过顶部 Tab 选择台账分类
3.
页面展示该分类的表格台账
3.
页面展示该分类的表格台账
4.
筛选区使用可展开的 Surface,统一包含关键词、年份和当前分类关键字段;谈话类额外支持是否党员、是否领导干部、是否投资序列筛选
4.
筛选区使用可展开的 Surface,统一包含关键词、年份和当前分类关键字段;谈话类额外支持是否党员、是否领导干部、是否投资序列筛选
。年份字段为数字时按
`metadata.year`
精确匹配;年份筛选映射到日期字段时,按所选年份首日至下一年首日构造 ISO 8601 的
`$gte / $lt`
半开区间查询
5.
选择类筛选通过 Autocomplete 完成;
页面不显示刷新或查询按钮,筛选条件变化后自动调用
`POST /record/query`
,关键词输入采用约 300ms 延迟以减少重复请求
;筛选 Surface 收起时只占主筛选行高度,点击展开后才增加溢出字段区域
5.
选择类筛选通过 Autocomplete 完成;
填写筛选条件后点击
`查询`
调用
`POST /record/query`
,
`重置筛选`
清空当前分类筛选草稿
;筛选 Surface 收起时只占主筛选行高度,点击展开后才增加溢出字段区域
6.
列表
按
`updated_at`
、
`created_at`
倒序展示
6.
当前表格包含
`sequence_no`
序号列时,请求按
`metadata.sequence_no`
升序排列,并以
`updated_at`
、
`created_at`
倒序作为同序号记录的稳定排序;不含序号列时
按
`updated_at`
、
`created_at`
倒序展示
-
列表标题仅展示业务主题,不在标题末尾拼接日期;历史记录若标题带有自动生成的日期后缀,仅在列表展示时去除,原始字段保持不变
-
列表标题仅展示业务主题,不在标题末尾拼接日期;历史记录若标题带有自动生成的日期后缀,仅在列表展示时去除,原始字段保持不变
7.
表格仅展示核心字段和操作入口,6 类台账统一不展示“附件状态”和“更新时间”列;附件完整清单在编辑弹窗中维护,更新时间继续作为后台排序字段使用
7.
表格仅展示核心字段和操作入口,6 类台账统一不展示“附件状态”和“更新时间”列;附件完整清单在编辑弹窗中维护,更新时间继续作为后台排序字段使用
8.
表格采用固定列宽布局:日期、状态类字段使用紧凑宽度,主题和长文本字段使用较宽列并单行省略,完整内容通过悬停提示查看;列数较多时允许在表格区域横向滚动,避免内容覆盖操作入口
8.
表格采用固定列宽布局:日期、状态类字段使用紧凑宽度,主题和长文本字段使用较宽列并单行省略,完整内容通过悬停提示查看;列数较多时允许在表格区域横向滚动,避免内容覆盖操作入口
9.
完整字段在编辑弹窗中维护
9.
完整字段在编辑弹窗中维护
10.
DataGrid
固定每页展示 10 条,不提供页容量切换
;通过底部 Pagination 翻页
10.
DataGrid
默认每页展示 20 条,支持切换为 50 或 100 条
;通过底部 Pagination 翻页
11.
勾选记录后显示 ActionBar,集中提供导出勾选、批量修改和批量删除入口;清空选择后 ActionBar 关闭
11.
勾选记录后显示 ActionBar,集中提供导出勾选、批量修改和批量删除入口;清空选择后 ActionBar 关闭
12.
Grid View
`shortTitle=true`
时只隐藏标题列,标题数据和操作列入口保持不变;只有一个可访问 View 时不显示二级 View Tabs
12.
Grid View
`shortTitle=true`
时只隐藏标题列,标题数据和操作列入口保持不变;只有一个可访问 View 时不显示二级 View Tabs
13.
存在真实 Grid View 时,普通用户仅可访问明确授权的 View;未配置
`viewAuth`
的历史 View 保持可见。Subject 完全没有 Grid View 时使用 legacy compatibility view 保持固定台账可用;系统管理员及本应用“管理”角色按规则旁路
13.
存在真实 Grid View 时,普通用户仅可访问明确授权的 View;未配置
`viewAuth`
的历史 View 保持可见。Subject 完全没有 Grid View 时使用 legacy compatibility view 保持固定台账可用;系统管理员及本应用“管理”角色按规则旁路
...
@@ -60,7 +60,7 @@
...
@@ -60,7 +60,7 @@
1.
用户点击
`新建记录`
1.
用户点击
`新建记录`
2.
前端根据当前 Tab 字段配置在 HeroUI Modal 中生成表单
2.
前端根据当前 Tab 字段配置在 HeroUI Modal 中生成表单
3.
用户填写最小必填字段
3.
用户填写最小必填字段
;点击保存时前端先校验必填项,未填写时弹窗保持打开,在弹窗顶部展示错误摘要、在对应字段下方展示错误原因,并自动定位到第一个错误字段
4.
附件字段按运行时
`multiple`
支持“材料中心 / 本地上传”;材料中心查询
`am_material`
最近 20 条并搜索标题或文件名
4.
附件字段按运行时
`multiple`
支持“材料中心 / 本地上传”;材料中心查询
`am_material`
最近 20 条并搜索标题或文件名
5.
材料中心选择只写入
`{name,type,size,url}`
快照;本地文件调用
`/upload`
后仅进入详情草稿
5.
材料中心选择只写入
`{name,type,size,url}`
快照;本地文件调用
`/upload`
后仅进入详情草稿
6.
用户保存时先逐文件创建
`am_material`
,
`source_app`
为
`incorrupt_party_build`
;全部成功后再调用
`POST /record`
6.
用户保存时先逐文件创建
`am_material`
,
`source_app`
为
`incorrupt_party_build`
;全部成功后再调用
`POST /record`
...
@@ -79,7 +79,7 @@
...
@@ -79,7 +79,7 @@
### 4.3 编辑台账
### 4.3 编辑台账
1.
用户在表格中点击
`编辑`
1.
用户在表格中点击
`编辑`
2.
页面打开 HeroUI 详情弹窗,展示当前分类全部字段;必填、日期、布尔和选择字段沿用新建表单规则
2.
页面打开 HeroUI 详情弹窗,展示当前分类全部字段;必填、日期、布尔和选择字段沿用新建表单规则
,校验错误直接在当前弹窗内展示
3.
用户修改字段或上传、移除附件
3.
用户修改字段或上传、移除附件
4.
前端调用
`PUT /record/:name`
4.
前端调用
`PUT /record/:name`
5.
保存成功后刷新当前列表
5.
保存成功后刷新当前列表
...
@@ -105,7 +105,7 @@
...
@@ -105,7 +105,7 @@
### 4.6 Excel 导入
### 4.6 Excel 导入
1.
用户在当前 Tab 点击
`下载模板`
1.
用户在当前 Tab 点击
`下载模板`
2.
前端导出当前分类的独立模板
2.
前端导出当前分类的独立模板
;工作簿首个 Sheet 为数据录入页,第二个
`选项说明`
Sheet 从 Subject 字段的
`settings.options`
读取并逐项列出选择类型的选项名称与写入值,没有选择类型字段时显示对应提示
3.
用户维护 Excel 后点击
`Excel 导入`
,在导入弹窗的 DropZone 选择或拖入文件
3.
用户维护 Excel 后点击
`Excel 导入`
,在导入弹窗的 DropZone 选择或拖入文件
4.
前端读取首个工作表并按当前 Tab 字段映射解析,并在弹窗中展示只读预览
4.
前端读取首个工作表并按当前 Tab 字段映射解析,并在弹窗中展示只读预览
5.
附件字段不参与导入
5.
附件字段不参与导入
...
@@ -146,7 +146,8 @@
...
@@ -146,7 +146,8 @@
## 6. 异常处理与限制
## 6. 异常处理与限制
-
保存失败时保留当前表单输入并提示用户
-
保存失败时保留当前表单输入,弹窗保持打开,并在弹窗内提示用户;必填校验同时标记对应字段并定位到第一个错误字段
-
Excel 导入、批量修改和删除确认在弹窗内失败时,错误信息在当前弹窗内展示,不要求用户先关闭弹窗
-
Excel 解析失败时不发起创建请求
-
Excel 解析失败时不发起创建请求
-
Excel 批量导入单条失败不影响其它条目继续导入
-
Excel 批量导入单条失败不影响其它条目继续导入
-
批量删除和单条删除均需要二次确认
-
批量删除和单条删除均需要二次确认
...
...
src/pages/project_manager_xy/integrity-party/index.jsx
View file @
31252990
This diff is collapsed.
Click to expand it.
src/pages/project_manager_xy/integrityPartyService.js
View file @
31252990
...
@@ -828,18 +828,34 @@ function buildYearFilter(config, year = '') {
...
@@ -828,18 +828,34 @@ function buildYearFilter(config, year = '') {
return
null
;
return
null
;
}
}
if
(
yearField
.
type
===
FIELD_TYPES
.
NUMBER
)
{
const
numericYear
=
Number
(
normalizedYear
);
const
numericYear
=
Number
(
normalizedYear
);
return
Number
.
isFinite
(
numericYear
)
if
(
!
Number
.
isInteger
(
numericYear
))
{
?
{
[
`metadata.
${
config
.
yearField
}
`
]:
numericYear
}
return
null
;
:
null
;
}
if
(
yearField
.
type
===
FIELD_TYPES
.
NUMBER
)
{
return
{
[
`metadata.
${
config
.
yearField
}
`
]:
numericYear
};
}
}
if
(
yearField
.
type
===
FIELD_TYPES
.
DATE
)
{
const
startYear
=
String
(
numericYear
).
padStart
(
4
,
'0'
);
const
endYear
=
String
(
numericYear
+
1
).
padStart
(
4
,
'0'
);
return
{
return
{
[
`metadata.
${
config
.
yearField
}
`
]:
{
[
`metadata.
${
config
.
yearField
}
`
]:
{
$regex
:
`^
${
escapeRegExp
(
normalizedYear
)}
`
,
$gte
:
`
${
startYear
}
-01-01T00:00:00.000Z`
,
$lt
:
`
${
endYear
}
-01-01T00:00:00.000Z`
,
},
},
};
};
}
return
null
;
}
function
buildLedgerSort
(
config
)
{
return
config
.
tableColumns
.
includes
(
'sequence_no'
)
?
'metadata.sequence_no -updated_at -created_at'
:
'-updated_at -created_at'
;
}
}
function
buildFocusFilter
(
config
,
value
=
''
)
{
function
buildFocusFilter
(
config
,
value
=
''
)
{
...
@@ -922,7 +938,7 @@ export async function loadLedgerPage(configKey, filters = {}) {
...
@@ -922,7 +938,7 @@ export async function loadLedgerPage(configKey, filters = {}) {
page
,
page
,
page_size
:
pageSize
,
page_size
:
pageSize
,
filter
,
filter
,
sort
:
'-updated_at -created_at'
,
sort
:
buildLedgerSort
(
config
)
,
});
});
const
records
=
extractArray
(
response
).
map
((
record
)
=>
const
records
=
extractArray
(
response
).
map
((
record
)
=>
normalizeLedgerRecord
(
record
,
config
.
key
),
normalizeLedgerRecord
(
record
,
config
.
key
),
...
@@ -1240,6 +1256,89 @@ function createWorkbookFromRows(config, rows = [], sheetName) {
...
@@ -1240,6 +1256,89 @@ function createWorkbookFromRows(config, rows = [], sheetName) {
return
workbook
;
return
workbook
;
}
}
function
normalizeSelectOption
(
option
)
{
if
(
option
&&
typeof
option
===
'object'
)
{
const
label
=
String
(
option
.
label
??
option
.
value
??
''
).
trim
();
const
value
=
String
(
option
.
value
??
option
.
label
??
''
).
trim
();
return
{
label
,
value
};
}
const
value
=
String
(
option
??
''
).
trim
();
return
{
label
:
value
,
value
};
}
function
appendSelectOptionsSheet
(
workbook
,
config
)
{
const
selectFields
=
config
.
fields
.
filter
(
(
fieldConfig
)
=>
fieldConfig
.
type
===
FIELD_TYPES
.
SELECT
,
);
const
rows
=
selectFields
.
flatMap
((
fieldConfig
)
=>
{
if
(
fieldConfig
.
options
.
length
===
0
)
{
return
[{
字段名称
:
fieldConfig
.
label
,
字段别名
:
fieldConfig
.
name
,
选项名称
:
'未配置可选值'
,
选项值
:
''
,
}];
}
return
fieldConfig
.
options
.
map
((
option
,
index
)
=>
{
const
normalizedOption
=
normalizeSelectOption
(
option
);
return
{
字段名称
:
index
===
0
?
fieldConfig
.
label
:
''
,
字段别名
:
index
===
0
?
fieldConfig
.
name
:
''
,
选项名称
:
normalizedOption
.
label
,
选项值
:
normalizedOption
.
value
,
};
});
});
const
worksheet
=
XLSX
.
utils
.
json_to_sheet
(
rows
.
length
>
0
?
rows
:
[{
字段名称
:
'当前模板无选择类型字段'
,
字段别名
:
''
,
选项名称
:
''
,
选项值
:
''
,
}],
{
header
:
[
'字段名称'
,
'字段别名'
,
'选项名称'
,
'选项值'
]
},
);
worksheet
[
'!cols'
]
=
[
{
wch
:
24
},
{
wch
:
32
},
{
wch
:
36
},
{
wch
:
36
},
];
XLSX
.
utils
.
book_append_sheet
(
workbook
,
worksheet
,
'选项说明'
);
}
async
function
buildTemplateImportConfig
(
config
)
{
const
subject
=
await
loadSubject
(
config
.
subject
);
const
subjectFieldMap
=
new
Map
(
(
Array
.
isArray
(
subject
?.
fields
)
?
subject
.
fields
:
[]).
map
((
subjectField
)
=>
[
subjectField
.
name
,
subjectField
,
]),
);
return
{
...
config
,
fields
:
config
.
fields
.
filter
((
fieldConfig
)
=>
fieldConfig
.
type
!==
FIELD_TYPES
.
FILE
)
.
map
((
fieldConfig
)
=>
{
const
subjectField
=
subjectFieldMap
.
get
(
fieldConfig
.
name
);
const
type
=
subjectField
?.
type
||
fieldConfig
.
type
;
return
{
...
fieldConfig
,
type
,
options
:
type
===
FIELD_TYPES
.
SELECT
&&
Array
.
isArray
(
subjectField
?.
settings
?.
options
)
?
subjectField
.
settings
.
options
:
[],
};
}),
};
}
export
async
function
exportLedgerWorkbook
(
configKey
,
filters
=
{})
{
export
async
function
exportLedgerWorkbook
(
configKey
,
filters
=
{})
{
const
records
=
await
loadAllLedgerRecords
(
configKey
,
filters
);
const
records
=
await
loadAllLedgerRecords
(
configKey
,
filters
);
return
exportSelectedLedgerWorkbook
(
configKey
,
records
);
return
exportSelectedLedgerWorkbook
(
configKey
,
records
);
...
@@ -1261,11 +1360,9 @@ export async function exportSelectedLedgerWorkbook(configKey, records = []) {
...
@@ -1261,11 +1360,9 @@ export async function exportSelectedLedgerWorkbook(configKey, records = []) {
export
async
function
exportLedgerTemplateWorkbook
(
configKey
)
{
export
async
function
exportLedgerTemplateWorkbook
(
configKey
)
{
const
config
=
getConfig
(
configKey
);
const
config
=
getConfig
(
configKey
);
const
importFields
=
{
const
importFields
=
await
buildTemplateImportConfig
(
config
);
...
config
,
fields
:
config
.
fields
.
filter
((
fieldConfig
)
=>
fieldConfig
.
type
!==
FIELD_TYPES
.
FILE
),
};
const
workbook
=
createWorkbookFromRows
(
importFields
,
[],
`
${
config
.
tabLabel
}
导入模板`
);
const
workbook
=
createWorkbookFromRows
(
importFields
,
[],
`
${
config
.
tabLabel
}
导入模板`
);
appendSelectOptionsSheet
(
workbook
,
importFields
);
const
fileName
=
`
${
config
.
filePrefix
}
_导入模板_
${
dayjs
().
format
(
'YYYYMMDD_HHmmss'
)}
.xlsx`
;
const
fileName
=
`
${
config
.
filePrefix
}
_导入模板_
${
dayjs
().
format
(
'YYYYMMDD_HHmmss'
)}
.xlsx`
;
XLSX
.
writeFile
(
workbook
,
fileName
);
XLSX
.
writeFile
(
workbook
,
fileName
);
...
...
src/pages/project_manager_xy/resource-stat/index.jsx
View file @
31252990
...
@@ -66,6 +66,22 @@ function EmptyBlock({ title }) {
...
@@ -66,6 +66,22 @@ function EmptyBlock({ title }) {
);
);
}
}
function
compareYearGroups
(
left
,
right
)
{
const
leftYear
=
Number
.
parseInt
(
String
(
left
?.
label
??
''
),
10
);
const
rightYear
=
Number
.
parseInt
(
String
(
right
?.
label
??
''
),
10
);
const
leftHasYear
=
Number
.
isFinite
(
leftYear
);
const
rightHasYear
=
Number
.
isFinite
(
rightYear
);
if
(
leftHasYear
&&
rightHasYear
)
{
return
leftYear
-
rightYear
;
}
if
(
leftHasYear
)
return
-
1
;
if
(
rightHasYear
)
return
1
;
return
String
(
left
?.
label
??
''
).
localeCompare
(
String
(
right
?.
label
??
''
),
'zh-CN'
);
}
function
LedgerDistributionChart
({
groups
})
{
function
LedgerDistributionChart
({
groups
})
{
const
data
=
groups
.
map
((
item
)
=>
({
const
data
=
groups
.
map
((
item
)
=>
({
name
:
item
.
label
,
name
:
item
.
label
,
...
@@ -81,7 +97,9 @@ function LedgerDistributionChart({ groups }) {
...
@@ -81,7 +97,9 @@ function LedgerDistributionChart({ groups }) {
}
}
function
YearDistributionChart
({
groups
})
{
function
YearDistributionChart
({
groups
})
{
const
data
=
groups
.
map
((
item
)
=>
({
const
data
=
[...
groups
]
.
sort
(
compareYearGroups
)
.
map
((
item
)
=>
({
name
:
item
.
label
,
name
:
item
.
label
,
count
:
Number
(
item
.
count
||
0
),
count
:
Number
(
item
.
count
||
0
),
}));
}));
...
...
src/pages/project_manager_xy/resource-stat/resource-stat.business-flow.md
0 → 100644
View file @
31252990
# 驾驶舱流程文档
## 1. 功能入口
-
左侧菜单进入
`驾驶舱`
-
页面展示党风廉政 6 类台账的汇总统计,不提供编辑入口或筛选条件
## 2. 关键交互与状态
1.
页面进入后并行查询培训、谈话、调研、专题会议、廉洁主题活动、节前廉洁提醒 6 类台账
2.
加载期间展示加载状态;全部请求结束后展示指标卡、台账分类分布、年度分布和本年度模块概览
3.
部分或全部请求失败时,在页面顶部展示错误信息;成功返回的数据仍按现有结果展示
4.
查询结果为空时,对应图表或概览区域展示空状态
## 3. 年度分布规则
-
年份取自各台账配置的年度字段:日期字段截取年份,数字年份字段直接转为文本
-
横轴按年份从小到大排列,例如
`2025、2026`
,不按各年份记录数量排序
-
无法识别为年份的分组排在有效年份之后,并保留其分组标签
-
柱顶直接显示记录数,悬停提示展示年份和对应记录数量
## 4. 数据对象与统计口径
-
数据通过
`POST /record/query`
读取,Subject 为:
-
`integrity_party_training`
-
`integrity_party_talk`
-
`integrity_party_research`
-
`integrity_party_meeting`
-
`integrity_party_activity`
-
`integrity_party_holiday_reminder`
-
页面只读取台账数据,不修改记录状态或业务字段
-
单类台账当前最多读取 500 条;年度分布基于已读取记录计算
-
本年度模块概览的详细统计口径沿用
`integrity-party.business-flow.md`
## 5. 异常与限制
-
API 错误信息统一展示在页面顶部,不隐藏已成功加载的统计结果
-
单类台账超过 500 条时,当前年度分布可能不包含第 501 条及之后的记录
-
年度字段为空时归入非年份分组,并排在有效年份之后
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