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
fb9d7988
Commit
fb9d7988
authored
Sep 02, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: inherit view restrictions and honor short titles
parent
8789a8c6
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
118 additions
and
13 deletions
+118
-13
DynamicRecordDialog.jsx
src/components/DynamicRecordDialog.jsx
+40
-9
integrity-risk-prevention.business-flow.md
...isk_prevention/integrity-risk-prevention.business-flow.md
+6
-4
subjectView.js
src/utils/subjectView.js
+72
-0
No files found.
src/components/DynamicRecordDialog.jsx
View file @
fb9d7988
...
...
@@ -20,7 +20,9 @@ import {
getSelectOptions
,
isDateFieldType
,
isReferenceFieldType
,
getViewRestrictionDefaults
,
normalizeValueForSubmit
,
shouldHideViewTitleColumn
,
toArray
,
}
from
'@/utils/subjectView'
;
import
{
Delete
,
PlusIcon
}
from
'@/components/AppIcons'
;
...
...
@@ -45,8 +47,13 @@ function isConfiguredRequired(column) {
}
function
getDialogColumns
(
subject
,
view
)
{
const
hideTitleColumn
=
shouldHideViewTitleColumn
(
view
);
const
columns
=
buildViewColumns
(
view
,
subject
,
{
includeHidden
:
true
})
.
filter
((
column
)
=>
column
?.
field
&&
!
column
.
hide
)
.
filter
((
column
)
=>
(
column
?.
field
&&
!
column
.
hide
&&
(
!
hideTitleColumn
||
column
.
field
!==
'title'
)
))
.
map
((
column
)
=>
column
.
field
===
'title'
?
{
...
column
,
fieldDef
:
{
...
...
@@ -477,33 +484,50 @@ export default function DynamicRecordDialog({
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
''
);
const
editing
=
Boolean
(
record
);
const
hideTitle
=
shouldHideViewTitleColumn
(
view
);
const
dialogColumns
=
useMemo
(()
=>
getDialogColumns
(
subject
,
view
),
[
subject
,
view
]);
const
titleColumn
=
dialogColumns
.
find
((
column
)
=>
column
.
field
===
'title'
);
const
dialogFields
=
dialogColumns
.
filter
((
column
)
=>
column
.
field
!==
'title'
)
.
map
((
column
)
=>
column
.
fieldDef
).
filter
((
field
)
=>
field
?.
name
);
const
restrictionDefaults
=
useMemo
(
()
=>
getViewRestrictionDefaults
(
view
,
subject
),
[
subject
,
view
],
);
const
initialValues
=
useMemo
(()
=>
{
return
(
subject
?.
fields
||
[]).
reduce
((
result
,
field
)
=>
{
const
defaults
=
(
subject
?.
fields
||
[]).
reduce
((
result
,
field
)
=>
{
result
[
field
.
name
]
=
fieldInitialValue
(
field
,
record
?.
metadata
?.[
field
.
name
]);
return
result
;
},
{});
},
[
subject
,
record
]);
if
(
!
record
)
{
Object
.
entries
(
restrictionDefaults
.
metadata
).
forEach
(([
fieldName
,
value
])
=>
{
defaults
[
fieldName
]
=
value
;
});
}
return
defaults
;
},
[
subject
,
record
,
restrictionDefaults
]);
useEffect
(()
=>
{
if
(
!
open
)
{
return
;
}
setTitle
(
record
?.
title
||
record
?.
metadata
?.
project_name
||
''
);
setTitle
(
record
?.
title
||
record
?.
metadata
?.
project_name
||
(
!
editing
?
restrictionDefaults
.
title
??
''
:
''
));
setValues
(
initialValues
);
setError
(
''
);
},
[
open
,
record
,
initialValues
]);
},
[
open
,
record
,
initialValues
,
editing
,
restrictionDefaults
]);
const
handleSubmit
=
async
()
=>
{
setSaving
(
true
);
setError
(
''
);
try
{
const
prepared
=
await
prepareValuesForSubmit
(
subject
,
values
);
const
validationErrors
=
prepared
.
errors
;
const
validationErrors
=
[...
prepared
.
errors
];
if
(
!
editing
&&
restrictionDefaults
.
errors
.
length
>
0
)
{
validationErrors
.
unshift
(...
restrictionDefaults
.
errors
);
}
if
(
titleColumn
?.
fieldDef
?.
required
&&
!
title
.
trim
())
{
validationErrors
.
unshift
(
'记录标题为必填项'
);
}
...
...
@@ -518,9 +542,13 @@ export default function DynamicRecordDialog({
}
else
{
const
payload
=
{
subject
:
subjectName
,
metadata
:
prepared
.
metadata
,
metadata
:
{
...
prepared
.
metadata
,
...
restrictionDefaults
.
metadata
,
},
};
if
(
titleColumn
&&
title
.
trim
())
payload
.
title
=
title
.
trim
();
else
if
(
!
hideTitle
&&
restrictionDefaults
.
title
!==
undefined
)
payload
.
title
=
restrictionDefaults
.
title
;
await
createRecord
(
payload
);
}
await
onSaved
?.();
...
...
@@ -542,7 +570,7 @@ export default function DynamicRecordDialog({
title=
{
editing
?
'编辑记录'
:
'新建记录'
}
description=
{
editing
?
'修改当前记录的字段内容后保存,字段校验将在保存时执行。'
:
'填写记录标题和字段内容后保存新记录。'
}
:
`${hideTitle ? '填写字段内容' : '填写记录标题和字段内容'}后保存新记录。`
}
footer=
{
(
<>
<
Button
isDisabled=
{
saving
}
variant=
"secondary"
onPress=
{
onClose
}
>
取消
</
Button
>
...
...
@@ -559,6 +587,7 @@ export default function DynamicRecordDialog({
placeholder=
{
`请输入${titleColumn.label || '记录标题'}`
}
value=
{
title
}
onChange=
{
(
event
)
=>
setTitle
(
event
.
target
.
value
)
}
disabled=
{
saving
||
(
!
editing
&&
restrictionDefaults
.
title
!==
undefined
)
}
variant=
"secondary"
/>
</
TextField
>
:
null
}
...
...
@@ -571,7 +600,9 @@ export default function DynamicRecordDialog({
<
DynamicField
fieldDef=
{
field
}
value=
{
values
[
field
.
name
]
}
disabled=
{
saving
||
(
editing
&&
isPrimaryField
(
field
))
}
disabled=
{
saving
||
(
editing
&&
isPrimaryField
(
field
))
||
(
!
editing
&&
Object
.
prototype
.
hasOwnProperty
.
call
(
restrictionDefaults
.
metadata
,
field
.
name
)
)
}
onChange=
{
(
nextValue
)
=>
setValues
((
current
)
=>
({
...
current
,
[
field
.
name
]:
nextValue
,
...
...
src/pages/integrity_risk_prevention/integrity-risk-prevention.business-flow.md
View file @
fb9d7988
...
...
@@ -30,9 +30,10 @@
5.
归档页为只读,不提供新建、编辑、复制或删除。
6.
页面请求必须展示加载、成功和错误状态;删除与替换导入必须提供确认。删除确认弹窗
仅展示选中记录数量和软删除说明,不展示记录标题列表。
7.
Grid View 的
`settings.shortTitle === true`
时仅隐藏列表
`title`
展示列;Detail View
的同名配置同时隐藏详情 Header 的记录标题和字段区
`title`
。
`false`
或未配置时正常
显示。该配置不删除标题数据,也不改变标题搜索、排序、筛选、详情身份或保存协议。
7.
Grid View 的
`settings.shortTitle === true`
时隐藏列表
`title`
展示列及新建/编辑弹窗中的根级
“记录标题”输入框;此时不校验、不提交根级
`title`
。Detail View 的同名配置同时隐藏详情
Header 的记录标题和字段区
`title`
。
`false`
或未配置时正常显示并保留标题必填逻辑。该配置
不删除已有标题数据,也不改变标题搜索、排序、筛选或详情身份。
8.
Subject 配置为多选的选择字段,在新建弹窗和详情编辑中使用可搜索的下拉勾选器;
已选项以标签展示并支持逐项移除或清空,保存值仍为
`settings.options`
中的 value 数组。
9.
列表工具栏默认只显示新建、导入和主要导出等常用入口;展示表的次要导出与归档动作
...
...
@@ -72,7 +73,8 @@
-
列表字段、字段标题和筛选项均从当前 Grid View 配置生成;切换视图会清空当前筛选和勾选状态。
-
筛选控件按视图字段类型生成,引用和用户字段通过远程搜索选择,查询提交后按字段值向
`/record/query`
发送远程
`filter`
;列表不再提供关键字搜索。
-
新建记录弹窗只渲染当前视图配置的字段,包含视图显式配置的
`title`
字段;标题是否显示及是否必填遵循视图配置,业务字段仍按 Subject 定义写入
`metadata`
。
-
新建记录弹窗只渲染当前视图配置的字段;
`settings.shortTitle === true`
时隐藏列表和弹窗中的根级“记录标题”,不校验、不提交根级
`title`
,未配置或为
`false`
时保留标题显示和必填逻辑。
-
从当前 Grid View 新建记录时读取
`settings.restrictions`
:
`$eq`
或单值
`$in`
约束自动写入新记录
`metadata`
,对应字段(包括未列入弹窗的字段)不可修改;多值集合、否定比较或同字段冲突无法确定唯一值时禁止创建并提示调整视图。编辑已有记录保留原值,不用视图限制覆盖。
-
列表加载期间 loading 指示器在表格区域居中显示;查询失败保留错误状态,不提交成功反馈。
-
Grid View 的
`settings.viewAuth`
只控制视图可见性。普通用户只读取
...
...
src/utils/subjectView.js
View file @
fb9d7988
...
...
@@ -534,6 +534,78 @@ export function mergeRestrictionsIntoFilter(baseFilter = {}, restrictions = [])
},
{
...
baseFilter
});
}
function
normalizeRestrictionFieldPath
(
fieldPath
)
{
const
normalized
=
normalizeText
(
fieldPath
);
if
(
!
normalized
||
normalized
===
'title'
||
normalized
.
startsWith
(
'metadata.'
))
{
return
normalized
;
}
return
`metadata.
${
normalized
}
`
;
}
function
hasRestrictionValue
(
value
)
{
return
value
!==
undefined
&&
value
!==
null
&&
value
!==
''
;
}
function
restrictionValuesEqual
(
left
,
right
)
{
if
(
Object
.
is
(
left
,
right
))
return
true
;
try
{
return
JSON
.
stringify
(
left
)
===
JSON
.
stringify
(
right
);
}
catch
(
error
)
{
return
false
;
}
}
/** Resolve only restrictions that identify one unambiguous value for creation. */
export
function
getViewRestrictionDefaults
(
view
,
subject
)
{
const
assignments
=
new
Map
();
const
unresolvedPaths
=
new
Set
();
const
errors
=
new
Map
();
const
addError
=
(
fieldPath
,
fieldDef
)
=>
{
if
(
errors
.
has
(
fieldPath
))
return
;
const
label
=
fieldDef
?.
label
||
getFieldNameFromPath
(
fieldPath
)
||
fieldPath
;
errors
.
set
(
fieldPath
,
`
${
label
}
的视图约束无法自动确定唯一值,请先调整视图配置。`
);
};
toArray
(
view
?.
settings
?.
restrictions
).
forEach
((
restriction
)
=>
{
if
(
restriction
?.
type
!==
'field'
||
!
restriction
.
field
||
restriction
.
value
===
undefined
)
return
;
const
fieldPath
=
normalizeRestrictionFieldPath
(
restriction
.
field
);
const
fieldDef
=
getFieldDefinition
(
fieldPath
,
subject
);
const
comparator
=
normalizeText
(
restriction
.
comparator
||
'$in'
);
const
values
=
toArray
(
restriction
.
value
).
filter
(
hasRestrictionValue
);
if
(
!
fieldPath
||
!
[
'$eq'
,
'$in'
].
includes
(
comparator
)
||
values
.
length
!==
1
)
{
if
(
fieldPath
)
{
unresolvedPaths
.
add
(
fieldPath
);
addError
(
fieldPath
,
fieldDef
);
}
return
;
}
if
(
unresolvedPaths
.
has
(
fieldPath
))
return
;
let
value
=
normalizeValueForSubmit
(
values
[
0
],
fieldDef
);
if
(
fieldDef
?.
multiple
)
value
=
toArray
(
value
);
const
existing
=
assignments
.
get
(
fieldPath
);
if
(
assignments
.
has
(
fieldPath
)
&&
!
restrictionValuesEqual
(
existing
,
value
))
{
assignments
.
delete
(
fieldPath
);
unresolvedPaths
.
add
(
fieldPath
);
addError
(
fieldPath
,
fieldDef
);
return
;
}
assignments
.
set
(
fieldPath
,
value
);
});
const
metadata
=
{};
let
title
;
assignments
.
forEach
((
value
,
fieldPath
)
=>
{
if
(
unresolvedPaths
.
has
(
fieldPath
))
return
;
if
(
fieldPath
===
'title'
)
{
title
=
value
;
return
;
}
const
fieldName
=
getFieldNameFromPath
(
fieldPath
);
if
(
fieldName
)
metadata
[
fieldName
]
=
value
;
});
return
{
metadata
,
title
,
errors
:
[...
errors
.
values
()]
};
}
export
function
sortRecordsByView
(
records
,
view
)
{
const
sortColumns
=
toArray
(
view
?.
settings
?.
columns
)
.
filter
((
column
)
=>
column
.
sort
)
...
...
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