Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
R
risk_manager_new
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_manager_new
Commits
22d96165
Commit
22d96165
authored
Oct 07, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: configure table field count and label display
parent
19c9905e
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
473 additions
and
24 deletions
+473
-24
SubjectRecordWorkbench.jsx
src/components/SubjectRecordWorkbench.jsx
+9
-2
TableFieldValue.jsx
src/components/TableFieldValue.jsx
+38
-0
index.jsx
src/components/record-workbench/index.jsx
+16
-0
archiveWorkbenchService.js
.../problem_rectification/archive/archiveWorkbenchService.js
+8
-5
index.jsx
src/pages/problem_rectification/archive/index.jsx
+15
-2
index.jsx
src/pages/problem_rectification/list/index.jsx
+15
-2
problem-rectification.business-flow.md
...blem_rectification/problem-rectification.business-flow.md
+59
-0
problemRectificationListService.js
.../problem_rectification/problemRectificationListService.js
+10
-7
SubjectWorkbenchService.js
src/pages/project_manager_xy/SubjectWorkbenchService.js
+10
-5
risk-management.business-flow.md
src/pages/risk_management/risk-management.business-flow.md
+13
-0
record-api.md
src/services/record-api.md
+8
-0
tableFieldDisplayService.js
src/services/tableFieldDisplayService.js
+17
-0
subjectView.js
src/utils/subjectView.js
+5
-1
tableFieldDisplay.js
src/utils/tableFieldDisplay.js
+80
-0
tableFieldDisplay.test.mjs
tests/tableFieldDisplay.test.mjs
+170
-0
No files found.
src/components/SubjectRecordWorkbench.jsx
View file @
22d96165
...
@@ -145,6 +145,8 @@ export default function SubjectRecordWorkbench({
...
@@ -145,6 +145,8 @@ export default function SubjectRecordWorkbench({
const
[
records
,
setRecords
]
=
useState
([]);
const
[
records
,
setRecords
]
=
useState
([]);
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
views
,
setViews
]
=
useState
([]);
const
[
views
,
setViews
]
=
useState
([]);
const
[
tableDisplayConfig
,
setTableDisplayConfig
]
=
useState
({});
const
[
tableDisplayConfigError
,
setTableDisplayConfigError
]
=
useState
(
''
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
success
,
setSuccess
]
=
useState
(
''
);
const
[
success
,
setSuccess
]
=
useState
(
''
);
...
@@ -183,12 +185,16 @@ export default function SubjectRecordWorkbench({
...
@@ -183,12 +185,16 @@ export default function SubjectRecordWorkbench({
setTotalRecords
(
resources
.
totalCount
||
0
);
setTotalRecords
(
resources
.
totalCount
||
0
);
setSubject
(
resources
.
subject
||
null
);
setSubject
(
resources
.
subject
||
null
);
setViews
(
resources
.
views
||
[]);
setViews
(
resources
.
views
||
[]);
setTableDisplayConfig
(
resources
.
displayConfig
||
{});
setTableDisplayConfigError
(
resources
.
displayConfigError
||
''
);
}
catch
(
requestError
)
{
}
catch
(
requestError
)
{
if
(
requestId
!==
requestSequence
.
current
)
return
;
if
(
requestId
!==
requestSequence
.
current
)
return
;
setRecords
([]);
setRecords
([]);
setTotalRecords
(
0
);
setTotalRecords
(
0
);
setSubject
(
null
);
setSubject
(
null
);
setViews
([]);
setViews
([]);
setTableDisplayConfig
({});
setTableDisplayConfigError
(
''
);
setError
(
requestError
?.
message
||
'列表加载失败,请稍后重试。'
);
setError
(
requestError
?.
message
||
'列表加载失败,请稍后重试。'
);
}
finally
{
}
finally
{
if
(
requestId
===
requestSequence
.
current
)
{
if
(
requestId
===
requestSequence
.
current
)
{
...
@@ -234,8 +240,8 @@ export default function SubjectRecordWorkbench({
...
@@ -234,8 +240,8 @@ export default function SubjectRecordWorkbench({
);
);
const
hasAccessibleView
=
Boolean
(
activeView
);
const
hasAccessibleView
=
Boolean
(
activeView
);
const
configuredView
=
useMemo
(
const
configuredView
=
useMemo
(
()
=>
buildConfiguredWorkbenchView
(
records
,
{},
activeView
,
subject
),
()
=>
buildConfiguredWorkbenchView
(
records
,
{},
activeView
,
subject
,
tableDisplayConfig
),
[
records
,
activeView
,
subject
],
[
records
,
activeView
,
subject
,
tableDisplayConfig
],
);
);
const
filteredRecords
=
configuredView
.
filteredRecords
||
[];
const
filteredRecords
=
configuredView
.
filteredRecords
||
[];
const
visibleColumns
=
useMemo
(()
=>
{
const
visibleColumns
=
useMemo
(()
=>
{
...
@@ -465,6 +471,7 @@ export default function SubjectRecordWorkbench({
...
@@ -465,6 +471,7 @@ export default function SubjectRecordWorkbench({
),
),
}
:
null
}
}
:
null
}
states=
{
{
states=
{
{
warning
:
tableDisplayConfigError
,
loading
,
loading
,
error
,
error
,
success
,
success
,
...
...
src/components/TableFieldValue.jsx
0 → 100644
View file @
22d96165
import
{
Button
,
Chip
,
Tooltip
}
from
'@heroui/react'
;
import
{
DataTablePrimaryAction
,
DataTableText
}
from
'./DataTable'
;
import
{
getTableFieldDisplay
}
from
'@/utils/tableFieldDisplay'
;
export
default
function
TableFieldValue
({
value
,
column
,
onPress
})
{
const
display
=
getTableFieldDisplay
(
value
,
column
);
if
(
display
.
showCount
)
{
const
count
=
(
<
Chip
size=
"sm"
aria
-
label=
{
`${column.label || '关联条目'}:${display.count} 项`
}
>
{
display
.
count
}
</
Chip
>
);
return
display
.
labels
.
length
>
0
?
(
<
Tooltip
>
{
onPress
?
(
<
Button
variant=
"ghost"
size=
"sm"
onPress=
{
onPress
}
aria
-
label=
{
`${column.label || '关联条目'}:${display.count} 项,查看详情`
}
>
{
count
}
</
Button
>
)
:
(
<
Tooltip
.
Trigger
aria
-
label=
{
`${column.label || '关联条目'}:${display.count} 项,查看标题`
}
>
{
count
}
</
Tooltip
.
Trigger
>
)
}
<
Tooltip
.
Content
>
<
div
className=
"max-h-64 max-w-80 overflow-auto whitespace-pre-wrap break-words"
>
{
display
.
labels
.
join
(
'、'
)
}
</
div
>
</
Tooltip
.
Content
>
</
Tooltip
>
)
:
onPress
?
(
<
Button
variant=
"ghost"
size=
"sm"
onPress=
{
onPress
}
aria
-
label=
{
`${column.label || '关联条目'}:${display.count} 项,查看详情`
}
>
{
count
}
</
Button
>
)
:
count
;
}
if
(
onPress
)
return
<
DataTablePrimaryAction
text=
{
display
.
text
||
'-'
}
onPress=
{
onPress
}
/>;
return
<
DataTableText
text=
{
display
.
text
||
'-'
}
maxLines=
{
2
}
showTooltip
/>;
}
src/components/record-workbench/index.jsx
View file @
22d96165
...
@@ -26,6 +26,8 @@ import FilterSelect from '@/components/FilterSelect';
...
@@ -26,6 +26,8 @@ import FilterSelect from '@/components/FilterSelect';
import
FormDatePicker
from
'@/components/FormDatePicker'
;
import
FormDatePicker
from
'@/components/FormDatePicker'
;
import
ReferenceSearchSelect
from
'@/pages/RecordDetailDrawer/components/ReferenceSearchSelect'
;
import
ReferenceSearchSelect
from
'@/pages/RecordDetailDrawer/components/ReferenceSearchSelect'
;
import
UserSearchSelect
from
'@/components/UserSearchSelect'
;
import
UserSearchSelect
from
'@/components/UserSearchSelect'
;
import
TableFieldValue
from
'@/components/TableFieldValue'
;
import
{
getTableFieldDisplayConfig
}
from
'@/utils/tableFieldDisplay'
;
import
{
formatFieldValue
,
getFieldValue
,
normalizeText
}
from
'@/utils/subjectView'
;
import
{
formatFieldValue
,
getFieldValue
,
normalizeText
}
from
'@/utils/subjectView'
;
import
{
import
{
Alert
,
Alert
,
...
@@ -305,6 +307,9 @@ function getFilterPlaceholder(field) {
...
@@ -305,6 +307,9 @@ function getFilterPlaceholder(field) {
function
defaultRenderCell
(
record
,
column
)
{
function
defaultRenderCell
(
record
,
column
)
{
const
value
=
getRecordFieldValue
(
record
,
column
.
field
);
const
value
=
getRecordFieldValue
(
record
,
column
.
field
);
if
(
getTableFieldDisplayConfig
(
column
).
isConfigured
)
{
return
<
TableFieldValue
value=
{
value
}
column=
{
column
}
/>;
}
const
text
=
formatFieldValue
(
value
,
column
.
fieldDef
)
||
'-'
;
const
text
=
formatFieldValue
(
value
,
column
.
fieldDef
)
||
'-'
;
if
(
column
.
fieldDef
?.
type
===
'user'
)
{
if
(
column
.
fieldDef
?.
type
===
'user'
)
{
...
@@ -894,6 +899,11 @@ export default function RecordWorkbench({
...
@@ -894,6 +899,11 @@ export default function RecordWorkbench({
}
}
}
}
if (columnIndex === 0 && detailPanel && getTableFieldDisplayConfig(column).isConfigured) {
return <TableFieldValue value={getRecordFieldValue(row.record, column.field)} column={column}
onPress={() => row.rowContext.openDetail(row.record)} />;
}
if (columnIndex === 0 && detailPanel) {
if (columnIndex === 0 && detailPanel) {
const value = getRecordFieldValue(row.record, column.field);
const value = getRecordFieldValue(row.record, column.field);
const text = formatFieldValue(value, column.fieldDef) || '-';
const text = formatFieldValue(value, column.fieldDef) || '-';
...
@@ -1192,6 +1202,12 @@ export default function RecordWorkbench({
...
@@ -1192,6 +1202,12 @@ export default function RecordWorkbench({
</Alert>
</Alert>
) : null}
) : null}
{states?.warning ? (
<Alert status="warning">
<Alert.Content><Alert.Description>{states.warning}</Alert.Description></Alert.Content>
</Alert>
) : null}
<div ref={tableScrollRef} className={tableContentClass}>
<div ref={tableScrollRef} className={tableContentClass}>
<DataTable
<DataTable
ariaLabel={title || '业务数据列表'}
ariaLabel={title || '业务数据列表'}
...
...
src/pages/problem_rectification/archive/archiveWorkbenchService.js
View file @
22d96165
import
{
loadApplicationTableDisplayConfig
}
from
'@/services/tableFieldDisplayService'
;
import
{
countRecords
,
getSubjectView
,
loadRecord
,
loadSubject
,
queryRecords
}
from
'@/services/DataService'
;
import
{
countRecords
,
getSubjectView
,
loadRecord
,
loadSubject
,
queryRecords
}
from
'@/services/DataService'
;
import
{
import
{
buildViewColumns
,
buildViewColumns
,
...
@@ -30,9 +31,10 @@ export function normalizeArchiveBatchRecord(record) {
...
@@ -30,9 +31,10 @@ export function normalizeArchiveBatchRecord(record) {
}
}
export
async
function
loadArchiveWorkbenchResources
()
{
export
async
function
loadArchiveWorkbenchResources
()
{
const
[
archiveSubject
,
archiveViews
]
=
await
Promise
.
all
([
const
[
archiveSubject
,
archiveViews
,
tableDisplayConfig
]
=
await
Promise
.
all
([
loadSubject
(
ISSUE_RECTIFICATION_ARCHIVE_SUBJECT
),
loadSubject
(
ISSUE_RECTIFICATION_ARCHIVE_SUBJECT
),
getSubjectView
(
ISSUE_RECTIFICATION_ARCHIVE_SUBJECT
),
getSubjectView
(
ISSUE_RECTIFICATION_ARCHIVE_SUBJECT
),
loadApplicationTableDisplayConfig
(
ISSUE_RECTIFICATION_ARCHIVE_SUBJECT
),
]);
]);
const
accessibleViews
=
buildArchiveGridViews
(
archiveViews
||
[]);
const
accessibleViews
=
buildArchiveGridViews
(
archiveViews
||
[]);
const
archiveRecordsResult
=
accessibleViews
.
length
>
0
const
archiveRecordsResult
=
accessibleViews
.
length
>
0
...
@@ -50,6 +52,7 @@ export async function loadArchiveWorkbenchResources() {
...
@@ -50,6 +52,7 @@ export async function loadArchiveWorkbenchResources() {
records
,
records
,
subject
:
archiveSubject
,
subject
:
archiveSubject
,
views
:
archiveViews
||
[],
views
:
archiveViews
||
[],
...
tableDisplayConfig
,
};
};
}
}
...
@@ -70,11 +73,11 @@ export function buildArchiveGridViews(views = []) {
...
@@ -70,11 +73,11 @@ export function buildArchiveGridViews(views = []) {
return
isViewAccessible
(
fallbackView
)
?
[
fallbackView
]
:
[];
return
isViewAccessible
(
fallbackView
)
?
[
fallbackView
]
:
[];
}
}
export
function
buildArchiveColumns
(
view
,
subject
)
{
export
function
buildArchiveColumns
(
view
,
subject
,
displayConfig
=
{}
)
{
return
buildViewColumns
(
view
,
subject
);
return
buildViewColumns
(
view
,
subject
,
{
displayConfig
}
);
}
}
export
function
buildArchiveConfiguredView
(
records
,
filters
,
gridView
,
subject
)
{
export
function
buildArchiveConfiguredView
(
records
,
filters
,
gridView
,
subject
,
displayConfig
=
{}
)
{
if
(
!
gridView
)
{
if
(
!
gridView
)
{
return
{
return
{
filteredRecords
:
[],
filteredRecords
:
[],
...
@@ -83,7 +86,7 @@ export function buildArchiveConfiguredView(records, filters, gridView, subject)
...
@@ -83,7 +86,7 @@ export function buildArchiveConfiguredView(records, filters, gridView, subject)
}
}
const
keyword
=
normalizeText
(
filters
?.
keyword
).
toLowerCase
();
const
keyword
=
normalizeText
(
filters
?.
keyword
).
toLowerCase
();
const
columns
=
buildArchiveColumns
(
gridView
,
subject
);
const
columns
=
buildArchiveColumns
(
gridView
,
subject
,
displayConfig
);
const
searchColumns
=
buildViewSearchColumns
(
gridView
,
subject
);
const
searchColumns
=
buildViewSearchColumns
(
gridView
,
subject
);
const
filteredRecords
=
records
.
filter
((
record
)
=>
{
const
filteredRecords
=
records
.
filter
((
record
)
=>
{
const
rawRecord
=
record
.
raw
||
record
;
const
rawRecord
=
record
.
raw
||
record
;
...
...
src/pages/problem_rectification/archive/index.jsx
View file @
22d96165
import
TableFieldValue
from
'@/components/TableFieldValue'
;
import
{
getTableFieldDisplayConfig
}
from
'@/utils/tableFieldDisplay'
;
import
{
getFieldValue
}
from
'@/utils/subjectView'
;
import
RecordWorkbench
from
'@/components/record-workbench'
;
import
RecordWorkbench
from
'@/components/record-workbench'
;
import
RecordDetailDrawer
from
'@/pages/RecordDetailDrawer'
;
import
RecordDetailDrawer
from
'@/pages/RecordDetailDrawer'
;
import
{
useCallback
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useCallback
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
...
@@ -16,6 +19,8 @@ export default function ProblemRectificationArchivePage() {
...
@@ -16,6 +19,8 @@ export default function ProblemRectificationArchivePage() {
const
[
records
,
setRecords
]
=
useState
([]);
const
[
records
,
setRecords
]
=
useState
([]);
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
subject
,
setSubject
]
=
useState
(
null
);
const
[
views
,
setViews
]
=
useState
([]);
const
[
views
,
setViews
]
=
useState
([]);
const
[
tableDisplayConfig
,
setTableDisplayConfig
]
=
useState
({});
const
[
tableDisplayConfigError
,
setTableDisplayConfigError
]
=
useState
(
''
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
const
[
filters
,
setFilters
]
=
useState
({
const
[
filters
,
setFilters
]
=
useState
({
...
@@ -35,10 +40,14 @@ export default function ProblemRectificationArchivePage() {
...
@@ -35,10 +40,14 @@ export default function ProblemRectificationArchivePage() {
setRecords
(
resources
.
records
||
[]);
setRecords
(
resources
.
records
||
[]);
setSubject
(
resources
.
subject
||
null
);
setSubject
(
resources
.
subject
||
null
);
setViews
(
resources
.
views
||
[]);
setViews
(
resources
.
views
||
[]);
setTableDisplayConfig
(
resources
.
displayConfig
||
{});
setTableDisplayConfigError
(
resources
.
displayConfigError
||
''
);
}
catch
(
requestError
)
{
}
catch
(
requestError
)
{
setRecords
([]);
setRecords
([]);
setSubject
(
null
);
setSubject
(
null
);
setViews
([]);
setViews
([]);
setTableDisplayConfig
({});
setTableDisplayConfigError
(
''
);
setError
(
requestError
?.
message
||
'归档批次列表加载失败,请稍后重试。'
);
setError
(
requestError
?.
message
||
'归档批次列表加载失败,请稍后重试。'
);
}
finally
{
}
finally
{
setLoading
(
false
);
setLoading
(
false
);
...
@@ -68,8 +77,8 @@ export default function ProblemRectificationArchivePage() {
...
@@ -68,8 +77,8 @@ export default function ProblemRectificationArchivePage() {
);
);
const
configuredView
=
useMemo
(
const
configuredView
=
useMemo
(
()
=>
buildArchiveConfiguredView
(
records
,
filters
,
activeGridView
,
subject
),
()
=>
buildArchiveConfiguredView
(
records
,
filters
,
activeGridView
,
subject
,
tableDisplayConfig
),
[
records
,
filters
,
activeGridView
,
subject
],
[
records
,
filters
,
activeGridView
,
subject
,
tableDisplayConfig
],
);
);
const
filteredRecords
=
configuredView
.
filteredRecords
||
[];
const
filteredRecords
=
configuredView
.
filteredRecords
||
[];
...
@@ -163,6 +172,7 @@ export default function ProblemRectificationArchivePage() {
...
@@ -163,6 +172,7 @@ export default function ProblemRectificationArchivePage() {
),
),
}
}
}
}
states=
{
{
states=
{
{
warning
:
tableDisplayConfigError
,
loading
,
loading
,
error
,
error
,
loadingTitle
:
'正在加载归档批次'
,
loadingTitle
:
'正在加载归档批次'
,
...
@@ -172,6 +182,9 @@ export default function ProblemRectificationArchivePage() {
...
@@ -172,6 +182,9 @@ export default function ProblemRectificationArchivePage() {
}
}
}
}
renderCell=
{
({
record
,
column
})
=>
{
renderCell=
{
({
record
,
column
})
=>
{
if
(
column
.
field
===
'metadata.reference_records'
)
{
if
(
column
.
field
===
'metadata.reference_records'
)
{
if
(
getTableFieldDisplayConfig
(
column
).
isConfigured
)
{
return
<
TableFieldValue
column=
{
column
}
value=
{
getFieldValue
(
record
.
raw
||
record
,
column
.
field
)
}
/>;
}
return
(
return
(
<
span
className=
"tabular-nums"
>
<
span
className=
"tabular-nums"
>
{
record
.
archiveCount
||
0
}
条明细
{
record
.
archiveCount
||
0
}
条明细
...
...
src/pages/problem_rectification/list/index.jsx
View file @
22d96165
...
@@ -16,6 +16,8 @@ import {
...
@@ -16,6 +16,8 @@ import {
}
from
'@/components/TableActionButton'
;
}
from
'@/components/TableActionButton'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
RecordWorkbench
from
'@/components/record-workbench'
;
import
RecordWorkbench
from
'@/components/record-workbench'
;
import
TableFieldValue
from
'@/components/TableFieldValue'
;
import
{
getTableFieldDisplayConfig
}
from
'@/utils/tableFieldDisplay'
;
import
RecordDetailDrawer
from
'@/pages/RecordDetailDrawer'
;
import
RecordDetailDrawer
from
'@/pages/RecordDetailDrawer'
;
import
{
import
{
batchRemoveRecords
,
batchRemoveRecords
,
...
@@ -623,6 +625,10 @@ function FieldValuePreview({ column, record }) {
...
@@ -623,6 +625,10 @@ function FieldValuePreview({ column, record }) {
return <div className="font-medium">{record.title || '(无标题)'}</div>;
return <div className="font-medium">{record.title || '(无标题)'}</div>;
}
}
if (getTableFieldDisplayConfig(column).isConfigured) {
return <TableFieldValue value={value} column={column} />;
}
if (fieldName === 'rectification_progress') {
if (fieldName === 'rectification_progress') {
const text = formatFieldValue(value, column.fieldDef) || '未填写';
const text = formatFieldValue(value, column.fieldDef) || '未填写';
return <StatusPill text={text} tone={resolveProgressTone(text)} />;
return <StatusPill text={text} tone={resolveProgressTone(text)} />;
...
@@ -786,6 +792,8 @@ export default function ProblemRectificationListPage() {
...
@@ -786,6 +792,8 @@ export default function ProblemRectificationListPage() {
const [records, setRecords] = useState([]);
const [records, setRecords] = useState([]);
const [subject, setSubject] = useState(null);
const [subject, setSubject] = useState(null);
const [views, setViews] = useState([]);
const [views, setViews] = useState([]);
const [tableDisplayConfig, setTableDisplayConfig] = useState({});
const [tableDisplayConfigError, setTableDisplayConfigError] = useState('');
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useState(true);
const [recordsLoading, setRecordsLoading] = useState(false);
const [recordsLoading, setRecordsLoading] = useState(false);
const [error, setError] = useState('');
const [error, setError] = useState('');
...
@@ -837,10 +845,14 @@ export default function ProblemRectificationListPage() {
...
@@ -837,10 +845,14 @@ export default function ProblemRectificationListPage() {
const resources = await loadProblemRectificationListResources();
const resources = await loadProblemRectificationListResources();
setSubject(resources.subject || null);
setSubject(resources.subject || null);
setViews(resources.views || []);
setViews(resources.views || []);
setTableDisplayConfig(resources.displayConfig || {});
setTableDisplayConfigError(resources.displayConfigError || '');
} catch (requestError) {
} catch (requestError) {
setRecords([]);
setRecords([]);
setSubject(null);
setSubject(null);
setViews([]);
setViews([]);
setTableDisplayConfig({});
setTableDisplayConfigError('');
setError(requestError?.message || '问题整改列表加载失败,请稍后重试。');
setError(requestError?.message || '问题整改列表加载失败,请稍后重试。');
} finally {
} finally {
setLoading(false);
setLoading(false);
...
@@ -1058,8 +1070,8 @@ export default function ProblemRectificationListPage() {
...
@@ -1058,8 +1070,8 @@ export default function ProblemRectificationListPage() {
}, [reloadList]);
}, [reloadList]);
const configuredView = useMemo(
const configuredView = useMemo(
() => buildProblemRectificationConfiguredListView(records, {}, activeGridView, subject),
() => buildProblemRectificationConfiguredListView(records, {}, activeGridView, subject
, tableDisplayConfig
),
[records, activeGridView, subject],
[records, activeGridView, subject
, tableDisplayConfig
],
);
);
const dynamicFilterSchema = useMemo(
const dynamicFilterSchema = useMemo(
() =>
() =>
...
@@ -1947,6 +1959,7 @@ export default function ProblemRectificationListPage() {
...
@@ -1947,6 +1959,7 @@ export default function ProblemRectificationListPage() {
states={{
states={{
loading: loading || recordsLoading,
loading: loading || recordsLoading,
error,
error,
warning: tableDisplayConfigError,
success: '',
success: '',
loadingTitle: '正在加载问题整改列表',
loadingTitle: '正在加载问题整改列表',
loadingDescription: '正在读取对象结构、视图配置和记录数据,请稍候。',
loadingDescription: '正在读取对象结构、视图配置和记录数据,请稍候。',
...
...
src/pages/problem_rectification/problem-rectification.business-flow.md
View file @
22d96165
...
@@ -280,6 +280,65 @@
...
@@ -280,6 +280,65 @@
---
---
### 5.10 表格字段显示配置
入口为 streams-admin 的应用布局配置或 Subject 的 Grid 视图配置,作用于问题清单、归档批次和共享 RecordWorkbench 表格中的引用、选择、子对象及 form 字段。页面加载应用及视图配置后按列渲染,切换视图时使用该视图的配置。
-
应用默认值读取
`layouts.default.subjects.<subjectName>.gridRecordConfig.displayConfig`
,兼容 streams-admin 的
`displayConfig.maxTagCount`
。
-
视图默认值读取
`settings.displayConfig`
,同名属性覆盖应用默认值;
`settings.columns[]`
中的单列
`displayConfig`
再覆盖视图默认值,列上的
`displayMode`
、
`maxTagCount`
、
`titleField`
优先级最高。
-
`displayMode: "labels"`
显示已选选项或关联标题;选项文案来自字段
`settings.options`
,仅展示当前记录实际选中的值。
-
`displayMode: "count"`
显示有效条目数量,空值显示
`0`
,单值显示
`1`
。数量可通过鼠标悬停或键盘聚焦查看完整标题;空值不产生提示。计数只基于该字段已有数据,不额外查询关联记录总数。
-
`displayMode: "auto"`
在条目数达到
`maxTagCount`
时显示数量,否则显示标题或选项。只配置
`maxTagCount`
时自动启用此模式,判断使用
`>=`
,与 streams-admin 一致。显式
`auto`
未配置合法阈值时使用
`3`
;阈值为
`0`
时所有非空字段显示数量。
-
子对象标签优先使用
`titleField`
指定的子字段,例如
`responsible_department`
;也支持
`metadata`
下的子字段。未配置或值为空时使用已有
`title/name/_id`
,仍无标题时展示“第 N 条”,不猜测业务字段。form 对象为
`{form,data}`
时按
`data`
中的条目计数。
-
未配置显示方式或阈值时保留现有单元格行为,包括归档批次的“X 条明细”;非法显示方式或阈值不修改记录。普通文本、数字、日期、布尔、附件和人员字段不受这项集合显示配置影响。
-
应用配置读取失败时显示警告并使用视图配置;列表数据和详情入口仍可使用。重新加载页面会重试读取配置。配置不会改变筛选条件、排序、分页、审批状态、详情编辑、关联 ID 或 Record 的
`metadata`
。
应用配置示例(
`problem_rectification_new`
应用):
```
json
{
"layouts"
:
{
"default"
:
{
"subjects"
:
{
"problem_rectification"
:
{
"gridRecordConfig"
:
{
"displayConfig"
:
{
"maxTagCount"
:
3
}
}
},
"issue_rectification_archive"
:
{
"gridRecordConfig"
:
{
"displayConfig"
:
{
"displayMode"
:
"count"
}
}
}
}
}
}
}
```
Grid 视图配置示例(列举的是配置片段,修改时应保留完整原列列表):
```
json
{
"settings"
:
{
"displayConfig"
:
{
"displayMode"
:
"auto"
,
"maxTagCount"
:
3
},
"columns"
:
[
{
"field"
:
"metadata.responsible_department"
,
"displayMode"
:
"labels"
},
{
"field"
:
"metadata.department_rectification_items"
,
"displayMode"
:
"count"
,
"titleField"
:
"responsible_department"
},
{
"field"
:
"metadata.problem_rectification_delay_request_items"
,
"displayMode"
:
"labels"
,
"titleField"
:
"responsible_department"
}
]
}
}
```
## 6. 流程角色
## 6. 流程角色
-
发起人:通常为问题整改列表操作人,负责发起填报和最终审核;可编辑本次推送关联的父问题记录以及全部部门整改明细业务字段
-
发起人:通常为问题整改列表操作人,负责发起填报和最终审核;可编辑本次推送关联的父问题记录以及全部部门整改明细业务字段
...
...
src/pages/problem_rectification/problemRectificationListService.js
View file @
22d96165
...
@@ -11,11 +11,12 @@ import {
...
@@ -11,11 +11,12 @@ import {
DEPARTMENT_GROUPS
,
DEPARTMENT_GROUPS
,
}
from
'./problemRectificationDashboardService'
;
}
from
'./problemRectificationDashboardService'
;
import
{
calculateProblemSeverityLevel
}
from
'./problemSeverity'
;
import
{
calculateProblemSeverityLevel
}
from
'./problemSeverity'
;
import
{
loadApplicationTableDisplayConfig
}
from
'@/services/tableFieldDisplayService'
;
import
{
formatTableFieldValue
}
from
'@/utils/tableFieldDisplay'
;
import
{
import
{
buildViewColumns
,
buildViewColumns
,
checkViewCondition
,
checkViewCondition
,
emptyValueForField
,
emptyValueForField
,
formatFieldValue
,
getFieldNameFromPath
,
getFieldNameFromPath
,
getFieldValue
,
getFieldValue
,
getSelectOptions
,
getSelectOptions
,
...
@@ -1262,9 +1263,10 @@ export async function loadProblemRectificationListData({
...
@@ -1262,9 +1263,10 @@ export async function loadProblemRectificationListData({
}
}
export
async
function
loadProblemRectificationListResources
()
{
export
async
function
loadProblemRectificationListResources
()
{
const
[
subject
,
viewSummaries
]
=
await
Promise
.
all
([
const
[
subject
,
viewSummaries
,
tableDisplayConfig
]
=
await
Promise
.
all
([
loadSubject
(
PROBLEM_RECTIFICATION_SUBJECT
),
loadSubject
(
PROBLEM_RECTIFICATION_SUBJECT
),
getSubjectView
(
PROBLEM_RECTIFICATION_SUBJECT
),
getSubjectView
(
PROBLEM_RECTIFICATION_SUBJECT
),
loadApplicationTableDisplayConfig
(
PROBLEM_RECTIFICATION_SUBJECT
),
]);
]);
const
views
=
await
Promise
.
all
(
const
views
=
await
Promise
.
all
(
toArray
(
viewSummaries
).
map
(
async
(
view
)
=>
{
toArray
(
viewSummaries
).
map
(
async
(
view
)
=>
{
...
@@ -1291,6 +1293,7 @@ export async function loadProblemRectificationListResources() {
...
@@ -1291,6 +1293,7 @@ export async function loadProblemRectificationListResources() {
return
{
return
{
subject
,
subject
,
views
,
views
,
...
tableDisplayConfig
,
};
};
}
}
...
@@ -1326,8 +1329,8 @@ export function buildGridViews(views = []) {
...
@@ -1326,8 +1329,8 @@ export function buildGridViews(views = []) {
return
isViewAccessible
(
fallbackView
)
?
[
fallbackView
]
:
[];
return
isViewAccessible
(
fallbackView
)
?
[
fallbackView
]
:
[];
}
}
export
function
buildViewColumnsForList
(
view
,
subject
)
{
export
function
buildViewColumnsForList
(
view
,
subject
,
displayConfig
=
{}
)
{
return
buildViewColumns
(
view
,
subject
);
return
buildViewColumns
(
view
,
subject
,
{
displayConfig
}
);
}
}
export
function
getRecordCellText
(
record
,
column
)
{
export
function
getRecordCellText
(
record
,
column
)
{
...
@@ -1338,10 +1341,10 @@ export function getRecordCellText(record, column) {
...
@@ -1338,10 +1341,10 @@ export function getRecordCellText(record, column) {
return
formatDateText
(
value
)
||
'-'
;
return
formatDateText
(
value
)
||
'-'
;
}
}
return
format
FieldValue
(
value
,
column
.
fieldDef
)
||
'-'
;
return
format
TableFieldValue
(
value
,
column
)
||
'-'
;
}
}
export
function
buildProblemRectificationConfiguredListView
(
records
,
filters
,
gridView
,
subject
)
{
export
function
buildProblemRectificationConfiguredListView
(
records
,
filters
,
gridView
,
subject
,
displayConfig
=
{}
)
{
if
(
!
gridView
)
{
if
(
!
gridView
)
{
return
{
return
{
filteredRecords
:
[],
filteredRecords
:
[],
...
@@ -1357,7 +1360,7 @@ export function buildProblemRectificationConfiguredListView(records, filters, gr
...
@@ -1357,7 +1360,7 @@ export function buildProblemRectificationConfiguredListView(records, filters, gr
};
};
}
}
const
columns
=
buildViewColumnsForList
(
gridView
,
subject
);
const
columns
=
buildViewColumnsForList
(
gridView
,
subject
,
displayConfig
);
const
filteredRecords
=
records
.
filter
((
record
)
=>
{
const
filteredRecords
=
records
.
filter
((
record
)
=>
{
const
rawRecord
=
record
.
raw
||
record
;
const
rawRecord
=
record
.
raw
||
record
;
...
...
src/pages/project_manager_xy/SubjectWorkbenchService.js
View file @
22d96165
...
@@ -15,6 +15,8 @@ import {
...
@@ -15,6 +15,8 @@ import {
toArray
,
toArray
,
}
from
'@/utils/subjectView'
;
}
from
'@/utils/subjectView'
;
import
{
loadApplicationTableDisplayConfig
}
from
'@/services/tableFieldDisplayService'
;
const
QUERY_PAGE_SIZE
=
1000
;
const
QUERY_PAGE_SIZE
=
1000
;
const
DEFAULT_RECORD_SORT
=
'-updated_at -created_at'
;
const
DEFAULT_RECORD_SORT
=
'-updated_at -created_at'
;
const
SUBJECT_WORKBENCH_RESOURCE_CACHE
=
new
Map
();
const
SUBJECT_WORKBENCH_RESOURCE_CACHE
=
new
Map
();
...
@@ -317,8 +319,9 @@ function loadSubjectWorkbenchConfiguration(subjectName) {
...
@@ -317,8 +319,9 @@ function loadSubjectWorkbenchConfiguration(subjectName) {
configurationPromise
=
Promise
.
all
([
configurationPromise
=
Promise
.
all
([
loadSubject
(
cacheKey
),
loadSubject
(
cacheKey
),
getSubjectView
(
cacheKey
),
getSubjectView
(
cacheKey
),
loadApplicationTableDisplayConfig
(
cacheKey
),
])
])
.
then
(([
subjectResponse
,
viewResponse
])
=>
{
.
then
(([
subjectResponse
,
viewResponse
,
tableDisplayConfig
])
=>
{
const
subject
=
subjectResponse
?.
data
||
subjectResponse
?.
subject
||
subjectResponse
;
const
subject
=
subjectResponse
?.
data
||
subjectResponse
?.
subject
||
subjectResponse
;
const
viewCandidates
=
[
const
viewCandidates
=
[
viewResponse
,
viewResponse
,
...
@@ -343,7 +346,7 @@ function loadSubjectWorkbenchConfiguration(subjectName) {
...
@@ -343,7 +346,7 @@ function loadSubjectWorkbenchConfiguration(subjectName) {
}
catch
{
}
catch
{
return
view
;
return
view
;
}
}
})).
then
((
views
)
=>
({
subject
,
views
}));
})).
then
((
views
)
=>
({
subject
,
views
,
...
tableDisplayConfig
}));
})
})
.
catch
((
error
)
=>
{
.
catch
((
error
)
=>
{
// Do not keep a rejected promise: a later reload should be able to retry.
// Do not keep a rejected promise: a later reload should be able to retry.
...
@@ -371,7 +374,7 @@ export async function loadSubjectWorkbenchResources(
...
@@ -371,7 +374,7 @@ export async function loadSubjectWorkbenchResources(
query
=
{},
query
=
{},
{
viewName
=
''
,
filters
=
{},
page
=
0
,
pageSize
=
20
,
hiddenViewNames
=
[]
}
=
{},
{
viewName
=
''
,
filters
=
{},
page
=
0
,
pageSize
=
20
,
hiddenViewNames
=
[]
}
=
{},
)
{
)
{
const
{
subject
,
views
}
=
await
loadSubjectWorkbenchConfiguration
(
subjectName
);
const
{
subject
,
views
,
displayConfig
,
displayConfigError
}
=
await
loadSubjectWorkbenchConfiguration
(
subjectName
);
const
accessibleGridViews
=
buildGridViews
(
views
||
[],
hiddenViewNames
);
const
accessibleGridViews
=
buildGridViews
(
views
||
[],
hiddenViewNames
);
const
activeView
=
accessibleGridViews
.
find
((
view
)
=>
view
.
name
===
viewName
)
const
activeView
=
accessibleGridViews
.
find
((
view
)
=>
view
.
name
===
viewName
)
||
accessibleGridViews
[
0
]
||
accessibleGridViews
[
0
]
...
@@ -393,6 +396,8 @@ export async function loadSubjectWorkbenchResources(
...
@@ -393,6 +396,8 @@ export async function loadSubjectWorkbenchResources(
totalCount
:
pageResult
.
totalCount
,
totalCount
:
pageResult
.
totalCount
,
subject
,
subject
,
views
:
views
||
[],
views
:
views
||
[],
displayConfig
,
displayConfigError
,
};
};
}
}
...
@@ -417,10 +422,10 @@ export function filterWorkbenchViews(views = [], hiddenViewNames = []) {
...
@@ -417,10 +422,10 @@ export function filterWorkbenchViews(views = [], hiddenViewNames = []) {
return
buildGridViews
(
views
,
hiddenViewNames
);
return
buildGridViews
(
views
,
hiddenViewNames
);
}
}
export
function
buildConfiguredWorkbenchView
(
records
,
_filters
,
view
,
subject
)
{
export
function
buildConfiguredWorkbenchView
(
records
,
_filters
,
view
,
subject
,
displayConfig
=
{}
)
{
if
(
!
view
)
return
{
columns
:
[],
filteredRecords
:
[]
};
if
(
!
view
)
return
{
columns
:
[],
filteredRecords
:
[]
};
const
columns
=
buildViewColumns
(
view
,
subject
);
const
columns
=
buildViewColumns
(
view
,
subject
,
{
displayConfig
}
);
const
filteredRecords
=
records
.
filter
((
record
)
=>
{
const
filteredRecords
=
records
.
filter
((
record
)
=>
{
const
rawRecord
=
record
.
raw
||
record
;
const
rawRecord
=
record
.
raw
||
record
;
...
...
src/pages/risk_management/risk-management.business-flow.md
View file @
22d96165
...
@@ -93,3 +93,16 @@ HeroUI Pro `DataGrid` 和 `RecordDetailDrawer`。同一套多维表格组件也
...
@@ -93,3 +93,16 @@ HeroUI Pro `DataGrid` 和 `RecordDetailDrawer`。同一套多维表格组件也
引用并要求至少两条可用版本。
引用并要求至少两条可用版本。
-
引用记录缺失、历史版本不足、导入文件为空或目标公司/模版不存在时,在写入前提示用户
-
引用记录缺失、历史版本不足、导入文件为空或目标公司/模版不存在时,在写入前提示用户
检查数据后重试;写入阶段的接口失败会保留服务端错误,避免静默掩盖部分成功结果。
检查数据后重试;写入阶段的接口失败会保留服务端错误,避免静默掩盖部分成功结果。
## 6. 表格字段数量与标题显示
配置入口为 streams-admin 应用布局或 Subject 的 Grid 视图。基金产品、投监表模版、投监表指标、托管人及管理人、关联关系和关联方列表通过共享工作台读取配置,切换视图后使用当前视图的显示规则。
-
应用默认值通过
`loadApplication(APPLICATION_NAME)`
读取
`layouts.default.subjects.<subjectName>.gridRecordConfig.displayConfig`
。使用项目现有应用标识,不单独硬编码风险管理应用名。
-
属性覆盖顺序为应用
`displayConfig`
→ 视图
`settings.displayConfig`
→ 列
`displayConfig`
→ 列直接设置的
`displayMode`
、
`maxTagCount`
、
`titleField`
。
-
`labels`
展示关联标题或已选选项文案;选项来自字段
`settings.options`
。
`count`
展示有效条目数,空值为
`0`
,单值为
`1`
;悬停或键盘聚焦可查看完整标题,空值不显示提示。
-
`auto`
在有效条目数量大于等于
`maxTagCount`
时显示数量,否则显示标题。仅设置阈值时自动启用
`auto`
;显式
`auto`
的默认阈值为
`3`
;阈值为
`0`
时所有非空值显示数量。
-
引用、选择、子对象与 form 字段适用上述规则。子对象优先读取
`titleField`
(支持 metadata 子字段),然后使用已有
`title/name/_id`
,无标题时显示“第 N 条”;
`{form,data}`
按
`data`
条目计数。空字符串、null 和 undefined 不计数,数字
`0`
保留。
-
例如基金产品的
`metadata.contract_terms`
、投监表模版的
`metadata.associated_indicators`
可配置
`displayMode: "count"`
;基金产品的
`metadata.investment_scope`
可配置
`displayMode: "labels"`
。字段仍以 Subject 定义为准。
-
未配置或配置非法时保留原展示;普通文本、数字、日期、布尔、人员和附件不受影响。首列仍可点击打开详情,行操作入口保持可用。计数只使用当前字段已有数据,不额外查询关联总数,不修改 metadata、筛选、排序、分页、导出或审批规则。
-
加载配置期间沿用列表 loading;应用配置失败时显示警告并继续使用视图配置和列表数据。共享工作台按 Subject 缓存配置,刷新页面重新读取;记录查询失败仍显示列表错误。
src/services/record-api.md
View file @
22d96165
...
@@ -44,6 +44,14 @@
...
@@ -44,6 +44,14 @@
-
`404`
`{"code":404,"message":"record '<name>' not found"}`
-
`404`
`{"code":404,"message":"record '<name>' not found"}`
-
`400`
业务校验失败(缺少必填字段、主键冲突、类型不匹配等)
-
`400`
业务校验失败(缺少必填字段、主键冲突、类型不匹配等)
### 1.2 表格显示配置的关联读取接口
风险管理共享工作台、问题清单与归档批次通过已有
`DataService.loadApplication(APPLICATION_NAME)`
调用
`GET /application/:name`
,读取应用的
`layouts.default.subjects.<subjectName>.gridRecordConfig.displayConfig`
,路径与 streams-admin 的
`useAppLayout().getSubjectConfig()`
/
`RecordGridView`
一致。
视图继续使用
`GET /subject/:subjectName/view`
与
`GET /subject/:subjectName/view/:viewName`
,分别对应
`getSubjectView`
/
`getSubjectViewDetail`
。视图级显示配置位于
`settings.displayConfig`
,单列覆盖位于
`settings.columns[]`
。支持
`displayMode: labels | count | auto`
、
`maxTagCount`
(非负整数)和子对象列
`titleField`
(子字段路径)。
这里只读取配置,不更新应用、视图、Subject 或 Record。应用配置请求失败时仍加载记录,提示配置读取失败并使用视图配置;记录查询和写入协议不变。完整优先级和示例见
`src/pages/problem_rectification/problem-rectification.business-flow.md`
的“5.10 表格字段显示配置”及
`src/pages/risk_management/risk-management.business-flow.md`
的“6. 表格字段数量与标题显示”。
## 2. Record 核心模型字段
## 2. Record 核心模型字段
不同
`subject`
通过
`metadata`
扩展业务字段。
不同
`subject`
通过
`metadata`
扩展业务字段。
...
...
src/services/tableFieldDisplayService.js
0 → 100644
View file @
22d96165
import
{
APPLICATION_NAME
}
from
'@/config/application'
;
import
{
loadApplication
}
from
'@/services/DataService'
;
export
async
function
loadApplicationTableDisplayConfig
(
subjectName
)
{
try
{
const
application
=
await
loadApplication
(
APPLICATION_NAME
);
return
{
displayConfig
:
application
?.
layouts
?.
default
?.
subjects
?.[
subjectName
]?.
gridRecordConfig
?.
displayConfig
||
{},
displayConfigError
:
''
,
};
}
catch
(
error
)
{
return
{
displayConfig
:
{},
displayConfigError
:
`应用表格显示配置读取失败,暂使用视图默认显示:
${
error
?.
message
||
'请稍后重试'
}
`,
};
}
}
src/utils/subjectView.js
View file @
22d96165
...
@@ -297,9 +297,10 @@ export function shouldHideViewTitleColumn(view) {
...
@@ -297,9 +297,10 @@ export function shouldHideViewTitleColumn(view) {
return
view
?.
settings
?.
shortTitle
===
true
;
return
view
?.
settings
?.
shortTitle
===
true
;
}
}
export
function
buildViewColumns
(
view
,
subject
,
{
includeHidden
=
false
}
=
{})
{
export
function
buildViewColumns
(
view
,
subject
,
{
includeHidden
=
false
,
displayConfig
=
{}
}
=
{})
{
const
configuredColumns
=
view
?.
settings
?.
columns
||
[];
const
configuredColumns
=
view
?.
settings
?.
columns
||
[];
const
hideTitleColumn
=
!
includeHidden
&&
shouldHideViewTitleColumn
(
view
);
const
hideTitleColumn
=
!
includeHidden
&&
shouldHideViewTitleColumn
(
view
);
const
viewDisplayConfig
=
{
...
displayConfig
,
...
view
?.
settings
?.
displayConfig
};
if
(
configuredColumns
.
length
>
0
)
{
if
(
configuredColumns
.
length
>
0
)
{
return
configuredColumns
return
configuredColumns
...
@@ -312,6 +313,7 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
...
@@ -312,6 +313,7 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
const
fieldDef
=
getFieldDefinition
(
column
.
field
,
subject
);
const
fieldDef
=
getFieldDefinition
(
column
.
field
,
subject
);
return
{
return
{
...
column
,
...
column
,
displayConfig
:
{
...
viewDisplayConfig
,
...
column
.
displayConfig
},
width
:
column
.
width
||
180
,
width
:
column
.
width
||
180
,
label
:
getConfiguredColumnLabel
(
column
,
fieldDef
),
label
:
getConfiguredColumnLabel
(
column
,
fieldDef
),
fieldDef
:
fieldDef
||
{
name
:
column
.
field
,
label
:
column
.
field
,
type
:
'text'
},
fieldDef
:
fieldDef
||
{
name
:
column
.
field
,
label
:
column
.
field
,
type
:
'text'
},
...
@@ -323,6 +325,7 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
...
@@ -323,6 +325,7 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
if
(
!
hideTitleColumn
)
{
if
(
!
hideTitleColumn
)
{
defaultColumns
.
push
({
defaultColumns
.
push
({
field
:
'title'
,
field
:
'title'
,
displayConfig
:
viewDisplayConfig
,
width
:
180
,
width
:
180
,
label
:
'标题'
,
label
:
'标题'
,
fieldDef
:
{
name
:
'title'
,
label
:
'标题'
,
type
:
'text'
},
fieldDef
:
{
name
:
'title'
,
label
:
'标题'
,
type
:
'text'
},
...
@@ -332,6 +335,7 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
...
@@ -332,6 +335,7 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
toArray
(
subject
?.
fields
).
forEach
((
field
)
=>
{
toArray
(
subject
?.
fields
).
forEach
((
field
)
=>
{
defaultColumns
.
push
({
defaultColumns
.
push
({
field
:
`metadata.
${
field
.
name
}
`
,
field
:
`metadata.
${
field
.
name
}
`
,
displayConfig
:
viewDisplayConfig
,
width
:
180
,
width
:
180
,
label
:
field
.
label
||
field
.
name
,
label
:
field
.
label
||
field
.
name
,
fieldDef
:
field
,
fieldDef
:
field
,
...
...
src/utils/tableFieldDisplay.js
0 → 100644
View file @
22d96165
import
{
formatFieldValue
,
getFieldValue
,
getSelectOptions
,
isReferenceFieldType
,
normalizeRecordLabel
,
normalizeText
,
toArray
,
}
from
'./subjectView'
;
export
const
TABLE_FIELD_DISPLAY_MODES
=
[
'labels'
,
'count'
,
'auto'
];
export
const
DEFAULT_TABLE_MAX_TAG_COUNT
=
3
;
export
function
isTableFieldDisplayConfigurable
(
column
)
{
const
type
=
column
?.
fieldDef
?.
type
;
return
isReferenceFieldType
(
type
)
||
[
'select'
,
'subject'
,
'form'
].
includes
(
type
);
}
export
function
getTableFieldDisplayConfig
(
column
)
{
const
config
=
column
?.
displayConfig
||
{};
const
configuredMode
=
column
?.
displayMode
??
config
.
displayMode
;
const
configuredThreshold
=
column
?.
maxTagCount
??
config
.
maxTagCount
;
const
threshold
=
Number
(
configuredThreshold
);
const
hasThreshold
=
[
'number'
,
'string'
].
includes
(
typeof
configuredThreshold
)
&&
normalizeText
(
configuredThreshold
)
!==
''
&&
Number
.
isInteger
(
threshold
)
&&
threshold
>=
0
;
const
hasMode
=
TABLE_FIELD_DISPLAY_MODES
.
includes
(
configuredMode
);
return
{
isConfigured
:
isTableFieldDisplayConfigurable
(
column
)
&&
(
hasMode
||
hasThreshold
),
displayMode
:
hasMode
?
configuredMode
:
hasThreshold
?
'auto'
:
'labels'
,
maxTagCount
:
hasThreshold
?
threshold
:
DEFAULT_TABLE_MAX_TAG_COUNT
,
titleField
:
normalizeText
(
column
?.
titleField
??
config
.
titleField
),
};
}
function
getDisplayItems
(
value
,
fieldDef
)
{
const
items
=
fieldDef
?.
type
===
'form'
&&
value
&&
!
Array
.
isArray
(
value
)
&&
Array
.
isArray
(
value
.
data
)
?
value
.
data
:
toArray
(
value
);
return
items
.
filter
((
item
)
=>
item
!==
null
&&
item
!==
undefined
&&
!
(
typeof
item
===
'string'
&&
!
item
.
trim
()),
);
}
export
function
getTableFieldDisplay
(
value
,
column
)
{
const
config
=
getTableFieldDisplayConfig
(
column
);
const
items
=
getDisplayItems
(
value
,
column
?.
fieldDef
);
const
type
=
column
?.
fieldDef
?.
type
;
const
options
=
type
===
'select'
?
getSelectOptions
(
column
.
fieldDef
)
:
[];
const
labels
=
items
.
map
((
item
,
index
)
=>
{
if
(
type
===
'select'
)
{
const
optionValue
=
item
&&
typeof
item
===
'object'
?
item
.
value
:
item
;
const
option
=
options
.
find
((
candidate
)
=>
String
(
candidate
.
value
)
===
String
(
optionValue
));
return
normalizeText
(
option
?.
label
??
optionValue
);
}
if
([
'subject'
,
'form'
].
includes
(
type
))
{
const
titleValue
=
config
.
titleField
?
getFieldValue
(
item
,
config
.
titleField
)
??
getFieldValue
(
item
?.
metadata
,
config
.
titleField
)
:
null
;
return
formatFieldValue
(
titleValue
)
||
normalizeRecordLabel
(
item
)
||
`第
${
index
+
1
}
条`
;
}
if
(
type
===
'tag'
)
return
normalizeText
(
item
?.
label
)
||
normalizeRecordLabel
(
item
);
return
normalizeRecordLabel
(
item
);
}).
filter
(
Boolean
);
const
showCount
=
config
.
displayMode
===
'count'
||
(
config
.
displayMode
===
'auto'
&&
items
.
length
>
0
&&
items
.
length
>=
config
.
maxTagCount
);
return
{
...
config
,
count
:
items
.
length
,
labels
,
showCount
,
text
:
showCount
?
String
(
items
.
length
)
:
labels
.
join
(
'、'
),
};
}
export
function
formatTableFieldValue
(
value
,
column
)
{
const
display
=
getTableFieldDisplay
(
value
,
column
);
return
display
.
isConfigured
?
display
.
text
:
formatFieldValue
(
value
,
column
?.
fieldDef
);
}
tests/tableFieldDisplay.test.mjs
0 → 100644
View file @
22d96165
import
assert
from
'node:assert/strict'
;
import
{
readFileSync
}
from
'node:fs'
;
import
{
createRequire
}
from
'node:module'
;
import
{
dirname
,
resolve
}
from
'node:path'
;
import
test
from
'node:test'
;
import
ts
from
'typescript'
;
const
require
=
createRequire
(
import
.
meta
.
url
);
const
sourceRoot
=
new
URL
(
'../src/'
,
import
.
meta
.
url
).
pathname
;
function
loadModule
(
entry
,
dataService
=
{})
{
const
modules
=
new
Map
();
const
load
=
(
path
)
=>
{
if
(
modules
.
has
(
path
))
return
modules
.
get
(
path
);
const
exports
=
{};
modules
.
set
(
path
,
exports
);
const
{
outputText
}
=
ts
.
transpileModule
(
readFileSync
(
path
,
'utf8'
),
{
compilerOptions
:
{
module
:
ts
.
ModuleKind
.
CommonJS
,
target
:
ts
.
ScriptTarget
.
ES2022
,
esModuleInterop
:
true
},
});
const
localRequire
=
(
specifier
)
=>
{
if
(
specifier
===
'./DataService'
||
specifier
===
'@/services/DataService'
)
return
dataService
;
if
(
!
specifier
.
startsWith
(
'.'
)
&&
!
specifier
.
startsWith
(
'@/'
))
return
require
(
specifier
);
const
dependencyPath
=
specifier
.
startsWith
(
'@/'
)
?
resolve
(
sourceRoot
,
specifier
.
slice
(
2
))
:
resolve
(
dirname
(
path
),
specifier
);
return
load
(
`
${
dependencyPath
}
.js`
);
};
new
Function
(
'require'
,
'exports'
,
outputText
)(
localRequire
,
exports
);
return
exports
;
};
return
load
(
resolve
(
sourceRoot
,
entry
));
}
const
{
getTableFieldDisplay
,
getTableFieldDisplayConfig
,
formatTableFieldValue
}
=
loadModule
(
'utils/tableFieldDisplay.js'
);
const
{
buildViewColumns
}
=
loadModule
(
'utils/subjectView.js'
);
const
column
=
(
type
,
config
=
{})
=>
({
field
:
'metadata.values'
,
fieldDef
:
{
type
},
...
config
});
test
(
'count mode counts selected values, including zero, and excludes empty entries'
,
()
=>
{
const
config
=
column
(
'select'
,
{
displayMode
:
'count'
});
assert
.
equal
(
formatTableFieldValue
([
0
,
'选中'
,
null
,
undefined
,
''
,
' '
],
config
),
'2'
);
for
(
const
value
of
[
null
,
undefined
,
''
,
[]])
assert
.
equal
(
formatTableFieldValue
(
value
,
config
),
'0'
);
assert
.
equal
(
formatTableFieldValue
(
0
,
config
),
'1'
);
assert
.
equal
(
formatTableFieldValue
({
_id
:
'one'
,
title
:
'关联标题'
},
column
(
'reference'
,
{
displayMode
:
'count'
})),
'1'
);
});
test
(
'labels show selected option labels and populated record titles'
,
()
=>
{
const
config
=
column
(
'select'
,
{
displayMode
:
'labels'
});
config
.
fieldDef
.
settings
=
{
options
:
[{
value
:
0
,
label
:
'零号'
},
{
value
:
'a'
,
label
:
'选项 A'
},
{
value
:
'b'
,
label
:
'未选项'
}]
};
assert
.
equal
(
formatTableFieldValue
([
'0'
,
'a'
],
config
),
'零号、选项 A'
);
assert
.
equal
(
formatTableFieldValue
([{
_id
:
'id-1'
,
title
:
'记录 A'
},
{
_id
:
'id-2'
,
title
:
'记录 B'
}],
column
(
'reference'
,
{
displayMode
:
'labels'
})),
'记录 A、记录 B'
);
assert
.
equal
(
formatTableFieldValue
([{
name
:
'risk'
,
label
:
'风险标签'
}],
column
(
'tag'
,
{
displayMode
:
'labels'
})),
'风险标签'
);
});
test
(
'maxTagCount alone matches streams-admin at the exact threshold'
,
()
=>
{
const
config
=
column
(
'reference'
,
{
displayConfig
:
{
maxTagCount
:
2
}
});
assert
.
equal
(
formatTableFieldValue
([{
title
:
'A'
}],
config
),
'A'
);
assert
.
equal
(
formatTableFieldValue
([{
title
:
'A'
},
{
title
:
'B'
}],
config
),
'2'
);
assert
.
equal
(
formatTableFieldValue
([],
config
),
''
);
assert
.
equal
(
formatTableFieldValue
([
'A'
],
column
(
'reference'
,
{
displayMode
:
'auto'
,
maxTagCount
:
0
})),
'1'
);
assert
.
equal
(
getTableFieldDisplayConfig
(
column
(
'reference'
,
{
displayMode
:
'auto'
})).
maxTagCount
,
3
);
});
test
(
'embedded subjects and forms use configured child labels without changing data'
,
()
=>
{
const
value
=
[{
responsible_department
:
'审计部'
},
{
metadata
:
{
responsible_department
:
'风险管理部'
}
}];
const
original
=
JSON
.
stringify
(
value
);
const
config
=
column
(
'subject'
,
{
displayMode
:
'labels'
,
titleField
:
'responsible_department'
});
assert
.
equal
(
formatTableFieldValue
(
value
,
config
),
'审计部、风险管理部'
);
assert
.
equal
(
getTableFieldDisplay
(
value
,
{
...
config
,
displayMode
:
'count'
}).
count
,
2
);
assert
.
equal
(
formatTableFieldValue
([{},
{}],
config
),
'第 1 条、第 2 条'
);
assert
.
equal
(
formatTableFieldValue
({
form
:
{
title
:
'表单'
},
data
:
value
},
column
(
'form'
,
{
displayMode
:
'count'
})),
'2'
);
assert
.
equal
(
JSON
.
stringify
(
value
),
original
);
});
test
(
'application defaults, view settings, and column overrides keep independent modes'
,
()
=>
{
const
subject
=
{
fields
:
[{
name
:
'values'
,
type
:
'select'
}]
};
const
view
=
{
settings
:
{
displayConfig
:
{
maxTagCount
:
2
},
columns
:
[{
field
:
'metadata.values'
,
displayMode
:
'labels'
}],
}
};
const
original
=
JSON
.
stringify
(
view
);
const
[
config
]
=
buildViewColumns
(
view
,
subject
,
{
displayConfig
:
{
displayMode
:
'count'
,
maxTagCount
:
5
}
});
assert
.
equal
(
getTableFieldDisplayConfig
(
config
).
maxTagCount
,
2
);
assert
.
equal
(
formatTableFieldValue
([
'A'
,
'B'
],
config
),
'A、B'
);
const
[
nestedConfig
]
=
buildViewColumns
({
settings
:
{
displayConfig
:
{
displayMode
:
'count'
,
maxTagCount
:
5
},
columns
:
[{
field
:
'metadata.values'
,
displayConfig
:
{
displayMode
:
'auto'
,
maxTagCount
:
2
}
}],
}
},
subject
);
assert
.
equal
(
formatTableFieldValue
([
'A'
,
'B'
],
nestedConfig
),
'2'
);
const
fallback
=
buildViewColumns
({
settings
:
{}
},
subject
,
{
displayConfig
:
{
displayMode
:
'count'
}
});
assert
.
equal
(
formatTableFieldValue
([
'A'
,
'B'
],
fallback
.
find
((
item
)
=>
item
.
field
===
'metadata.values'
)),
'2'
);
assert
.
equal
(
JSON
.
stringify
(
view
),
original
);
});
test
(
'missing or invalid config preserves current behavior and does not affect scalar fields'
,
()
=>
{
const
value
=
[{
responsible_department
:
'审计部'
}];
assert
.
equal
(
formatTableFieldValue
(
value
,
column
(
'subject'
)),
JSON
.
stringify
(
value
));
assert
.
equal
(
formatTableFieldValue
(
'A'
,
column
(
'select'
,
{
displayMode
:
'invalid'
,
maxTagCount
:
-
1
})),
'A'
);
for
(
const
type
of
[
'text'
,
'number'
,
'date'
,
'boolean'
,
'user'
,
'file'
,
'object'
])
{
assert
.
equal
(
getTableFieldDisplayConfig
(
column
(
type
,
{
displayMode
:
'count'
})).
isConfigured
,
false
);
}
for
(
const
maxTagCount
of
[
'invalid'
,
-
1
,
1.5
,
null
,
''
,
' '
,
false
,
true
,
[]])
{
assert
.
equal
(
getTableFieldDisplayConfig
(
column
(
'reference'
,
{
displayConfig
:
{
maxTagCount
}
})).
isConfigured
,
false
);
}
});
test
(
'application config is read from the same layout path as streams-admin'
,
async
()
=>
{
const
requests
=
[];
const
service
=
loadModule
(
'services/tableFieldDisplayService.js'
,
{
loadApplication
:
async
(
name
)
=>
{
requests
.
push
(
name
);
return
{
layouts
:
{
default
:
{
subjects
:
{
problem_rectification
:
{
gridRecordConfig
:
{
displayConfig
:
{
maxTagCount
:
2
}
},
}
}
}
}
};
},
});
assert
.
deepEqual
(
await
service
.
loadApplicationTableDisplayConfig
(
'problem_rectification'
),
{
displayConfig
:
{
maxTagCount
:
2
},
displayConfigError
:
''
,
});
assert
.
deepEqual
(
requests
,
[
'problem_rectification_new'
]);
assert
.
deepEqual
(
await
service
.
loadApplicationTableDisplayConfig
(
'issue_rectification_archive'
),
{
displayConfig
:
{},
displayConfigError
:
''
,
});
});
test
(
'unavailable application config returns a warning and usable view fallback'
,
async
()
=>
{
const
service
=
loadModule
(
'services/tableFieldDisplayService.js'
,
{
loadApplication
:
async
()
=>
{
throw
new
Error
(
'无权限'
);
},
});
const
result
=
await
service
.
loadApplicationTableDisplayConfig
(
'problem_rectification'
);
assert
.
deepEqual
(
result
.
displayConfig
,
{});
assert
.
match
(
result
.
displayConfigError
,
/暂使用视图默认显示.*无权限/
);
});
test
(
'risk workbench carries application defaults through resources and view changes'
,
async
()
=>
{
const
applicationRequests
=
[];
const
service
=
loadModule
(
'pages/project_manager_xy/SubjectWorkbenchService.js'
,
{
loadSubject
:
async
()
=>
({
fields
:
[{
name
:
'contract_terms'
,
type
:
'reference'
,
multiple
:
true
}]
}),
getSubjectView
:
async
()
=>
[],
loadApplication
:
async
(
name
)
=>
{
applicationRequests
.
push
(
name
);
return
{
layouts
:
{
default
:
{
subjects
:
{
fund_product
:
{
gridRecordConfig
:
{
displayConfig
:
{
maxTagCount
:
2
}
},
}
}
}
}
};
},
});
const
resources
=
await
service
.
loadSubjectWorkbenchResources
(
'fund_product'
);
assert
.
deepEqual
(
resources
.
displayConfig
,
{
maxTagCount
:
2
});
assert
.
equal
(
resources
.
displayConfigError
,
''
);
const
records
=
[{
raw
:
{
metadata
:
{
contract_terms
:
[{
title
:
'A'
},
{
title
:
'B'
}]
}
}
}];
for
(
const
[
displayMode
,
expected
]
of
[[
'auto'
,
'2'
],
[
'labels'
,
'A、B'
]])
{
const
view
=
{
settings
:
{
columns
:
[{
field
:
'metadata.contract_terms'
,
displayMode
}]
}
};
const
result
=
service
.
buildConfiguredWorkbenchView
(
records
,
{},
view
,
resources
.
subject
,
resources
.
displayConfig
);
assert
.
equal
(
formatTableFieldValue
(
records
[
0
].
raw
.
metadata
.
contract_terms
,
result
.
columns
[
0
]),
expected
);
assert
.
equal
(
result
.
filteredRecords
.
length
,
1
);
}
await
service
.
loadSubjectWorkbenchResources
(
'fund_product'
);
assert
.
equal
(
applicationRequests
.
length
,
1
);
});
test
(
'risk workbench preserves subject resources when application configuration fails'
,
async
()
=>
{
const
service
=
loadModule
(
'pages/project_manager_xy/SubjectWorkbenchService.js'
,
{
loadSubject
:
async
()
=>
({
name
:
'fund_product'
,
fields
:
[]
}),
getSubjectView
:
async
()
=>
[],
loadApplication
:
async
()
=>
{
throw
new
Error
(
'配置不可用'
);
},
});
const
resources
=
await
service
.
loadSubjectWorkbenchResources
(
'fund_product'
);
assert
.
equal
(
resources
.
subject
.
name
,
'fund_product'
);
assert
.
deepEqual
(
resources
.
displayConfig
,
{});
assert
.
match
(
resources
.
displayConfigError
,
/配置不可用/
);
});
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