Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
I
integrity_risk_prevention
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
integrity_risk_prevention
Commits
6c54c130
Commit
6c54c130
authored
Aug 05, 2026
by
David Yang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 恢复廉洁风险配置表格交互
parent
e446f89a
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
449 additions
and
43 deletions
+449
-43
RiskDataWorkspace.jsx
src/pages/integrity_risk/RiskDataWorkspace.jsx
+220
-35
integrity-risk-prevention.business-flow.md
...integrity_risk/integrity-risk-prevention.business-flow.md
+3
-3
integrityRiskService.js
src/pages/integrity_risk/integrityRiskService.js
+226
-5
No files found.
src/pages/integrity_risk/RiskDataWorkspace.jsx
View file @
6c54c130
...
...
@@ -40,6 +40,7 @@ import {
}
from
'@/catalyst-ui-kit/javascript/table'
;
import
{
Textarea
}
from
'@/catalyst-ui-kit/javascript/textarea'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
*
as
Headless
from
'@headlessui/react'
;
import
{
ArchiveBoxArrowDownIcon
,
ArrowDownTrayIcon
,
...
...
@@ -64,6 +65,7 @@ import dayjs from 'dayjs';
import
{
useContext
,
useEffect
,
Fragment
,
useMemo
,
useRef
,
useState
,
...
...
@@ -77,10 +79,13 @@ import {
exportRiskWorkbook
,
formatCellValue
,
getColumnLabel
,
getColumnFieldName
,
getConfiguredColumns
,
getFieldLabel
,
getFieldName
,
getFieldOptions
,
getFieldType
,
getTableHeaderGroups
,
importDisplayWorkbook
,
importMeasureWorkbook
,
isFieldMultiple
,
...
...
@@ -146,8 +151,9 @@ function getRecordTone(value) {
return
'zinc'
;
}
function
CellValue
({
record
,
name
})
{
const
value
=
formatCellValue
(
record
,
name
);
function
CellValue
({
record
,
column
})
{
const
name
=
getColumnFieldName
(
column
);
const
value
=
formatCellValue
(
record
,
column
);
const
isStatus
=
[
'probability'
,
'hazard_degree'
,
...
...
@@ -339,18 +345,76 @@ function DynamicField({ field, value, onChange, disabled, error }) {
);
}
export
function
RiskRecordDialog
({
open
,
onClose
,
config
,
subject
,
record
,
onSaved
})
{
function
getDrawerFieldGroups
(
fields
,
view
)
{
const
groups
=
view
?.
settings
?.
groups
||
view
?.
groups
||
[];
if
(
!
Array
.
isArray
(
groups
)
||
groups
.
length
===
0
)
return
[];
const
groupByField
=
new
Map
();
groups
.
forEach
((
group
)
=>
{
const
id
=
group
.
id
||
group
.
name
;
(
group
.
children
||
[]).
forEach
((
child
)
=>
{
groupByField
.
set
(
String
(
child
).
replace
(
/^metadata
\.
/
,
''
),
{
id
,
label
:
group
.
name
||
group
.
label
||
id
,
});
});
});
const
renderedGroups
=
new
Set
();
const
renderedFields
=
new
Set
();
const
sections
=
[];
fields
.
forEach
((
field
)
=>
{
const
name
=
getFieldName
(
field
);
if
(
renderedFields
.
has
(
name
))
return
;
const
group
=
groupByField
.
get
(
name
);
if
(
!
group
||
renderedGroups
.
has
(
group
.
id
))
{
sections
.
push
({
id
:
name
,
label
:
''
,
fields
:
[
field
]
});
renderedFields
.
add
(
name
);
return
;
}
const
groupFields
=
fields
.
filter
((
item
)
=>
groupByField
.
get
(
getFieldName
(
item
))?.
id
===
group
.
id
);
groupFields
.
forEach
((
item
)
=>
renderedFields
.
add
(
getFieldName
(
item
)));
renderedGroups
.
add
(
group
.
id
);
sections
.
push
({
id
:
group
.
id
,
label
:
group
.
label
,
fields
:
groupFields
});
});
return
sections
;
}
export
function
RiskRecordDialog
({
open
,
onClose
,
config
,
subject
,
view
,
record
,
onSaved
})
{
const
[
editing
,
setEditing
]
=
useState
(
!
record
);
const
[
metadata
,
setMetadata
]
=
useState
({});
const
[
errors
,
setErrors
]
=
useState
({});
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
field
s
=
useMemo
(
const
displayColumn
s
=
useMemo
(
()
=>
fieldsOf
(
subject
).
filter
((
field
)
=>
{
getConfiguredColumns
({
subject
,
view
,
fallbackColumns
:
config
.
columns
,
sampleRecord
:
record
,
hideRecordTitle
:
config
.
drawerConfig
?.
hideRecordTitle
,
}),
[
subject
,
view
,
config
,
record
],
);
const
fields
=
useMemo
(
()
=>
{
const
subjectFields
=
fieldsOf
(
subject
);
const
configuredFields
=
displayColumns
.
map
((
column
)
=>
column
.
fieldDef
)
.
filter
((
field
)
=>
subjectFields
.
some
((
subjectField
)
=>
getFieldName
(
subjectField
)
===
getFieldName
(
field
)),
);
const
source
=
configuredFields
.
length
>
0
?
configuredFields
:
subjectFields
;
return
source
.
filter
((
field
)
=>
{
const
type
=
getFieldType
(
field
);
return
!
[
'formula'
,
'computed'
].
includes
(
type
)
&&
!
field
?.
settings
?.
hidden
&&
!
field
?.
hidden
;
}),
[
subject
],
});
},
[
subject
,
displayColumns
],
);
const
drawerFieldGroups
=
useMemo
(
()
=>
getDrawerFieldGroups
(
fields
,
view
),
[
fields
,
view
],
);
useEffect
(()
=>
{
...
...
@@ -397,16 +461,7 @@ export function RiskRecordDialog({ open, onClose, config, subject, record, onSav
}
};
return
(
<
Dialog
open=
{
open
}
onClose=
{
onClose
}
size=
"5xl"
>
<
DialogTitle
>
{
record
?
'记录详情'
:
`新增${config.title}`
}
</
DialogTitle
>
<
DialogDescription
>
{
editing
?
'业务字段将统一保存到 metadata 对象中。'
:
'查看完整字段信息。'
}
</
DialogDescription
>
<
DialogBody
>
{
fields
.
length
>
0
?
(
<
div
className=
"grid gap-x-6 gap-y-5 md:grid-cols-2"
>
{
fields
.
map
((
field
)
=>
{
const
renderField
=
(
field
)
=>
{
const
name
=
getFieldName
(
field
);
return
(
<
Field
...
...
@@ -429,15 +484,61 @@ export function RiskRecordDialog({ open, onClose, config, subject, record, onSav
{
errors
[
name
]
?
<
ErrorMessage
>
{
errors
[
name
]
}
</
ErrorMessage
>
:
null
}
</
Field
>
);
})
}
};
return
(
<
Headless
.
Dialog
open=
{
open
}
onClose=
{
onClose
}
className=
"relative z-50"
>
<
Headless
.
DialogBackdrop
transition
className=
"fixed inset-0 bg-zinc-950/25 transition duration-150 data-closed:opacity-0"
/>
<
div
className=
"fixed inset-0 overflow-hidden"
>
<
div
className=
"absolute inset-0 overflow-hidden"
>
<
div
className=
"pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-8 sm:pl-16"
>
<
Headless
.
DialogPanel
transition
className=
"pointer-events-auto flex h-full w-screen max-w-5xl flex-col bg-white shadow-2xl ring-1 ring-zinc-950/10 transition duration-150 data-closed:translate-x-full"
>
<
div
className=
"flex items-start justify-between gap-4 border-b border-zinc-200 px-5 py-4 sm:px-6"
>
<
div
>
<
Headless
.
DialogTitle
className=
"text-base font-semibold text-zinc-950"
>
{
record
?
'记录详情'
:
`新增${config.title}`
}
</
Headless
.
DialogTitle
>
<
p
className=
"mt-1 text-sm text-zinc-500"
>
{
editing
?
'业务字段将统一保存到 metadata 对象中。'
:
'查看完整字段信息。'
}
</
p
>
</
div
>
<
Button
plain
onClick=
{
onClose
}
aria
-
label=
"关闭详情"
title=
"关闭详情"
>
<
XMarkIcon
/>
</
Button
>
</
div
>
<
div
className=
"flex-1 overflow-y-auto px-5 py-5 sm:px-6"
>
{
fields
.
length
>
0
?
(
drawerFieldGroups
.
length
>
0
?
(
<
div
className=
"space-y-6"
>
{
drawerFieldGroups
.
map
((
section
)
=>
(
<
section
key=
{
section
.
id
}
className=
{
section
.
label
?
'border-t border-zinc-200 pt-5 first:border-t-0 first:pt-0'
:
''
}
>
{
section
.
label
?
(
<
h3
className=
"mb-4 text-sm font-semibold text-zinc-900"
>
{
section
.
label
}
</
h3
>
)
:
null
}
<
div
className=
"grid gap-x-6 gap-y-5 md:grid-cols-2"
>
{
section
.
fields
.
map
(
renderField
)
}
</
div
>
</
section
>
))
}
</
div
>
)
:
(
<
div
className=
"grid gap-x-6 gap-y-5 md:grid-cols-2"
>
{
fields
.
map
(
renderField
)
}
</
div
>
)
)
:
(
<
div
className=
"rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800"
>
当前 Subject 未返回可编辑字段,请检查线上字段配置。
</
div
>
)
}
</
DialogBody
>
<
DialogActions
>
</
div
>
<
div
className=
"flex flex-col-reverse gap-3 border-t border-zinc-200 px-5 py-4 sm:flex-row sm:justify-end sm:px-6"
>
<
Button
plain
onClick=
{
onClose
}
>
关闭
</
Button
>
{
record
&&
config
.
canEdit
&&
!
editing
?
(
<
Button
color=
"blue"
onClick=
{
()
=>
setEditing
(
true
)
}
>
...
...
@@ -461,8 +562,12 @@ export function RiskRecordDialog({ open, onClose, config, subject, record, onSav
</
Button
>
</>
)
:
null
}
</
DialogActions
>
</
Dialog
>
</
div
>
</
Headless
.
DialogPanel
>
</
div
>
</
div
>
</
div
>
</
Headless
.
Dialog
>
);
}
...
...
@@ -547,6 +652,7 @@ export default function RiskDataWorkspace({ workspaceKey }) {
const
fileInputRef
=
useRef
(
null
);
const
[
records
,
setRecords
]
=
useState
([]);
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
view
,
setView
]
=
useState
(
null
);
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
page
,
setPage
]
=
useState
(
0
);
const
[
pageSize
,
setPageSize
]
=
useState
(
20
);
...
...
@@ -572,6 +678,47 @@ export default function RiskDataWorkspace({ workspaceKey }) {
()
=>
new
Map
(
fieldsOf
(
subject
).
map
((
field
)
=>
[
getFieldName
(
field
),
field
])),
[
subject
],
);
const
tableColumns
=
useMemo
(
()
=>
getConfiguredColumns
({
subject
,
view
,
fallbackColumns
:
config
.
columns
,
sampleRecord
:
records
[
0
],
hideRecordTitle
:
config
.
drawerConfig
?.
hideRecordTitle
,
}),
[
subject
,
view
,
config
,
records
],
);
const
tableHeaderGroups
=
useMemo
(
()
=>
getTableHeaderGroups
(
tableColumns
,
view
,
config
.
subject
),
[
tableColumns
,
view
,
config
.
subject
],
);
const
hasGroupedHeaders
=
tableHeaderGroups
.
some
((
item
)
=>
item
.
type
===
'group'
);
const
rowGroupFields
=
useMemo
(
()
=>
(
view
?.
settings
?.
groupCols
||
[])
.
map
((
item
)
=>
item
?.
field
)
.
filter
(
Boolean
),
[
view
],
);
const
groupedRecordSections
=
useMemo
(()
=>
{
if
(
!
rowGroupFields
.
length
)
return
[{
key
:
'__all__'
,
label
:
''
,
records
}];
const
sectionMap
=
new
Map
();
records
.
forEach
((
record
)
=>
{
const
labels
=
rowGroupFields
.
map
((
field
)
=>
{
const
value
=
formatCellValue
(
record
,
field
);
return
`
${
getColumnLabel
(
field
,
subject
)}
:
${
value
===
'-'
?
'空'
:
value
}
`
;
});
const
key
=
labels
.
join
(
' / '
);
if
(
!
sectionMap
.
has
(
key
))
sectionMap
.
set
(
key
,
[]);
sectionMap
.
get
(
key
).
push
(
record
);
});
return
Array
.
from
(
sectionMap
.
entries
()).
map
(([
key
,
items
])
=>
({
key
,
label
:
key
,
records
:
items
,
}));
},
[
records
,
rowGroupFields
,
subject
]);
const
totalPages
=
Math
.
max
(
1
,
Math
.
ceil
(
total
/
pageSize
));
const
load
=
async
()
=>
{
...
...
@@ -585,6 +732,7 @@ export default function RiskDataWorkspace({ workspaceKey }) {
});
setRecords
(
result
.
records
);
setSubject
(
result
.
subject
);
setView
(
result
.
view
);
setTotal
(
result
.
total
);
setSelectedIds
(
new
Set
());
}
catch
(
loadError
)
{
...
...
@@ -819,7 +967,7 @@ export default function RiskDataWorkspace({ workspaceKey }) {
<
Table
dense
striped
bleed
>
<
TableHead
>
<
TableRow
>
<
TableHeader
className=
"w-12"
>
<
TableHeader
className=
"w-12"
rowSpan=
{
hasGroupedHeaders
?
2
:
undefined
}
>
<
Checkbox
color=
"sky"
checked=
{
allCurrentSelected
}
...
...
@@ -830,14 +978,43 @@ export default function RiskDataWorkspace({ workspaceKey }) {
aria
-
label=
"全选当前页"
/>
</
TableHeader
>
{
config
.
columns
.
map
((
name
)
=>
(
<
TableHeader
key=
{
name
}
>
{
getColumnLabel
(
name
,
subject
)
}
</
TableHeader
>
{
(
hasGroupedHeaders
?
tableHeaderGroups
:
tableColumns
.
map
((
column
)
=>
({
type
:
'column'
,
column
}))).
map
((
item
)
=>
(
item
.
type
===
'group'
?
(
<
TableHeader
key=
{
item
.
id
}
colSpan=
{
item
.
columns
.
length
}
className=
"border-x border-zinc-950/5 text-center text-zinc-700"
>
{
item
.
label
}
</
TableHeader
>
)
:
(
<
TableHeader
key=
{
item
.
column
.
field
}
rowSpan=
{
hasGroupedHeaders
?
2
:
undefined
}
>
{
getColumnLabel
(
item
.
column
,
subject
)
}
</
TableHeader
>
)
))
}
<
TableHeader
className=
"w-16"
rowSpan=
{
hasGroupedHeaders
?
2
:
undefined
}
>
<
span
className=
"sr-only"
>
操作
</
span
>
</
TableHeader
>
</
TableRow
>
{
hasGroupedHeaders
?
(
<
TableRow
>
{
tableHeaderGroups
.
filter
((
item
)
=>
item
.
type
===
'group'
)
.
flatMap
((
item
)
=>
item
.
columns
)
.
map
((
column
)
=>
(
<
TableHeader
key=
{
column
.
field
}
>
{
getColumnLabel
(
column
,
subject
)
}
</
TableHeader
>
))
}
<
TableHeader
className=
"w-16"
><
span
className=
"sr-only"
>
操作
</
span
></
TableHeader
>
</
TableRow
>
)
:
null
}
</
TableHead
>
<
TableBody
>
{
records
.
map
((
record
)
=>
(
{
groupedRecordSections
.
map
((
section
)
=>
(
<
Fragment
key=
{
section
.
key
}
>
{
section
.
label
?
(
<
TableRow
>
<
TableCell
colSpan=
{
tableColumns
.
length
+
2
}
className=
"bg-zinc-50 py-2 text-xs font-medium text-zinc-600"
>
{
section
.
label
}
(
{
section
.
records
.
length
}
)
</
TableCell
>
</
TableRow
>
)
:
null
}
{
section
.
records
.
map
((
record
)
=>
(
<
TableRow
key=
{
recordKey
(
record
)
}
className=
"hover:bg-zinc-50"
>
<
TableCell
>
<
Checkbox
...
...
@@ -847,9 +1024,9 @@ export default function RiskDataWorkspace({ workspaceKey }) {
aria
-
label=
{
`选择记录 ${record.title || recordKey(record)}`
}
/>
</
TableCell
>
{
config
.
columns
.
map
((
name
)
=>
(
<
TableCell
key=
{
name
}
onClick=
{
()
=>
openRecord
(
record
)
}
className=
"cursor-pointer"
>
<
CellValue
record=
{
record
}
name=
{
name
}
/>
{
tableColumns
.
map
((
column
)
=>
(
<
TableCell
key=
{
column
.
field
}
onClick=
{
()
=>
openRecord
(
record
)
}
className=
"cursor-pointer"
>
<
CellValue
record=
{
record
}
column=
{
column
}
/>
</
TableCell
>
))
}
<
TableCell
>
...
...
@@ -885,6 +1062,8 @@ export default function RiskDataWorkspace({ workspaceKey }) {
</
TableCell
>
</
TableRow
>
))
}
</
Fragment
>
))
}
</
TableBody
>
</
Table
>
</
div
>
...
...
@@ -901,15 +1080,20 @@ export default function RiskDataWorkspace({ workspaceKey }) {
/>
<
button
type=
"button"
onClick=
{
()
=>
openRecord
(
record
)
}
className=
"min-w-0 flex-1 text-left"
>
<
div
className=
"font-medium text-zinc-950"
>
{
formatCellValue
(
record
,
config
.
columns
[
0
])
}
{
formatCellValue
(
record
,
tableColumns
[
0
]
||
config
.
columns
[
0
])
}
</
div
>
<
div
className=
"mt-2 line-clamp-3 text-sm text-zinc-600"
>
{
formatCellValue
(
record
,
config
.
columns
.
find
((
name
)
=>
[
'integrity_risk_points'
,
'description'
,
'title'
].
includes
(
name
))
||
config
.
columns
[
1
])
}
{
formatCellValue
(
record
,
tableColumns
.
find
((
column
)
=>
[
'integrity_risk_points'
,
'description'
,
'title'
].
includes
(
getColumnFieldName
(
column
)),
)
||
tableColumns
[
1
]
||
config
.
columns
[
1
],
)
}
</
div
>
<
div
className=
"mt-3 flex flex-wrap gap-2"
>
{
config
.
columns
.
slice
(
1
,
5
).
map
((
name
)
=>
{
const
value
=
formatCellValue
(
record
,
name
);
return
value
===
'-'
?
null
:
<
Badge
key=
{
name
}
color=
{
getRecordTone
(
value
)
}
>
{
value
}
</
Badge
>;
{
tableColumns
.
slice
(
1
,
5
).
map
((
column
)
=>
{
const
value
=
formatCellValue
(
record
,
column
);
return
value
===
'-'
?
null
:
<
Badge
key=
{
column
.
field
}
color=
{
getRecordTone
(
value
)
}
>
{
value
}
</
Badge
>;
})
}
</
div
>
</
button
>
...
...
@@ -949,6 +1133,7 @@ export default function RiskDataWorkspace({ workspaceKey }) {
onClose=
{
()
=>
setRecordDialogOpen
(
false
)
}
config=
{
config
}
subject=
{
subject
}
view=
{
view
}
record=
{
activeRecord
}
onSaved=
{
async
(
message
)
=>
{
setNotice
(
message
);
...
...
src/pages/integrity_risk/integrity-risk-prevention.business-flow.md
View file @
6c54c130
...
...
@@ -14,11 +14,11 @@
## 3. 通用交互流程
1.
页面进入时调用
`GET /subject/:name`
加载字段定义
,并
调用
`POST /record/query`
与
`POST /record/count`
分页加载记录。
1.
页面进入时调用
`GET /subject/:name`
加载字段定义
;若模块配置了
`filterView`
,同时调用
`GET /subject/:name/view/:viewName`
读取视图列、列分组和行分组配置;随后
调用
`POST /record/query`
与
`POST /record/count`
分页加载记录。
2.
关键词可搜索标题、廉洁风险点、主要防控措施或措施描述。
3.
分类、概率、危害程度、审批状态和类别筛选只在对应字段存在
`settings.options`
时展示,筛选值不在前端硬编码。
4.
桌面端使用 Catalyst
`Table`
,
移动端使用可点击的
记录列表;两端都支持当前页勾选。
5.
点击记录打开 Headless UI
/Catalyst 详情弹框。
具备编辑权限的页面可切换到编辑态。
4.
桌面端使用 Catalyst
`Table`
,
表头优先按视图
`settings.columns`
生成,并兼容
`settings.groups`
的两层分组表头;若视图配置
`settings.groupCols`
,当前页记录按配置字段分块展示。移动端使用同一列配置生成可点击
记录列表;两端都支持当前页勾选。
5.
点击记录打开 Headless UI
右侧详情抽屉。详情字段优先沿用当前视图列顺序和分组配置,
具备编辑权限的页面可切换到编辑态。
6.
新增和编辑表单根据 Subject 字段类型动态生成;业务字段统一写入
`metadata`
。
7.
删除前必须二次确认,默认执行软删除。接口异常保留当前页面状态并显示可恢复的错误提示。
...
...
src/pages/integrity_risk/integrityRiskService.js
View file @
6c54c130
...
...
@@ -9,6 +9,7 @@ import {
importRecords
,
loadRecord
,
loadSubject
,
getSubjectViewDetail
,
loadUser
,
queryRecords
,
queryUsers
,
...
...
@@ -54,6 +55,7 @@ export const WORKSPACE_CONFIG = {
canArchive
:
true
,
canCopy
:
true
,
exportKind
:
'process'
,
filterView
:
[
'view_RAGm05'
],
},
display
:
{
subject
:
RISK_SUBJECTS
.
display
,
...
...
@@ -152,6 +154,45 @@ const EXPORT_COLUMNS = [
[
'类别'
,
'type'
],
];
const
RISK_TABLE_GROUPS
=
[
{
id
:
'risk_identification'
,
name
:
'风险识别'
,
children
:
[
'original_index'
,
'classification'
,
'involving_major_aspects_business_transaction_processes'
,
'involving_major_aspects_business_transaction_processes_add'
,
'integrity_risk_points'
,
'involving_positions_or_personnel'
,
'involving_positions_or_personnel_add'
,
],
},
{
id
:
'risk_assessment'
,
name
:
'风险评估'
,
children
:
[
'probability'
,
'hazard_degree'
,
'__risk_level'
],
},
{
id
:
'prevention_measures'
,
name
:
'防控措施'
,
children
:
[
'description_of_main_prevention_and_control_measures'
,
'file_name_and_document_number'
,
'file_name_other'
,
'subsidiary_system'
,
'subsidiary_system_add'
,
'business_unit'
,
'business_unit_add'
,
],
},
{
id
:
'approval_flow'
,
name
:
'填报审批'
,
children
:
[
'filled_by'
,
'approved_by'
,
'approval_status'
,
'final_approval_status'
,
'type'
],
},
];
function
extractRecords
(
response
)
{
if
(
Array
.
isArray
(
response
))
return
response
;
if
(
Array
.
isArray
(
response
?.
records
))
return
response
.
records
;
...
...
@@ -172,6 +213,70 @@ function extractCount(response) {
return
values
.
find
((
value
)
=>
Number
.
isFinite
(
value
))
||
0
;
}
function
fieldsOf
(
subject
)
{
if
(
Array
.
isArray
(
subject
?.
fields
))
return
subject
.
fields
;
if
(
subject
?.
fields
&&
typeof
subject
.
fields
===
'object'
)
return
Object
.
values
(
subject
.
fields
);
return
[];
}
function
stripMetadataPrefix
(
fieldPath
=
''
)
{
const
normalized
=
String
(
fieldPath
||
''
);
if
(
normalized
.
startsWith
(
'metadata.'
))
return
normalized
.
replace
(
'metadata.'
,
''
);
return
normalized
.
split
(
'.'
).
pop
()
||
normalized
;
}
function
getNestedValue
(
source
,
path
)
{
if
(
!
source
||
!
path
)
return
undefined
;
return
String
(
path
)
.
split
(
'.'
)
.
reduce
((
value
,
key
)
=>
(
value
===
undefined
||
value
===
null
?
undefined
:
value
[
key
]),
source
);
}
function
getFieldDefinition
(
subject
,
columnField
)
{
const
name
=
stripMetadataPrefix
(
columnField
);
return
fieldsOf
(
subject
).
find
((
field
)
=>
getFieldName
(
field
)
===
name
)
||
null
;
}
function
isHiddenField
(
field
=
{})
{
const
type
=
getFieldType
(
field
);
return
Boolean
(
field
.
hidden
||
field
?.
settings
?.
hidden
||
[
'formula'
,
'computed'
].
includes
(
type
));
}
function
normalizeColumnConfig
(
column
,
subject
)
{
const
field
=
typeof
column
===
'string'
?
column
:
column
?.
field
||
column
?.
name
;
if
(
!
field
)
return
null
;
const
name
=
stripMetadataPrefix
(
field
);
const
fieldDef
=
getFieldDefinition
(
subject
,
field
);
return
{
field
,
name
,
width
:
typeof
column
===
'object'
?
column
.
width
:
undefined
,
pinned
:
typeof
column
===
'object'
?
column
.
pinned
:
undefined
,
fieldDef
:
fieldDef
||
{
name
,
label
:
RISK_FIELD_LABELS
[
name
],
type
:
'text'
},
label
:
typeof
column
===
'object'
?
column
.
label
||
column
.
headerName
||
column
.
title
||
getColumnLabel
(
name
,
subject
)
:
getColumnLabel
(
name
,
subject
),
};
}
function
workspaceConfigBySubject
(
subjectName
)
{
return
Object
.
values
(
WORKSPACE_CONFIG
).
find
((
item
)
=>
item
.
subject
===
subjectName
)
||
{};
}
async
function
loadConfiguredView
(
subjectName
)
{
const
config
=
workspaceConfigBySubject
(
subjectName
);
const
viewNames
=
config
.
filterView
||
[];
for
(
const
viewName
of
viewNames
)
{
try
{
const
view
=
await
getSubjectViewDetail
(
subjectName
,
viewName
);
if
(
view
)
return
view
;
}
catch
{
// View configuration is optional. The table falls back to Subject fields.
}
}
return
null
;
}
export
function
getFieldName
(
field
=
{})
{
return
field
.
name
||
field
.
alias
||
field
.
key
||
''
;
}
...
...
@@ -212,8 +317,12 @@ export function isFieldMultiple(field = {}) {
}
export
function
getColumnLabel
(
name
,
subject
)
{
if
(
RISK_FIELD_LABELS
[
name
])
return
RISK_FIELD_LABELS
[
name
];
return
getFieldLabel
(
subject
?.
fields
?.
find
((
field
)
=>
getFieldName
(
field
)
===
name
));
const
fieldName
=
stripMetadataPrefix
(
typeof
name
===
'object'
?
name
.
field
||
name
.
name
:
name
);
if
(
typeof
name
===
'object'
&&
(
name
.
label
||
name
.
headerName
||
name
.
title
))
{
return
name
.
label
||
name
.
headerName
||
name
.
title
;
}
if
(
RISK_FIELD_LABELS
[
fieldName
])
return
RISK_FIELD_LABELS
[
fieldName
];
return
getFieldLabel
(
fieldsOf
(
subject
).
find
((
field
)
=>
getFieldName
(
field
)
===
fieldName
));
}
export
function
calculateRiskLevel
(
probability
,
hazardDegree
)
{
...
...
@@ -231,7 +340,13 @@ function unwrapReference(value) {
return
value
??
''
;
}
export
function
formatCellValue
(
record
,
name
)
{
export
function
getColumnFieldName
(
column
)
{
return
stripMetadataPrefix
(
typeof
column
===
'object'
?
column
.
field
||
column
.
name
:
column
);
}
export
function
formatCellValue
(
record
,
column
)
{
const
fieldPath
=
typeof
column
===
'object'
?
column
.
field
||
column
.
name
:
column
;
const
name
=
stripMetadataPrefix
(
fieldPath
);
if
(
name
===
'title'
)
return
record
?.
title
||
'-'
;
if
(
name
===
'created_at'
||
name
===
'updated_at'
)
{
const
value
=
record
?.[
name
];
...
...
@@ -249,7 +364,9 @@ export function formatCellValue(record, name) {
].
reduce
((
total
,
value
)
=>
total
+
(
Array
.
isArray
(
value
)
?
value
.
length
:
value
?
1
:
0
),
0
);
}
const
value
=
record
?.
metadata
?.[
name
];
const
value
=
String
(
fieldPath
||
''
).
includes
(
'.'
)
?
getNestedValue
(
record
,
fieldPath
)
:
record
?.[
name
]
??
record
?.
metadata
?.[
name
];
if
(
Array
.
isArray
(
value
))
{
return
value
.
map
(
unwrapReference
).
filter
(
Boolean
).
join
(
'、'
)
||
'-'
;
}
...
...
@@ -261,6 +378,108 @@ export function formatCellValue(record, name) {
return
value
===
0
?
'0'
:
value
||
'-'
;
}
export
function
getConfiguredColumns
({
subject
,
view
,
fallbackColumns
=
[],
sampleRecord
,
hideRecordTitle
=
false
})
{
const
viewColumns
=
view
?.
settings
?.
columns
||
view
?.
columns
||
[];
const
hasViewColumns
=
Array
.
isArray
(
viewColumns
)
&&
viewColumns
.
length
>
0
;
const
configuredNames
=
new
Set
();
const
columns
=
[];
if
(
hasViewColumns
)
{
viewColumns
.
forEach
((
column
)
=>
{
const
field
=
column
?.
field
||
column
?.
name
;
if
(
!
field
)
return
;
const
name
=
stripMetadataPrefix
(
field
);
configuredNames
.
add
(
name
);
if
(
column
.
hide
||
(
name
===
'title'
&&
hideRecordTitle
))
return
;
const
normalized
=
normalizeColumnConfig
(
column
,
subject
);
if
(
normalized
)
columns
.
push
(
normalized
);
});
}
const
sourceFields
=
fieldsOf
(
subject
).
filter
((
field
)
=>
{
const
name
=
getFieldName
(
field
);
return
name
&&
!
configuredNames
.
has
(
name
)
&&
!
isHiddenField
(
field
);
});
if
(
hasViewColumns
)
{
sourceFields
.
forEach
((
field
)
=>
{
const
name
=
getFieldName
(
field
);
const
fieldPath
=
sampleRecord
?.
metadata
&&
Object
.
prototype
.
hasOwnProperty
.
call
(
sampleRecord
.
metadata
,
name
)
?
`metadata.
${
name
}
`
:
name
;
columns
.
push
({
field
:
fieldPath
,
name
,
label
:
getFieldLabel
(
field
),
fieldDef
:
field
,
});
});
}
if
(
!
hasViewColumns
&&
sourceFields
.
length
>
0
)
{
if
(
!
hideRecordTitle
&&
(
sampleRecord
?.
title
||
fallbackColumns
.
includes
(
'title'
)))
{
columns
.
push
(
normalizeColumnConfig
(
'title'
,
subject
));
}
sourceFields
.
forEach
((
field
)
=>
{
const
name
=
getFieldName
(
field
);
columns
.
push
({
field
:
`metadata.
${
name
}
`
,
name
,
label
:
getFieldLabel
(
field
),
fieldDef
:
field
,
});
});
}
fallbackColumns
.
forEach
((
field
)
=>
{
const
name
=
stripMetadataPrefix
(
field
);
if
(
!
columns
.
some
((
column
)
=>
column
.
name
===
name
)
&&
(
name
.
startsWith
(
'__'
)
||
columns
.
length
===
0
))
{
const
normalized
=
normalizeColumnConfig
(
field
,
subject
);
if
(
normalized
)
columns
.
push
(
normalized
);
}
});
return
columns
.
filter
(
Boolean
);
}
export
function
getTableHeaderGroups
(
columns
,
view
,
subjectName
)
{
const
configuredGroups
=
view
?.
settings
?.
groups
||
view
?.
groups
||
[];
const
groups
=
Array
.
isArray
(
configuredGroups
)
&&
configuredGroups
.
length
>
0
?
configuredGroups
:
[
RISK_SUBJECTS
.
process
,
RISK_SUBJECTS
.
display
].
includes
(
subjectName
)
?
RISK_TABLE_GROUPS
:
[];
if
(
!
groups
.
length
)
return
[];
const
groupByField
=
new
Map
();
groups
.
forEach
((
group
)
=>
{
const
id
=
group
.
id
||
group
.
name
;
(
group
.
children
||
[]).
forEach
((
child
)
=>
{
groupByField
.
set
(
stripMetadataPrefix
(
child
),
{
id
,
name
:
group
.
name
||
group
.
label
||
id
});
});
});
const
renderedGroups
=
new
Set
();
const
renderedColumns
=
new
Set
();
const
headerGroups
=
[];
columns
.
forEach
((
column
)
=>
{
if
(
renderedColumns
.
has
(
column
.
name
))
return
;
const
group
=
groupByField
.
get
(
column
.
name
);
if
(
!
group
||
renderedGroups
.
has
(
group
.
id
))
{
headerGroups
.
push
({
type
:
'column'
,
column
});
renderedColumns
.
add
(
column
.
name
);
return
;
}
const
groupedColumns
=
columns
.
filter
((
item
)
=>
groupByField
.
get
(
item
.
name
)?.
id
===
group
.
id
);
groupedColumns
.
forEach
((
item
)
=>
renderedColumns
.
add
(
item
.
name
));
renderedGroups
.
add
(
group
.
id
);
headerGroups
.
push
({
type
:
'group'
,
id
:
group
.
id
,
label
:
group
.
name
,
columns
:
groupedColumns
});
});
return
headerGroups
;
}
function
buildRecordFilter
(
filters
=
{})
{
const
filter
=
{};
const
keyword
=
String
(
filters
.
keyword
||
''
).
trim
();
...
...
@@ -291,8 +510,9 @@ export async function loadWorkspaceData(subjectName, filters = {}) {
const
filter
=
buildRecordFilter
(
filters
);
const
page
=
Number
(
filters
.
page
||
0
);
const
pageSize
=
Number
(
filters
.
pageSize
||
20
);
const
[
subject
,
response
,
countResponse
]
=
await
Promise
.
all
([
const
[
subject
,
view
,
response
,
countResponse
]
=
await
Promise
.
all
([
loadSubject
(
subjectName
),
loadConfiguredView
(
subjectName
),
queryRecords
({
subject
:
subjectName
,
page
,
...
...
@@ -308,6 +528,7 @@ export async function loadWorkspaceData(subjectName, filters = {}) {
return
{
subject
,
view
,
records
:
extractRecords
(
response
),
total
:
extractCount
(
countResponse
),
};
...
...
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