Commit febb9274 authored by drigle's avatar drigle

fix reference selector selection state

parent 6c4d8a36
...@@ -15,6 +15,22 @@ function normalizeText(value) { ...@@ -15,6 +15,22 @@ function normalizeText(value) {
return String(value || '').trim(); return String(value || '').trim();
} }
function normalizeRecordId(value) {
if (value === null || value === undefined || value === '') {
return '';
}
if (typeof value === 'object') {
const candidate = value._id ?? value.id ?? value.$oid ?? value.name;
if (candidate === value) {
return '';
}
return normalizeRecordId(candidate);
}
return normalizeText(value);
}
function escapeMongoRegex(value) { function escapeMongoRegex(value) {
return normalizeText(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); return normalizeText(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
} }
...@@ -74,19 +90,11 @@ function resolveRecordId(record) { ...@@ -74,19 +90,11 @@ function resolveRecordId(record) {
} }
// Reference 写入协议:只接受 _id / id // Reference 写入协议:只接受 _id / id
return record.id || record._id; return normalizeRecordId(record.id ?? record._id);
} }
function resolveReferenceValueId(value) { function resolveReferenceValueId(value) {
if (!value) { return normalizeRecordId(value);
return '';
}
if (typeof value === 'object') {
return normalizeText(resolveRecordId(value));
}
return normalizeText(value);
} }
function resolveRecordSubjectName(record, fallbackSubject = '') { function resolveRecordSubjectName(record, fallbackSubject = '') {
......
...@@ -20,10 +20,22 @@ function defaultFilter(textValue, inputValue) { ...@@ -20,10 +20,22 @@ function defaultFilter(textValue, inputValue) {
function getSelectedKeySet(selectionMode, selectedKey, selectedKeys) { function getSelectedKeySet(selectionMode, selectedKey, selectedKeys) {
if (selectionMode === 'multiple') { if (selectionMode === 'multiple') {
return new Set(selectedKeys || []); if (selectedKeys === 'all') {
return new Set();
}
return new Set(
Array.from(selectedKeys || [])
.map(toSelectionKey)
.filter(Boolean),
);
} }
return new Set(selectedKey === null || selectedKey === undefined ? [] : [selectedKey]); return new Set(
selectedKey === null || selectedKey === undefined
? []
: [toSelectionKey(selectedKey)].filter(Boolean),
);
} }
function toSelectionKey(value) { function toSelectionKey(value) {
...@@ -79,42 +91,48 @@ export default function XinyuanAutocomplete({ ...@@ -79,42 +91,48 @@ export default function XinyuanAutocomplete({
(selectedOptions.length > 0 (selectedOptions.length > 0
? selectedOptions.map((option) => option.label).join('、') ? selectedOptions.map((option) => option.label).join('、')
: placeholder); : placeholder);
const isMultiple = selectionMode === 'multiple';
const normalizedSelectedKeys = selectedKeySet;
const rootSelectionProps = const rootSelectionProps =
selectionMode === 'multiple' isMultiple
? { selectedKeys } ? { selectedKey: null }
: { selectedKey }; : { selectedKey: normalizedSelectedKeys.values().next().value || null };
const listBoxSelectionProps = const handleMultipleOptionAction = (optionKey) => {
selectionMode === 'multiple' if (!onSelectionChange) {
? {
selectionMode,
selectedKeys,
}
: {};
const visibleOptions = isLoading || errorText ? [] : options;
const handleMultipleOptionPress = (option) => {
if (
selectionMode !== 'multiple' ||
!onSelectionChange ||
option?.isDisabled
) {
return; return;
} }
const optionKey = toSelectionKey(option.id); const nextKeys = new Set(normalizedSelectedKeys);
if (!optionKey) { const normalizedOptionKey = toSelectionKey(optionKey);
if (!normalizedOptionKey) {
return; return;
} }
const nextKeys = new Set( if (nextKeys.has(normalizedOptionKey)) {
Array.from(selectedKeyStringSet).filter(Boolean), nextKeys.delete(normalizedOptionKey);
);
if (nextKeys.has(optionKey)) {
nextKeys.delete(optionKey);
} else { } else {
nextKeys.add(optionKey); nextKeys.add(normalizedOptionKey);
} }
onSelectionChange(nextKeys); onSelectionChange(nextKeys);
}; };
const listBoxSelectionProps = isMultiple
? {
// Autocomplete is single-selection under the hood. Use item actions
// for multi-select so the list cannot replace the controlled values.
selectionMode: 'single',
onAction: handleMultipleOptionAction,
}
: {
selectionMode: 'single',
selectedKeys: normalizedSelectedKeys,
onSelectionChange,
};
const visibleOptions = isLoading || errorText
? []
: options.map((option) => ({
...option,
isSelected: selectedKeyStringSet.has(toSelectionKey(option.id)),
}));
return ( return (
<Autocomplete <Autocomplete
...@@ -128,7 +146,7 @@ export default function XinyuanAutocomplete({ ...@@ -128,7 +146,7 @@ export default function XinyuanAutocomplete({
isRequired={isRequired} isRequired={isRequired}
name={name} name={name}
onClear={onClear} onClear={onClear}
selectionMode={selectionMode} selectionMode={isMultiple ? 'single' : selectionMode}
variant={variant} variant={variant}
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
onSelectionChange={onSelectionChange} onSelectionChange={onSelectionChange}
...@@ -187,18 +205,14 @@ export default function XinyuanAutocomplete({ ...@@ -187,18 +205,14 @@ export default function XinyuanAutocomplete({
)} )}
> >
{(option) => { {(option) => {
const isSelected = selectedKeyStringSet.has( const isSelected = Boolean(option.isSelected);
toSelectionKey(option.id),
);
return ( return (
<ListBox.Item <ListBox.Item
id={option.id} id={option.id}
isDisabled={option.isDisabled} isDisabled={option.isDisabled}
isSelected={isSelected}
textValue={option.textValue || option.label} textValue={option.textValue || option.label}
{...(selectionMode === 'multiple'
? { onPress: () => handleMultipleOptionPress(option) }
: {})}
> >
{renderOption ? ( {renderOption ? (
renderOption(option) renderOption(option)
......
...@@ -42,7 +42,9 @@ ...@@ -42,7 +42,9 @@
Modal 可访问名称。标题、阶段 Tabs 和顶部业务操作固定在 Header,字段内容只在 Body 内纵向 Modal 可访问名称。标题、阶段 Tabs 和顶部业务操作固定在 Header,字段内容只在 Body 内纵向
滚动,滚动内容不得穿过或遮挡阶段 Tabs。查看态的 `reference`、`document`、`view`、 滚动,滚动内容不得穿过或遮挡阶段 Tabs。查看态的 `reference`、`document`、`view`、
`template`、`task`、`tag`、`group`、`category` 字段显示关联记录表,并在字段具备编辑权限时 `template`、`task`、`tag`、`group`、`category` 字段显示关联记录表,并在字段具备编辑权限时
提供“编辑关联记录”入口;保存时只向 `metadata` 写入目标记录 `_id`(多值字段写入 `_id[]`)。 提供“编辑关联记录”入口;引用选择器的输入摘要、列表勾选和当前草稿始终由同一组归一化记录
`_id` 驱动,多值选择支持逐项追加、取消和清空;保存时只向 `metadata` 写入目标记录 `_id`
(多值字段写入 `_id[]`)。
引用目标缺少 Subject、加载失败或保存失败时在字段附近保留可读错误反馈,不丢失当前编辑值。 引用目标缺少 Subject、加载失败或保存失败时在字段附近保留可读错误反馈,不丢失当前编辑值。
8. 列表页不展示前端写死的项目总数、完成率等摘要卡片,仅展示由服务端 Subject/View 配置驱动的视图、筛选和表格内容;项目统计统一在驾驶舱查看。 8. 列表页不展示前端写死的项目总数、完成率等摘要卡片,仅展示由服务端 Subject/View 配置驱动的视图、筛选和表格内容;项目统计统一在驾驶舱查看。
9. 业务列表、动态表单数据表和关联记录表中的长文本最多展示两行,内容始终限制在所属列内;超出内容通过 HeroUI Tooltip 或项目详情查看,避免遮挡相邻列或由单条长文本过度拉高整行。弹窗内只读关联记录表按实际数据行高度收缩,不提供无业务价值的列宽拖拽。 9. 业务列表、动态表单数据表和关联记录表中的长文本最多展示两行,内容始终限制在所属列内;超出内容通过 HeroUI Tooltip 或项目详情查看,避免遮挡相邻列或由单条长文本过度拉高整行。弹窗内只读关联记录表按实际数据行高度收缩,不提供无业务价值的列宽拖拽。
......
...@@ -260,12 +260,16 @@ export function isDateFieldType(fieldType) { ...@@ -260,12 +260,16 @@ export function isDateFieldType(fieldType) {
} }
export function normalizeRecordId(value) { export function normalizeRecordId(value) {
if (!value) { if (value === null || value === undefined || value === '') {
return ''; return '';
} }
if (typeof value === 'object') { if (typeof value === 'object') {
return value._id || value.id || value.name || ''; const candidate = value._id ?? value.id ?? value.$oid ?? value.name;
if (candidate === value) {
return '';
}
return normalizeRecordId(candidate);
} }
return normalizeText(value); return normalizeText(value);
......
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