Commit 34a0ef70 authored by drigle's avatar drigle

fix: align audit project create dialog with view

parent 0eba3150
{ {
"name": "problem_rectification_new", "name": "audit_project_manager",
"version": "1.0.6", "version": "1.0.7",
"build": "abc", "build": "abc",
"description": "merge master", "description": "merge master",
"date": "2025-07-14 19:27:20", "date": "2025-07-14 19:27:20",
......
...@@ -5,6 +5,7 @@ import FileUploadField from '@/components/FileUploadField'; ...@@ -5,6 +5,7 @@ import FileUploadField from '@/components/FileUploadField';
import FormDatePicker from '@/components/FormDatePicker'; import FormDatePicker from '@/components/FormDatePicker';
import FormSelect from '@/components/FormSelect'; import FormSelect from '@/components/FormSelect';
import XinyuanModal from '@/components/XinyuanModal'; import XinyuanModal from '@/components/XinyuanModal';
import DynamicFormValueEditor from './DynamicFormValueEditor';
import { import {
createRecord, createRecord,
loadSubject, loadSubject,
...@@ -12,6 +13,7 @@ import { ...@@ -12,6 +13,7 @@ import {
uploadFile, uploadFile,
} from '@/services/DataService'; } from '@/services/DataService';
import { import {
buildViewColumns,
dateInputValue, dateInputValue,
emptyValueForField, emptyValueForField,
getReferenceSubjectName, getReferenceSubjectName,
...@@ -38,6 +40,96 @@ function isPrimaryField(fieldDef) { ...@@ -38,6 +40,96 @@ function isPrimaryField(fieldDef) {
return Boolean(fieldDef?.primary || fieldDef?.isPrimaryKey || fieldDef?.is_primary_key); 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) { function isEmptyValue(value) {
return value === '' || value === null || value === undefined || ( return value === '' || value === null || value === undefined || (
Array.isArray(value) && value.length === 0 Array.isArray(value) && value.length === 0
...@@ -334,6 +426,15 @@ export function DynamicField({ fieldDef, value, onChange, disabled = false, dept ...@@ -334,6 +426,15 @@ export function DynamicField({ fieldDef, value, onChange, disabled = false, dept
); );
} else if (type === 'file') { } else if (type === 'file') {
control = <FileInput fieldDef={fieldDef} value={value} onChange={onChange} />; 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') { } else if (type === 'subject') {
control = ( control = (
<NestedSubjectFields <NestedSubjectFields
...@@ -344,7 +445,7 @@ export function DynamicField({ fieldDef, value, onChange, disabled = false, dept ...@@ -344,7 +445,7 @@ export function DynamicField({ fieldDef, value, onChange, disabled = false, dept
depth={depth} depth={depth}
/> />
); );
} else if (type === 'object' || type === 'form') { } else if (type === 'object') {
control = ( control = (
<JsonInput <JsonInput
value={value} value={value}
...@@ -481,12 +582,22 @@ export default function DynamicRecordDialog({ ...@@ -481,12 +582,22 @@ export default function DynamicRecordDialog({
subject, subject,
subjectName, subjectName,
record, record,
view,
}) { }) {
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [values, setValues] = useState({}); const [values, setValues] = useState({});
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const editing = Boolean(record); 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(() => { const initialValues = useMemo(() => {
return (subject?.fields || []).reduce((result, field) => { return (subject?.fields || []).reduce((result, field) => {
...@@ -510,7 +621,7 @@ export default function DynamicRecordDialog({ ...@@ -510,7 +621,7 @@ export default function DynamicRecordDialog({
try { try {
const prepared = await prepareValuesForSubmit(subject, values); const prepared = await prepareValuesForSubmit(subject, values);
const validationErrors = prepared.errors; const validationErrors = prepared.errors;
if (!title.trim()) { if (titleColumn?.fieldDef?.required && !title.trim()) {
validationErrors.unshift('记录标题为必填项'); validationErrors.unshift('记录标题为必填项');
} }
if (validationErrors.length > 0) { if (validationErrors.length > 0) {
...@@ -518,16 +629,16 @@ export default function DynamicRecordDialog({ ...@@ -518,16 +629,16 @@ export default function DynamicRecordDialog({
return; return;
} }
if (editing) { if (editing) {
await updateRecord(record.name || record._id, { const payload = { metadata: prepared.metadata };
title: title.trim(), if (titleColumn) payload.title = title.trim();
metadata: prepared.metadata, await updateRecord(record.name || record._id, payload);
});
} else { } else {
await createRecord({ const payload = {
subject: subjectName, subject: subjectName,
title: title.trim(),
metadata: prepared.metadata, metadata: prepared.metadata,
}); };
if (titleColumn && title.trim()) payload.title = title.trim();
await createRecord(payload);
} }
await onSaved?.(); await onSaved?.();
onClose?.(); onClose?.();
...@@ -556,17 +667,23 @@ export default function DynamicRecordDialog({ ...@@ -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"> <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> {titleColumn ? (
<Label>记录标题</Label> <TextField
<Input className="md:col-span-2"
value={title} fullWidth
placeholder="请输入记录标题" isRequired={Boolean(titleColumn.fieldDef?.required)}
onChange={(event) => setTitle(event.target.value)} >
disabled={saving} <Label>{titleColumn.label || '记录标题'}</Label>
variant="secondary" <Input
/> value={title}
</TextField> placeholder={`请输入${titleColumn.label || '记录标题'}`}
{(subject?.fields || []).map((field) => { onChange={(event) => setTitle(event.target.value)}
disabled={saving}
variant="secondary"
/>
</TextField>
) : null}
{dialogFields.map((field) => {
return ( return (
<DynamicField <DynamicField
key={field.name} key={field.name}
......
...@@ -380,6 +380,7 @@ export default function SubjectRecordWorkbench({ ...@@ -380,6 +380,7 @@ export default function SubjectRecordWorkbench({
subject={subject} subject={subject}
subjectName={subjectName} subjectName={subjectName}
record={editingRecord} record={editingRecord}
view={activeView}
/> />
<AlertDialog.Root> <AlertDialog.Root>
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
3. 新建和编辑时业务字段统一写入 `metadata`;引用和用户字段只提交 ID,日期提交 ISO 8601。 3. 新建和编辑时业务字段统一写入 `metadata`;引用和用户字段只提交 ID,日期提交 ISO 8601。
4. 编辑模式下主键字段禁用;必填字段按 Subject 配置校验。 4. 编辑模式下主键字段禁用;必填字段按 Subject 配置校验。
5. 新建与编辑弹窗的空控件按可见字段标签显示输入、选择或搜索提示;提示仅用于界面引导,不参与校验,也不写入提交数据。 5. 新建与编辑弹窗的空控件按可见字段标签显示输入、选择或搜索提示;提示仅用于界面引导,不参与校验,也不写入提交数据。
新建弹窗按当前 Grid View 的列配置决定字段可见性,并按 `settings.groups` 的子字段顺序排列;根记录标题仅在 View 配置了标题列时展示,是否必填由该列配置决定。创建时未填写的可选标题不写入空字符串。`form` 类型字段复用详情侧栏的动态表单编辑器,支持模板、分组和数据行维护,不再以 JSON 长文本输入。
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` 字段,但始终保留“记录详情”功能标题和
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment