Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
A
audit_project_manager
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
audit_project_manager
Commits
1b7e20fc
Commit
1b7e20fc
authored
Sep 02, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix inherit view restrictions on record creation
parent
d369a505
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
143 additions
and
9 deletions
+143
-9
DynamicRecordDialog.jsx
src/components/DynamicRecordDialog.jsx
+36
-9
project-management.business-flow.md
...es/project_manager_xy/project-management.business-flow.md
+1
-0
subjectView.js
src/utils/subjectView.js
+106
-0
No files found.
src/components/DynamicRecordDialog.jsx
View file @
1b7e20fc
...
@@ -20,6 +20,7 @@ import {
...
@@ -20,6 +20,7 @@ import {
getSelectOptions
,
getSelectOptions
,
isDateFieldType
,
isDateFieldType
,
isReferenceFieldType
,
isReferenceFieldType
,
getViewRestrictionDefaults
,
normalizeProjectYearDisplay
,
normalizeProjectYearDisplay
,
normalizeValueForSubmit
,
normalizeValueForSubmit
,
toArray
,
toArray
,
...
@@ -598,29 +599,46 @@ export default function DynamicRecordDialog({
...
@@ -598,29 +599,46 @@ export default function DynamicRecordDialog({
.
filter
((
column
)
=>
column
.
field
!==
'title'
)
.
filter
((
column
)
=>
column
.
field
!==
'title'
)
.
map
((
column
)
=>
column
.
fieldDef
)
.
map
((
column
)
=>
column
.
fieldDef
)
.
filter
((
field
)
=>
field
?.
name
);
.
filter
((
field
)
=>
field
?.
name
);
const
restrictionDefaults
=
useMemo
(
()
=>
getViewRestrictionDefaults
(
view
,
subject
),
[
subject
,
view
],
);
const
initialValues
=
useMemo
(()
=>
{
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
]);
result
[
field
.
name
]
=
fieldInitialValue
(
field
,
record
?.
metadata
?.[
field
.
name
]);
return
result
;
return
result
;
},
{});
},
{});
},
[
subject
,
record
]);
if
(
!
record
)
{
Object
.
entries
(
restrictionDefaults
.
metadata
).
forEach
(([
fieldName
,
value
])
=>
{
defaults
[
fieldName
]
=
value
;
});
}
return
defaults
;
},
[
subject
,
record
,
restrictionDefaults
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
open
)
{
if
(
!
open
)
{
return
;
return
;
}
}
setTitle
(
record
?.
title
||
record
?.
metadata
?.
project_name
||
''
);
setTitle
(
record
?.
title
||
record
?.
metadata
?.
project_name
||
(
!
editing
?
restrictionDefaults
.
title
??
''
:
''
));
setValues
(
initialValues
);
setValues
(
initialValues
);
setError
(
''
);
setError
(
''
);
},
[
open
,
record
,
initialValues
]);
},
[
open
,
record
,
initialValues
,
editing
,
restrictionDefaults
]);
const
handleSubmit
=
async
()
=>
{
const
handleSubmit
=
async
()
=>
{
setSaving
(
true
);
setSaving
(
true
);
setError
(
''
);
setError
(
''
);
try
{
try
{
const
prepared
=
await
prepareValuesForSubmit
(
subject
,
values
);
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
())
{
if
(
titleColumn
?.
fieldDef
?.
required
&&
!
title
.
trim
())
{
validationErrors
.
unshift
(
'记录标题为必填项'
);
validationErrors
.
unshift
(
'记录标题为必填项'
);
}
}
...
@@ -635,9 +653,16 @@ export default function DynamicRecordDialog({
...
@@ -635,9 +653,16 @@ export default function DynamicRecordDialog({
}
else
{
}
else
{
const
payload
=
{
const
payload
=
{
subject
:
subjectName
,
subject
:
subjectName
,
metadata
:
prepared
.
metadata
,
metadata
:
{
...
prepared
.
metadata
,
...
restrictionDefaults
.
metadata
,
},
};
};
if
(
titleColumn
&&
title
.
trim
())
payload
.
title
=
title
.
trim
();
if
(
titleColumn
&&
title
.
trim
())
{
payload
.
title
=
title
.
trim
();
}
else
if
(
restrictionDefaults
.
title
!==
undefined
)
{
payload
.
title
=
restrictionDefaults
.
title
;
}
await
createRecord
(
payload
);
await
createRecord
(
payload
);
}
}
await
onSaved
?.();
await
onSaved
?.();
...
@@ -678,7 +703,7 @@ export default function DynamicRecordDialog({
...
@@ -678,7 +703,7 @@ export default function DynamicRecordDialog({
value=
{
title
}
value=
{
title
}
placeholder=
{
`请输入${titleColumn.label || '记录标题'}`
}
placeholder=
{
`请输入${titleColumn.label || '记录标题'}`
}
onChange=
{
(
event
)
=>
setTitle
(
event
.
target
.
value
)
}
onChange=
{
(
event
)
=>
setTitle
(
event
.
target
.
value
)
}
disabled=
{
saving
}
disabled=
{
saving
||
(
!
editing
&&
restrictionDefaults
.
title
!==
undefined
)
}
variant=
"secondary"
variant=
"secondary"
/>
/>
</
TextField
>
</
TextField
>
...
@@ -690,7 +715,9 @@ export default function DynamicRecordDialog({
...
@@ -690,7 +715,9 @@ export default function DynamicRecordDialog({
className=
{
shouldSpanDynamicFieldColumns
(
field
)
?
'md:col-span-2'
:
undefined
}
className=
{
shouldSpanDynamicFieldColumns
(
field
)
?
'md:col-span-2'
:
undefined
}
fieldDef=
{
field
}
fieldDef=
{
field
}
value=
{
values
[
field
.
name
]
}
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
)
=>
({
onChange=
{
(
nextValue
)
=>
setValues
((
current
)
=>
({
...
current
,
...
current
,
[
field
.
name
]:
nextValue
,
[
field
.
name
]:
nextValue
,
...
...
src/pages/project_manager_xy/project-management.business-flow.md
View file @
1b7e20fc
...
@@ -36,6 +36,7 @@
...
@@ -36,6 +36,7 @@
4.
编辑模式下主键字段禁用;必填字段按 Subject 配置校验。
4.
编辑模式下主键字段禁用;必填字段按 Subject 配置校验。
5.
新建与编辑弹窗的空控件按可见字段标签显示输入、选择或搜索提示;提示仅用于界面引导,不参与校验,也不写入提交数据。
5.
新建与编辑弹窗的空控件按可见字段标签显示输入、选择或搜索提示;提示仅用于界面引导,不参与校验,也不写入提交数据。
新建弹窗按当前 Grid View 的列配置决定字段可见性,并按
`settings.groups`
的子字段顺序排列;根记录标题仅在 View 配置了标题列时展示,是否必填由该列配置决定。创建时未填写的可选标题不写入空字符串。
`form`
类型字段复用详情侧栏的动态表单编辑器,支持模板、分组和数据行维护,不再以 JSON 长文本输入。
新建弹窗按当前 Grid View 的列配置决定字段可见性,并按
`settings.groups`
的子字段顺序排列;根记录标题仅在 View 配置了标题列时展示,是否必填由该列配置决定。创建时未填写的可选标题不写入空字符串。
`form`
类型字段复用详情侧栏的动态表单编辑器,支持模板、分组和数据行维护,不再以 JSON 长文本输入。
从当前 Grid View 新建记录时,系统同时读取
`settings.restrictions`
中的字段约束;对于
`$eq`
或单值
`$in`
约束,自动把唯一值写入新记录的
`metadata`
(约束字段在创建过程中不可修改,即使未配置为弹窗列也会写入)。多值集合或否定比较无法确定唯一归属时阻止创建并提示调整视图配置;编辑已有记录时保留记录原值,不用视图约束覆盖。
6.
删除调用
`POST /record/batch/remove`
,默认软删除;列表行内不提供编辑、复制或归档按钮,字段维护统一从详情侧栏进入。
6.
删除调用
`POST /record/batch/remove`
,默认软删除;列表行内不提供编辑、复制或归档按钮,字段维护统一从详情侧栏进入。
7.
详情弹窗按符合条件的 detail 视图显示字段,并支持字段内联维护;当前 Detail View 的
7.
详情弹窗按符合条件的 detail 视图显示字段,并支持字段内联维护;当前 Detail View 的
`settings.shortTitle === true`
时隐藏记录标题和
`title`
字段,但始终保留“记录详情”功能标题和
`settings.shortTitle === true`
时隐藏记录标题和
`title`
字段,但始终保留“记录详情”功能标题和
...
...
src/utils/subjectView.js
View file @
1b7e20fc
...
@@ -488,6 +488,112 @@ export function mergeRestrictionsIntoFilter(baseFilter = {}, restrictions = [])
...
@@ -488,6 +488,112 @@ export function mergeRestrictionsIntoFilter(baseFilter = {}, restrictions = [])
},
{
...
baseFilter
});
},
{
...
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 the values that a create dialog can safely inherit from a Grid View.
* A view may filter by a set or by a negative comparison, but neither defines a
* single value that can be written to a newly created record.
*/
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
)
{
export
function
sortRecordsByView
(
records
,
view
)
{
const
sortColumns
=
toArray
(
view
?.
settings
?.
columns
)
const
sortColumns
=
toArray
(
view
?.
settings
?.
columns
)
.
filter
((
column
)
=>
column
.
sort
)
.
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