-
Notifications
You must be signed in to change notification settings - Fork 8.1k
feat: setValues合并算法支持嵌套对象赋值,解决嵌套对象无法正常初始化问题 #6352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
1455668754
wants to merge
8
commits into
vbenjs:main
Choose a base branch
from
1455668754:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
7e4abc2
setValues合并算法支持嵌套对象赋值,解决嵌套对象无法正常初始化问题
1455668754 8456353
Merge branch 'vbenjs:main' into main
1455668754 600a9ed
setValues合并算法支持嵌套对象赋值,且排除不存在于schema中的属性
1455668754 b16f81e
Merge branch 'main' into main
1455668754 0db8061
Update form-api.ts
1455668754 ea32895
Merge branch 'vbenjs:main' into main
1455668754 5aa1316
Merge branch 'main' into main
1455668754 2e96266
Merge branch 'main' into main
jinmao88 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,6 @@ import { isRef, toRaw } from 'vue'; | |
import { Store } from '@vben-core/shared/store'; | ||
import { | ||
bindMethods, | ||
createMerge, | ||
formatDate, | ||
isDate, | ||
isDayjsObject, | ||
|
@@ -330,24 +329,114 @@ export class FormApi { | |
} | ||
|
||
/** | ||
* 合并算法有待改进,目前的算法不支持object类型的值。 | ||
* antd的日期时间相关组件的值类型为dayjs对象 | ||
* element-plus的日期时间相关组件的值类型可能为Date对象 | ||
* 以上两种类型需要排除深度合并 | ||
* 深度合并两个对象,支持嵌套对象、数组直接覆盖,忽略 Date 和 Dayjs 对象深度合并 | ||
* | ||
* 主要用于合并表单当前值 [target] 与传入的新值 [source] | ||
* 合并策略: | ||
* - 基本类型直接覆盖 | ||
* - 数组直接替换 | ||
* - 非日期类对象进行递归合并 | ||
* | ||
* @param target - 当前对象(通常是 form.values) | ||
* @param source - 新传入的对象(通常是 fields) | ||
* @returns 返回合并后的新对象,不修改原对象 | ||
* | ||
* @example | ||
* fieldMergeFn({ a: { b: 1 } }, { a: { c: 2 }, d: 3 }); | ||
* // 返回: { a: { b: 1, c: 2 }, d: 3 } | ||
*/ | ||
const fieldMergeFn = createMerge((obj, key, value) => { | ||
if (key in obj) { | ||
obj[key] = | ||
!Array.isArray(obj[key]) && | ||
isObject(obj[key]) && | ||
!isDayjsObject(obj[key]) && | ||
!isDate(obj[key]) | ||
? fieldMergeFn(obj[key], value) | ||
: value; | ||
const fieldMergeFn = ( | ||
target: Record<string, any>, | ||
source: Record<string, any>, | ||
) => { | ||
const result = { ...target }; | ||
|
||
for (const key in source) { | ||
const targetValue = result[key]; | ||
const sourceValue = source[key]; | ||
|
||
// 如果 sourceValue 是 null 或 undefined,保留旧值 | ||
if (sourceValue === null || sourceValue === undefined) { | ||
continue; | ||
} | ||
|
||
// 如果 sourceValue 是数组,直接覆盖 | ||
if (Array.isArray(sourceValue)) { | ||
result[key] = sourceValue; | ||
} | ||
// 如果 sourceValue 是对象(非 Date、非 Dayjs),进行深度合并 | ||
else if ( | ||
isObject(sourceValue) && | ||
!isDate(sourceValue) && | ||
!isDayjsObject(sourceValue) | ||
) { | ||
result[key] = | ||
isObject(targetValue) && | ||
!isDate(targetValue) && | ||
!isDayjsObject(targetValue) | ||
? fieldMergeFn(targetValue, sourceValue) | ||
: sourceValue; | ||
} | ||
// 其他情况(如基本类型)直接赋值 | ||
else { | ||
result[key] = sourceValue; | ||
} | ||
} | ||
return true; | ||
}); | ||
const filteredFields = fieldMergeFn(fields, form.values); | ||
|
||
return result; | ||
}; | ||
|
||
/** | ||
* 从对象中提取指定字段路径的子集,支持多级嵌套字段(如 'user.address.city') | ||
* | ||
* @param obj - 要从中提取字段的对象 | ||
* @param filedNames - 字段路径数组,可以是多级字段(例如 ['user.name', 'user.age']) | ||
* @returns 返回一个新对象,仅包含 `filedNames` 所指定的字段及其值 | ||
* | ||
* @example | ||
* const obj = { | ||
* user: { name: 'Alice', age: 25 }, | ||
* email: '[email protected]' | ||
* }; | ||
* pickFields(obj, ['user.name', 'email']); | ||
* // 返回: { user: { name: 'Alice' }, email: '[email protected]' } | ||
*/ | ||
function pickFields(obj: Record<string, any>, filedNames: string[]) { | ||
const result: Record<string, any> = {}; | ||
|
||
for (const path of filedNames) { | ||
const keys: string[] = path.split('.'); | ||
let value: any = obj; | ||
let target: any = result; | ||
|
||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i] as string; | ||
|
||
if (value && typeof value === 'object' && key in value) { | ||
value = value[key]; | ||
if (i === keys.length - 1) { | ||
// 最后一级字段存在才赋值 | ||
target[key] = value; | ||
} else { | ||
// 初始化中间结构 | ||
target[key] = target[key] || {}; | ||
target = target[key]; | ||
} | ||
} else { | ||
// 路径不存在则跳过 | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
Comment on lines
+389
to
+433
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Harden pickFields: block prototype keys, use hasOwnProperty, and fix typo in parameter name
- function pickFields(obj: Record<string, any>, filedNames: string[]) {
+ function pickFields(obj: Record<string, any>, fieldNames: string[]) {
const result: Record<string, any> = {};
- for (const path of filedNames) {
- const keys: string[] = path.split('.');
+ for (const rawPath of fieldNames) {
+ const path = String(rawPath).trim();
+ if (!path) continue;
+ const keys: string[] = path.split('.');
let value: any = obj;
let target: any = result;
for (let i = 0; i < keys.length; i++) {
const key = keys[i] as string;
- if (value && typeof value === 'object' && key in value) {
+ // Block prototype-polluting keys
+ if (key === '__proto__' || key === 'prototype' || key === 'constructor') {
+ break;
+ }
+
+ if (
+ value &&
+ typeof value === 'object' &&
+ Object.prototype.hasOwnProperty.call(value, key)
+ ) {
value = value[key];
if (i === keys.length - 1) {
// 最后一级字段存在才赋值
target[key] = value;
} else {
// 初始化中间结构
- target[key] = target[key] || {};
+ target[key] = isObject(target[key]) ? target[key] : {};
target = target[key];
}
} else {
// 路径不存在则跳过
break;
}
}
}
return result;
}
|
||
|
||
const fieldNames = (this.state?.schema ?? []).map((item) => item.fieldName); | ||
const filteredFields = pickFields( | ||
fieldMergeFn(form.values, fields), | ||
fieldNames, | ||
); | ||
this.handleStringToArrayFields(filteredFields); | ||
form.setValues(filteredFields, shouldValidate); | ||
} | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Harden deep-merge: prevent prototype pollution, avoid for-in over prototypes, and clone arrays to avoid aliasing
Apply these changes:
📝 Committable suggestion
🤖 Prompt for AI Agents