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
34a0ef70
Commit
34a0ef70
authored
Sep 01, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: align audit project create dialog with view
parent
0eba3150
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
141 additions
and
22 deletions
+141
-22
manifest.json
public/manifest.json
+2
-2
DynamicRecordDialog.jsx
src/components/DynamicRecordDialog.jsx
+137
-20
SubjectRecordWorkbench.jsx
src/components/SubjectRecordWorkbench.jsx
+1
-0
project-management.business-flow.md
...es/project_manager_xy/project-management.business-flow.md
+1
-0
No files found.
public/manifest.json
View file @
34a0ef70
{
"name"
:
"
problem_rectification_new
"
,
"version"
:
"1.0.
6
"
,
"name"
:
"
audit_project_manager
"
,
"version"
:
"1.0.
7
"
,
"build"
:
"abc"
,
"description"
:
"merge master"
,
"date"
:
"2025-07-14 19:27:20"
,
...
...
src/components/DynamicRecordDialog.jsx
View file @
34a0ef70
...
...
@@ -5,6 +5,7 @@ import FileUploadField from '@/components/FileUploadField';
import
FormDatePicker
from
'@/components/FormDatePicker'
;
import
FormSelect
from
'@/components/FormSelect'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
DynamicFormValueEditor
from
'./DynamicFormValueEditor'
;
import
{
createRecord
,
loadSubject
,
...
...
@@ -12,6 +13,7 @@ import {
uploadFile
,
}
from
'@/services/DataService'
;
import
{
buildViewColumns
,
dateInputValue
,
emptyValueForField
,
getReferenceSubjectName
,
...
...
@@ -38,6 +40,96 @@ function isPrimaryField(fieldDef) {
return
Boolean
(
fieldDef
?.
primary
||
fieldDef
?.
isPrimaryKey
||
fieldDef
?.
is_primary_key
);
}
function
isConfiguredRequired
(
column
)
{
return
Boolean
(
column
?.
required
||
column
?.
isRequired
||
column
?.
is_required
||
column
?.
settings
?.
required
||
column
?.
validation
?.
required
,
);
}
function
orderDialogColumnsByGroups
(
columns
,
view
)
{
const
groups
=
Array
.
isArray
(
view
?.
settings
?.
groups
)
?
view
.
settings
.
groups
:
[];
if
(
groups
.
length
===
0
)
return
columns
;
const
columnsByField
=
new
Map
(
columns
.
map
((
column
)
=>
[
column
.
field
,
column
]));
const
fieldToGroup
=
new
Map
();
groups
.
forEach
((
group
)
=>
{
const
fields
=
(
group
?.
children
||
group
?.
fields
||
group
?.
columns
||
[])
.
map
((
item
)
=>
{
const
configuredField
=
typeof
item
===
'string'
?
item
:
item
?.
field
||
item
?.
name
||
item
?.
key
||
''
;
const
normalizedField
=
String
(
configuredField
).
trim
();
const
match
=
columns
.
find
((
column
)
=>
(
column
.
field
===
normalizedField
||
column
.
field
.
replace
(
/^metadata
\.
/
,
''
)
===
normalizedField
.
replace
(
/^metadata
\.
/
,
''
)
));
return
match
?.
field
||
''
;
})
.
filter
(
Boolean
);
fields
.
forEach
((
field
)
=>
{
if
(
!
fieldToGroup
.
has
(
field
))
fieldToGroup
.
set
(
field
,
fields
);
});
});
const
emittedFields
=
new
Set
();
const
orderedColumns
=
[];
columns
.
forEach
((
column
)
=>
{
if
(
emittedFields
.
has
(
column
.
field
))
return
;
const
groupFieldsForColumn
=
fieldToGroup
.
get
(
column
.
field
);
if
(
!
groupFieldsForColumn
)
{
emittedFields
.
add
(
column
.
field
);
orderedColumns
.
push
(
column
);
return
;
}
groupFieldsForColumn
.
forEach
((
field
)
=>
{
const
groupedColumn
=
columnsByField
.
get
(
field
);
if
(
!
groupedColumn
||
emittedFields
.
has
(
field
))
return
;
emittedFields
.
add
(
field
);
orderedColumns
.
push
(
groupedColumn
);
});
});
return
orderedColumns
;
}
function
getDialogColumns
(
subject
,
view
)
{
const
configuredColumns
=
Array
.
isArray
(
view
?.
settings
?.
columns
)
?
view
.
settings
.
columns
:
[];
if
(
configuredColumns
.
length
===
0
)
{
return
orderDialogColumnsByGroups
(
buildViewColumns
(
view
,
subject
).
filter
((
column
)
=>
column
?.
field
),
view
,
);
}
// shortTitle only controls the grid title presentation. Keep an explicitly configured
// title field available here so its requiredness can still follow the view config.
const
columns
=
buildViewColumns
(
view
,
subject
,
{
includeHidden
:
true
})
.
filter
((
column
)
=>
column
?.
field
&&
!
column
.
hide
)
.
map
((
column
)
=>
{
if
(
column
.
field
!==
'title'
)
return
column
;
return
{
...
column
,
fieldDef
:
{
...(
column
.
fieldDef
||
{}),
name
:
'title'
,
label
:
column
.
label
||
'记录标题'
,
type
:
'text'
,
required
:
isConfiguredRequired
(
column
),
},
};
});
return
orderDialogColumnsByGroups
(
columns
,
view
);
}
function
isEmptyValue
(
value
)
{
return
value
===
''
||
value
===
null
||
value
===
undefined
||
(
Array
.
isArray
(
value
)
&&
value
.
length
===
0
...
...
@@ -334,6 +426,15 @@ export function DynamicField({ fieldDef, value, onChange, disabled = false, dept
);
}
else
if
(
type
===
'file'
)
{
control
=
<
FileInput
fieldDef=
{
fieldDef
}
value=
{
value
}
onChange=
{
onChange
}
/>;
}
else
if
(
type
===
'form'
)
{
control
=
(
<
DynamicFormValueEditor
disabled=
{
disabled
}
fieldDef=
{
fieldDef
}
value=
{
value
}
onChange=
{
onChange
}
/>
);
}
else
if
(
type
===
'subject'
)
{
control
=
(
<
NestedSubjectFields
...
...
@@ -344,7 +445,7 @@ export function DynamicField({ fieldDef, value, onChange, disabled = false, dept
depth=
{
depth
}
/>
);
}
else
if
(
type
===
'object'
||
type
===
'form'
)
{
}
else
if
(
type
===
'object'
)
{
control
=
(
<
JsonInput
value=
{
value
}
...
...
@@ -481,12 +582,22 @@ export default function DynamicRecordDialog({
subject
,
subjectName
,
record
,
view
,
})
{
const
[
title
,
setTitle
]
=
useState
(
''
);
const
[
values
,
setValues
]
=
useState
({});
const
[
saving
,
setSaving
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
''
);
const
editing
=
Boolean
(
record
);
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
initialValues
=
useMemo
(()
=>
{
return
(
subject
?.
fields
||
[]).
reduce
((
result
,
field
)
=>
{
...
...
@@ -510,7 +621,7 @@ export default function DynamicRecordDialog({
try
{
const
prepared
=
await
prepareValuesForSubmit
(
subject
,
values
);
const
validationErrors
=
prepared
.
errors
;
if
(
!
title
.
trim
())
{
if
(
titleColumn
?.
fieldDef
?.
required
&&
!
title
.
trim
())
{
validationErrors
.
unshift
(
'记录标题为必填项'
);
}
if
(
validationErrors
.
length
>
0
)
{
...
...
@@ -518,16 +629,16 @@ export default function DynamicRecordDialog({
return
;
}
if
(
editing
)
{
await
updateRecord
(
record
.
name
||
record
.
_id
,
{
title
:
title
.
trim
(),
metadata
:
prepared
.
metadata
,
});
const
payload
=
{
metadata
:
prepared
.
metadata
};
if
(
titleColumn
)
payload
.
title
=
title
.
trim
();
await
updateRecord
(
record
.
name
||
record
.
_id
,
payload
);
}
else
{
await
createRecord
(
{
const
payload
=
{
subject
:
subjectName
,
title
:
title
.
trim
(),
metadata
:
prepared
.
metadata
,
});
};
if
(
titleColumn
&&
title
.
trim
())
payload
.
title
=
title
.
trim
();
await
createRecord
(
payload
);
}
await
onSaved
?.();
onClose
?.();
...
...
@@ -556,17 +667,23 @@ export default function DynamicRecordDialog({
)
}
>
<
div
className=
"grid min-w-0 grid-cols-1 gap-x-8 gap-y-5 md:grid-cols-2"
>
<
TextField
className=
"md:col-span-2"
fullWidth
isRequired
>
<
Label
>
记录标题
</
Label
>
<
Input
value=
{
title
}
placeholder=
"请输入记录标题"
onChange=
{
(
event
)
=>
setTitle
(
event
.
target
.
value
)
}
disabled=
{
saving
}
variant=
"secondary"
/>
</
TextField
>
{
(
subject
?.
fields
||
[]).
map
((
field
)
=>
{
{
titleColumn
?
(
<
TextField
className=
"md:col-span-2"
fullWidth
isRequired=
{
Boolean
(
titleColumn
.
fieldDef
?.
required
)
}
>
<
Label
>
{
titleColumn
.
label
||
'记录标题'
}
</
Label
>
<
Input
value=
{
title
}
placeholder=
{
`请输入${titleColumn.label || '记录标题'}`
}
onChange=
{
(
event
)
=>
setTitle
(
event
.
target
.
value
)
}
disabled=
{
saving
}
variant=
"secondary"
/>
</
TextField
>
)
:
null
}
{
dialogFields
.
map
((
field
)
=>
{
return
(
<
DynamicField
key=
{
field
.
name
}
...
...
src/components/SubjectRecordWorkbench.jsx
View file @
34a0ef70
...
...
@@ -380,6 +380,7 @@ export default function SubjectRecordWorkbench({
subject=
{
subject
}
subjectName=
{
subjectName
}
record=
{
editingRecord
}
view=
{
activeView
}
/>
<
AlertDialog
.
Root
>
...
...
src/pages/project_manager_xy/project-management.business-flow.md
View file @
34a0ef70
...
...
@@ -35,6 +35,7 @@
3.
新建和编辑时业务字段统一写入
`metadata`
;引用和用户字段只提交 ID,日期提交 ISO 8601。
4.
编辑模式下主键字段禁用;必填字段按 Subject 配置校验。
5.
新建与编辑弹窗的空控件按可见字段标签显示输入、选择或搜索提示;提示仅用于界面引导,不参与校验,也不写入提交数据。
新建弹窗按当前 Grid View 的列配置决定字段可见性,并按
`settings.groups`
的子字段顺序排列;根记录标题仅在 View 配置了标题列时展示,是否必填由该列配置决定。创建时未填写的可选标题不写入空字符串。
`form`
类型字段复用详情侧栏的动态表单编辑器,支持模板、分组和数据行维护,不再以 JSON 长文本输入。
6.
删除调用
`POST /record/batch/remove`
,默认软删除;列表行内不提供编辑、复制或归档按钮,字段维护统一从详情侧栏进入。
7.
详情弹窗按符合条件的 detail 视图显示字段,并支持字段内联维护;当前 Detail View 的
`settings.shortTitle === true`
时隐藏记录标题和
`title`
字段,但始终保留“记录详情”功能标题和
...
...
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