@yibozhang/pro-table 16.1.6 → 16.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,4 +22,4 @@ export { ArrayFormService } from "./lib/page-public/array-form";
22
22
  // Export modal width detector
23
23
  export { initModalWidthDetector } from "./lib/utils/modal-width-detector";
24
24
  export { initSelectWidthDetector } from "./lib/utils/select-width-detector";
25
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byby10YWJsZS9zcmMvcHVibGljLWFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsNkNBQTZDLENBQUM7QUFDNUQsY0FBYyx5Q0FBeUMsQ0FBQztBQUN4RCxjQUFjLHNDQUFzQyxDQUFDO0FBQ3JELGNBQWMsK0NBQStDLENBQUM7QUFDOUQsY0FBYyw0Q0FBNEMsQ0FBQztBQUMzRCxjQUFjLG1EQUFtRCxDQUFDO0FBQ2xFLGNBQWMsZ0RBQWdELENBQUM7QUFDL0QsY0FBYyx1Q0FBdUMsQ0FBQztBQUN0RCxjQUFjLG9DQUFvQyxDQUFDO0FBRW5ELHFDQUFxQztBQUNyQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFjOUQsc0NBQXNDO0FBQ3RDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBV2hFLDhCQUE4QjtBQUM5QixPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUMxRSxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qXHJcbiAqIFB1YmxpYyBBUEkgU3VyZmFjZSBvZiBwcm8tdGFibGVcclxuICovXHJcblxyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvcHJvLXRhYmxlLmNvbXBvbmVudFwiO1xyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvcHJvLXRhYmxlLm1vZHVsZVwiO1xyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvY29uc3RhbnRzXCI7XHJcbmV4cG9ydCAqIGZyb20gXCIuL2xpYi90eXBlXCI7XHJcbmV4cG9ydCAqIGZyb20gXCIuL2xpYi90b2tlbnNcIjtcclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL3BsYXRlLWlucHV0L3BsYXRlLXByZWZpeC1sb2FkLnNlcnZpY2VcIjtcclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL3BsYXRlLWlucHV0L3BsYXRlLWlucHV0LmNvbXBvbmVudFwiO1xyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvcGxhdGUtaW5wdXQvcGxhdGUtaW5wdXQubW9kdWxlXCI7XHJcbmV4cG9ydCAqIGZyb20gXCIuL2xpYi9wYWdlLWNvbnRhaW5lci9wYWdlLWNvbnRhaW5lci5jb21wb25lbnRcIjtcclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL3BhZ2UtY29udGFpbmVyL3BhZ2UtY29udGFpbmVyLm1vZHVsZVwiO1xyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvdGFibGUtc2VhcmNoLWJhci90YWJsZS1zZWFyY2gtYmFyLmNvbXBvbmVudFwiO1xyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvdGFibGUtc2VhcmNoLWJhci90YWJsZS1zZWFyY2gtYmFyLW1vZHVsZVwiO1xyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvZGlyZWN0aXZlcy90cmltLWlucHV0LmRpcmVjdGl2ZVwiO1xyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvZGlyZWN0aXZlcy90cmltLWlucHV0Lm1vZHVsZVwiO1xyXG5cclxuLy8gRXhwb3J0IGFudGQtZm9ybSBzZXJ2aWNlIGFuZCB0eXBlc1xyXG5leHBvcnQgeyBBbnRkRm9ybVNlcnZpY2UgfSBmcm9tIFwiLi9saWIvcGFnZS1wdWJsaWMvYW50ZC1mb3JtXCI7XHJcbmV4cG9ydCB0eXBlIHtcclxuICBGaWVsZEVycm9yTWVzc2FnZSxcclxuICBGaWVsZEVycm9yTWVzc2FnZXMsXHJcbiAgRmllbGRDb25maWcsXHJcbiAgRm9ybU1vZGlmeVR5cGUsXHJcbiAgRm9ybUNvbmZpZyxcclxuICBXYXRjaE9wdGlvbnMsXHJcbiAgQWRkRmllbGRzQ29uZmlnT3B0aW9ucyxcclxuICBBZGRGaWVsZHNDb25maWdSZXN1bHQsXHJcbiAgUmVtb3ZlRmllbGRzQ29uZmlnT3B0aW9ucyxcclxuICBSZW1vdmVGaWVsZHNDb25maWdSZXN1bHQsXHJcbn0gZnJvbSBcIi4vbGliL3BhZ2UtcHVibGljL2FudGQtZm9ybVwiO1xyXG5cclxuLy8gRXhwb3J0IGFycmF5LWZvcm0gc2VydmljZSBhbmQgdHlwZXNcclxuZXhwb3J0IHsgQXJyYXlGb3JtU2VydmljZSB9IGZyb20gXCIuL2xpYi9wYWdlLXB1YmxpYy9hcnJheS1mb3JtXCI7XHJcbmV4cG9ydCB0eXBlIHtcclxuICBWYWxpZGF0aW9uUmVzdWx0LFxyXG4gIEZpZWxkVmFsaWRhdG9yLFxyXG4gIEZpZWxkQ29uZmlnIGFzIEFycmF5RmllbGRDb25maWcsXHJcbiAgQXJyYXlGb3JtUm93LFxyXG4gIEFycmF5Rm9ybUNvbmZpZyxcclxuICBBcnJheUZvcm1TdG9yZUl0ZW0sXHJcbiAgQXJyYXlGb3JtU3RvcmUsXHJcbn0gZnJvbSBcIi4vbGliL3BhZ2UtcHVibGljL2FycmF5LWZvcm1cIjtcclxuXHJcbi8vIEV4cG9ydCBtb2RhbCB3aWR0aCBkZXRlY3RvclxyXG5leHBvcnQgeyBpbml0TW9kYWxXaWR0aERldGVjdG9yIH0gZnJvbSBcIi4vbGliL3V0aWxzL21vZGFsLXdpZHRoLWRldGVjdG9yXCI7XHJcbmV4cG9ydCB7IGluaXRTZWxlY3RXaWR0aERldGVjdG9yIH0gZnJvbSBcIi4vbGliL3V0aWxzL3NlbGVjdC13aWR0aC1kZXRlY3RvclwiO1xyXG4iXX0=
25
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byby10YWJsZS9zcmMvcHVibGljLWFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsNkNBQTZDLENBQUM7QUFDNUQsY0FBYyx5Q0FBeUMsQ0FBQztBQUN4RCxjQUFjLHNDQUFzQyxDQUFDO0FBQ3JELGNBQWMsK0NBQStDLENBQUM7QUFDOUQsY0FBYyw0Q0FBNEMsQ0FBQztBQUMzRCxjQUFjLG1EQUFtRCxDQUFDO0FBQ2xFLGNBQWMsZ0RBQWdELENBQUM7QUFDL0QsY0FBYyx1Q0FBdUMsQ0FBQztBQUN0RCxjQUFjLG9DQUFvQyxDQUFDO0FBRW5ELHFDQUFxQztBQUNyQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFnQjlELHNDQUFzQztBQUN0QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQVdoRSw4QkFBOEI7QUFDOUIsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFDMUUsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sbUNBQW1DLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxyXG4gKiBQdWJsaWMgQVBJIFN1cmZhY2Ugb2YgcHJvLXRhYmxlXHJcbiAqL1xyXG5cclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL3Byby10YWJsZS5jb21wb25lbnRcIjtcclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL3Byby10YWJsZS5tb2R1bGVcIjtcclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL2NvbnN0YW50c1wiO1xyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvdHlwZVwiO1xyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvdG9rZW5zXCI7XHJcbmV4cG9ydCAqIGZyb20gXCIuL2xpYi9wbGF0ZS1pbnB1dC9wbGF0ZS1wcmVmaXgtbG9hZC5zZXJ2aWNlXCI7XHJcbmV4cG9ydCAqIGZyb20gXCIuL2xpYi9wbGF0ZS1pbnB1dC9wbGF0ZS1pbnB1dC5jb21wb25lbnRcIjtcclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL3BsYXRlLWlucHV0L3BsYXRlLWlucHV0Lm1vZHVsZVwiO1xyXG5leHBvcnQgKiBmcm9tIFwiLi9saWIvcGFnZS1jb250YWluZXIvcGFnZS1jb250YWluZXIuY29tcG9uZW50XCI7XHJcbmV4cG9ydCAqIGZyb20gXCIuL2xpYi9wYWdlLWNvbnRhaW5lci9wYWdlLWNvbnRhaW5lci5tb2R1bGVcIjtcclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL3RhYmxlLXNlYXJjaC1iYXIvdGFibGUtc2VhcmNoLWJhci5jb21wb25lbnRcIjtcclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL3RhYmxlLXNlYXJjaC1iYXIvdGFibGUtc2VhcmNoLWJhci1tb2R1bGVcIjtcclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL2RpcmVjdGl2ZXMvdHJpbS1pbnB1dC5kaXJlY3RpdmVcIjtcclxuZXhwb3J0ICogZnJvbSBcIi4vbGliL2RpcmVjdGl2ZXMvdHJpbS1pbnB1dC5tb2R1bGVcIjtcclxuXHJcbi8vIEV4cG9ydCBhbnRkLWZvcm0gc2VydmljZSBhbmQgdHlwZXNcclxuZXhwb3J0IHsgQW50ZEZvcm1TZXJ2aWNlIH0gZnJvbSBcIi4vbGliL3BhZ2UtcHVibGljL2FudGQtZm9ybVwiO1xyXG5leHBvcnQgdHlwZSB7XHJcbiAgRmllbGRFcnJvck1lc3NhZ2UsXHJcbiAgRmllbGRFcnJvck1lc3NhZ2VzLFxyXG4gIEZpZWxkQ29uZmlnLFxyXG4gIEZvcm1Hcm91cENvbmZpZyxcclxuICBGb3JtQXJyYXlDb25maWcsXHJcbiAgRm9ybU1vZGlmeVR5cGUsXHJcbiAgRm9ybUNvbmZpZyxcclxuICBXYXRjaE9wdGlvbnMsXHJcbiAgQWRkRmllbGRzQ29uZmlnT3B0aW9ucyxcclxuICBBZGRGaWVsZHNDb25maWdSZXN1bHQsXHJcbiAgUmVtb3ZlRmllbGRzQ29uZmlnT3B0aW9ucyxcclxuICBSZW1vdmVGaWVsZHNDb25maWdSZXN1bHQsXHJcbn0gZnJvbSBcIi4vbGliL3BhZ2UtcHVibGljL2FudGQtZm9ybVwiO1xyXG5cclxuLy8gRXhwb3J0IGFycmF5LWZvcm0gc2VydmljZSBhbmQgdHlwZXNcclxuZXhwb3J0IHsgQXJyYXlGb3JtU2VydmljZSB9IGZyb20gXCIuL2xpYi9wYWdlLXB1YmxpYy9hcnJheS1mb3JtXCI7XHJcbmV4cG9ydCB0eXBlIHtcclxuICBWYWxpZGF0aW9uUmVzdWx0LFxyXG4gIEZpZWxkVmFsaWRhdG9yLFxyXG4gIEZpZWxkQ29uZmlnIGFzIEFycmF5RmllbGRDb25maWcsXHJcbiAgQXJyYXlGb3JtUm93LFxyXG4gIEFycmF5Rm9ybUNvbmZpZyxcclxuICBBcnJheUZvcm1TdG9yZUl0ZW0sXHJcbiAgQXJyYXlGb3JtU3RvcmUsXHJcbn0gZnJvbSBcIi4vbGliL3BhZ2UtcHVibGljL2FycmF5LWZvcm1cIjtcclxuXHJcbi8vIEV4cG9ydCBtb2RhbCB3aWR0aCBkZXRlY3RvclxyXG5leHBvcnQgeyBpbml0TW9kYWxXaWR0aERldGVjdG9yIH0gZnJvbSBcIi4vbGliL3V0aWxzL21vZGFsLXdpZHRoLWRldGVjdG9yXCI7XHJcbmV4cG9ydCB7IGluaXRTZWxlY3RXaWR0aERldGVjdG9yIH0gZnJvbSBcIi4vbGliL3V0aWxzL3NlbGVjdC13aWR0aC1kZXRlY3RvclwiO1xyXG4iXX0=
@@ -5,7 +5,7 @@ import { isObservable, lastValueFrom } from 'rxjs';
5
5
  import * as i1 from '@angular/common';
6
6
  import { CommonModule } from '@angular/common';
7
7
  import * as i2$2 from '@angular/forms';
8
- import { NG_VALUE_ACCESSOR, FormsModule, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
8
+ import { NG_VALUE_ACCESSOR, FormsModule, ReactiveFormsModule, UntypedFormArray, UntypedFormGroup } from '@angular/forms';
9
9
  import * as i3 from 'ng-zorro-antd/grid';
10
10
  import { NzGridModule } from 'ng-zorro-antd/grid';
11
11
  import * as i4$1 from 'ng-zorro-antd/form';
@@ -2025,6 +2025,9 @@ class AntdFormService {
2025
2025
  labelAlign = "right";
2026
2026
  labelObservers = {};
2027
2027
  errorMessageStore = {};
2028
+ arrayEditingRows = {};
2029
+ arrayRowSnapshots = {};
2030
+ arrayItemConfigs = {};
2028
2031
  classPrefix = "ant-form-";
2029
2032
  defaultErrorMessages = {
2030
2033
  required: "该字段为必填项",
@@ -2045,6 +2048,40 @@ class AntdFormService {
2045
2048
  "type" in config &&
2046
2049
  config.type === "group");
2047
2050
  }
2051
+ // 判断是否为 FormArray 配置
2052
+ isFormArrayConfig(config) {
2053
+ return (typeof config === "object" &&
2054
+ config !== null &&
2055
+ "type" in config &&
2056
+ config.type === "array");
2057
+ }
2058
+ // 根据路径获取嵌套的 FormArray
2059
+ getNestedFormArray(formGroup, path) {
2060
+ if (!path || path.trim() === "") {
2061
+ return null;
2062
+ }
2063
+ const pathParts = path.split(".").filter((p) => p.trim() !== "");
2064
+ let current = formGroup;
2065
+ for (let i = 0; i < pathParts.length; i++) {
2066
+ if (!current)
2067
+ return null;
2068
+ const part = pathParts[i];
2069
+ if (i === pathParts.length - 1) {
2070
+ const target = current.get
2071
+ ? current.get(part)
2072
+ : null;
2073
+ return target instanceof UntypedFormArray ? target : null;
2074
+ }
2075
+ else {
2076
+ current = current instanceof UntypedFormGroup ? current.get(part) : null;
2077
+ }
2078
+ }
2079
+ return null;
2080
+ }
2081
+ // 根据 itemConfig 创建 FormArray 的单行 FormGroup
2082
+ createArrayRowGroup(itemConfig, disabled) {
2083
+ return this.createNestedFormGroup(itemConfig, disabled);
2084
+ }
2048
2085
  // 递归创建嵌套 FormGroup
2049
2086
  createNestedFormGroup(fields, disabled) {
2050
2087
  const groupConfig = {};
@@ -2053,6 +2090,15 @@ class AntdFormService {
2053
2090
  // 递归创建嵌套的 FormGroup
2054
2091
  groupConfig[key] = this.createNestedFormGroup(fieldConfig.fields, fieldConfig.disabled ?? disabled);
2055
2092
  }
2093
+ else if (this.isFormArrayConfig(fieldConfig)) {
2094
+ // 创建 FormArray
2095
+ const rows = [];
2096
+ const rowCount = fieldConfig.initialRows ?? 0;
2097
+ for (let i = 0; i < rowCount; i++) {
2098
+ rows.push(this.createArrayRowGroup(fieldConfig.itemConfig, fieldConfig.disabled ?? disabled));
2099
+ }
2100
+ groupConfig[key] = this.fb.array(rows, fieldConfig.validators?.() ?? []);
2101
+ }
2056
2102
  else {
2057
2103
  // 创建普通 FormControl
2058
2104
  groupConfig[key] = [
@@ -2080,6 +2126,16 @@ class AntdFormService {
2080
2126
  this.errorMessageStore[name][key] = fieldConfig.errorMessages;
2081
2127
  }
2082
2128
  }
2129
+ else if (this.isFormArrayConfig(fieldConfig)) {
2130
+ // 处理 FormArray
2131
+ const rows = [];
2132
+ const rowCount = fieldConfig.initialRows ?? 0;
2133
+ for (let i = 0; i < rowCount; i++) {
2134
+ rows.push(this.createArrayRowGroup(fieldConfig.itemConfig, fieldConfig.disabled));
2135
+ }
2136
+ groupConfig[key] = this.fb.array(rows, fieldConfig.validators?.() ?? []);
2137
+ this._ensureArrayItemConfig(name, key, fieldConfig.itemConfig);
2138
+ }
2083
2139
  else {
2084
2140
  // 处理普通字段
2085
2141
  groupConfig[key] = [
@@ -2117,18 +2173,30 @@ class AntdFormService {
2117
2173
  // 销毁对应的表单
2118
2174
  destory(names) {
2119
2175
  names.forEach((name) => {
2120
- // 2. 清理错误消息存储
2176
+ // 清理错误消息存储
2121
2177
  if (this.errorMessageStore[name]) {
2122
2178
  delete this.errorMessageStore[name];
2123
2179
  }
2124
- // 3. 清理表单注册标记
2180
+ // 清理表单注册标记
2125
2181
  if (this.formRegisterStore[name]) {
2126
2182
  delete this.formRegisterStore[name];
2127
2183
  }
2128
- // 4. 清理表单组(Angular 会自动处理 FormGroup 的清理)
2184
+ // 清理表单组(Angular 会自动处理 FormGroup 的清理)
2129
2185
  if (this.formStore[name]) {
2130
2186
  delete this.formStore[name];
2131
2187
  }
2188
+ // 清理 FormArray 编辑状态
2189
+ if (this.arrayEditingRows[name]) {
2190
+ delete this.arrayEditingRows[name];
2191
+ }
2192
+ // 清理 FormArray 行快照
2193
+ if (this.arrayRowSnapshots[name]) {
2194
+ delete this.arrayRowSnapshots[name];
2195
+ }
2196
+ // 清理 FormArray itemConfig 缓存
2197
+ if (this.arrayItemConfigs[name]) {
2198
+ delete this.arrayItemConfigs[name];
2199
+ }
2132
2200
  });
2133
2201
  }
2134
2202
  // 根据路径获取嵌套的 FormGroup
@@ -2224,6 +2292,16 @@ class AntdFormService {
2224
2292
  errorMessagesToAdd[errorMessageKey] = fieldConfig.errorMessages;
2225
2293
  }
2226
2294
  }
2295
+ else if (this.isFormArrayConfig(fieldConfig)) {
2296
+ // 创建 FormArray
2297
+ const rows = [];
2298
+ const rowCount = fieldConfig.initialRows ?? 0;
2299
+ for (let i = 0; i < rowCount; i++) {
2300
+ rows.push(this.createArrayRowGroup(fieldConfig.itemConfig, fieldConfig.disabled));
2301
+ }
2302
+ control = this.fb.array(rows, fieldConfig.validators?.() ?? []);
2303
+ this._ensureArrayItemConfig(formName, fieldName, fieldConfig.itemConfig);
2304
+ }
2227
2305
  else {
2228
2306
  // 创建普通 FormControl
2229
2307
  const controlOptions = {
@@ -2347,16 +2425,58 @@ class AntdFormService {
2347
2425
  result.success = result.failed.length === 0;
2348
2426
  return result;
2349
2427
  }
2350
- // 局部赋值
2428
+ // 局部赋值(支持 FormArray 回填:自动重建行结构)
2351
2429
  patchFormValues(name, values, options) {
2352
2430
  const formGroup = this.formStore[name];
2353
2431
  if (!formGroup) {
2354
2432
  console.warn(`[AntdFormService] patchFormValues: form "${name}" not found.`);
2355
2433
  return;
2356
2434
  }
2357
- formGroup.patchValue(values, {
2358
- emitEvent: options?.emitEvent ?? true,
2435
+ const arrayValues = {};
2436
+ const plainValues = {};
2437
+ // 区分 FormArray 字段和普通字段
2438
+ Object.entries(values).forEach(([key, val]) => {
2439
+ const ctrl = formGroup.get(key);
2440
+ if (ctrl instanceof UntypedFormArray && Array.isArray(val)) {
2441
+ arrayValues[key] = val;
2442
+ }
2443
+ else {
2444
+ plainValues[key] = val;
2445
+ }
2446
+ });
2447
+ // 处理 FormArray 回填:清空旧行,按 itemConfig 重建
2448
+ Object.entries(arrayValues).forEach(([key, rows]) => {
2449
+ const formArray = formGroup.get(key);
2450
+ const itemConfig = this.arrayItemConfigs[name]?.[key];
2451
+ // 清空现有行
2452
+ while (formArray.length > 0) {
2453
+ formArray.removeAt(0, { emitEvent: false });
2454
+ }
2455
+ // 清理该数组的编辑状态和快照
2456
+ this._ensureArrayState(name, key);
2457
+ this.arrayEditingRows[name][key] = new Set();
2458
+ this.arrayRowSnapshots[name][key] = new Map();
2459
+ // 按 itemConfig 创建行并回填
2460
+ rows.forEach((rowData) => {
2461
+ const newRow = itemConfig
2462
+ ? this.createArrayRowGroup(itemConfig)
2463
+ : this.fb.group({});
2464
+ if (itemConfig) {
2465
+ newRow.patchValue(rowData, { emitEvent: false });
2466
+ }
2467
+ formArray.push(newRow, { emitEvent: false });
2468
+ });
2469
+ // 统一触发一次 valueChanges
2470
+ if (options?.emitEvent !== false) {
2471
+ formArray.updateValueAndValidity({ emitEvent: true });
2472
+ }
2359
2473
  });
2474
+ // 处理普通字段(含嵌套 FormGroup)
2475
+ if (Object.keys(plainValues).length > 0) {
2476
+ formGroup.patchValue(plainValues, {
2477
+ emitEvent: options?.emitEvent ?? true,
2478
+ });
2479
+ }
2360
2480
  }
2361
2481
  // 表单校验(自动过滤内部字段)
2362
2482
  validateForm(name, options) {
@@ -2385,6 +2505,12 @@ class AntdFormService {
2385
2505
  this.markAllControlsAsDirty(childControl, emitEvent, onlySelf);
2386
2506
  });
2387
2507
  }
2508
+ // 如果是 FormArray,递归处理所有行
2509
+ if (control instanceof UntypedFormArray) {
2510
+ control.controls.forEach((childControl) => {
2511
+ this.markAllControlsAsDirty(childControl, emitEvent, onlySelf);
2512
+ });
2513
+ }
2388
2514
  }
2389
2515
  // ==================== 错误消息相关 ====================
2390
2516
  // 获取字段首条错误提示
@@ -2441,6 +2567,191 @@ class AntdFormService {
2441
2567
  }
2442
2568
  return this.setupValueChangeSubscription(control, handler, options);
2443
2569
  }
2570
+ // ==================== FormArray 操作 ====================
2571
+ // 获取指定路径的 FormArray 实例(供模板 ngFor 使用)
2572
+ getFormArray(formName, arrayPath) {
2573
+ const formGroup = this.formStore[formName];
2574
+ if (!formGroup) {
2575
+ console.warn(`[AntdFormService] getFormArray: form "${formName}" not found.`);
2576
+ return null;
2577
+ }
2578
+ const formArray = this.getNestedFormArray(formGroup, arrayPath);
2579
+ if (!formArray) {
2580
+ console.warn(`[AntdFormService] getFormArray: FormArray at path "${arrayPath}" not found in form "${formName}".`);
2581
+ }
2582
+ return formArray;
2583
+ }
2584
+ // 向 FormArray 新增一行(自动进入编辑状态,无快照)
2585
+ addArrayRow(formName, arrayPath) {
2586
+ const formGroup = this.formStore[formName];
2587
+ if (!formGroup) {
2588
+ console.warn(`[AntdFormService] addArrayRow: form "${formName}" not found.`);
2589
+ return;
2590
+ }
2591
+ const formArray = this.getNestedFormArray(formGroup, arrayPath);
2592
+ if (!formArray) {
2593
+ console.warn(`[AntdFormService] addArrayRow: FormArray at path "${arrayPath}" not found in form "${formName}".`);
2594
+ return;
2595
+ }
2596
+ // 取第一行的结构作为模板,若无行则无法推断 itemConfig
2597
+ // 新行默认为空 FormGroup(调用方应通过 itemConfig 传入具体结构)
2598
+ // 实际行创建通过 _addArrayRowWithConfig 完成,此处保留空行兜底
2599
+ const newRow = this.fb.group({});
2600
+ formArray.push(newRow);
2601
+ const newIndex = formArray.length - 1;
2602
+ this._ensureArrayState(formName, arrayPath);
2603
+ // 新增行无历史快照,cancelRowEdit 时直接删除
2604
+ this.arrayEditingRows[formName][arrayPath].add(newIndex);
2605
+ }
2606
+ // 向 FormArray 新增一行(指定 itemConfig)
2607
+ addArrayRowWithConfig(formName, arrayPath, itemConfig) {
2608
+ const formGroup = this.formStore[formName];
2609
+ if (!formGroup) {
2610
+ console.warn(`[AntdFormService] addArrayRowWithConfig: form "${formName}" not found.`);
2611
+ return;
2612
+ }
2613
+ const formArray = this.getNestedFormArray(formGroup, arrayPath);
2614
+ if (!formArray) {
2615
+ console.warn(`[AntdFormService] addArrayRowWithConfig: FormArray at path "${arrayPath}" not found in form "${formName}".`);
2616
+ return;
2617
+ }
2618
+ const newRow = this.createArrayRowGroup(itemConfig);
2619
+ formArray.push(newRow);
2620
+ const newIndex = formArray.length - 1;
2621
+ this._ensureArrayState(formName, arrayPath);
2622
+ // 新增行无历史快照,cancelRowEdit 时直接删除
2623
+ this.arrayEditingRows[formName][arrayPath].add(newIndex);
2624
+ }
2625
+ // 删除 FormArray 中指定行(同时清理编辑状态和快照,并重对齐索引)
2626
+ removeArrayRow(formName, arrayPath, index) {
2627
+ const formGroup = this.formStore[formName];
2628
+ if (!formGroup) {
2629
+ console.warn(`[AntdFormService] removeArrayRow: form "${formName}" not found.`);
2630
+ return;
2631
+ }
2632
+ const formArray = this.getNestedFormArray(formGroup, arrayPath);
2633
+ if (!formArray) {
2634
+ console.warn(`[AntdFormService] removeArrayRow: FormArray at path "${arrayPath}" not found in form "${formName}".`);
2635
+ return;
2636
+ }
2637
+ if (index < 0 || index >= formArray.length) {
2638
+ console.warn(`[AntdFormService] removeArrayRow: index ${index} out of range.`);
2639
+ return;
2640
+ }
2641
+ formArray.removeAt(index);
2642
+ this._ensureArrayState(formName, arrayPath);
2643
+ // 清理被删行的快照
2644
+ const snapshotMap = this.arrayRowSnapshots[formName][arrayPath];
2645
+ snapshotMap.delete(index);
2646
+ // 重对齐:快照中 > index 的键全部 -1
2647
+ const newSnapshotMap = new Map();
2648
+ snapshotMap.forEach((value, key) => {
2649
+ newSnapshotMap.set(key > index ? key - 1 : key, value);
2650
+ });
2651
+ this.arrayRowSnapshots[formName][arrayPath] = newSnapshotMap;
2652
+ // 重对齐:editingRows 中 > index 的索引全部 -1
2653
+ const editingSet = this.arrayEditingRows[formName][arrayPath];
2654
+ const newEditingSet = new Set();
2655
+ editingSet.forEach((i) => {
2656
+ if (i !== index) {
2657
+ newEditingSet.add(i > index ? i - 1 : i);
2658
+ }
2659
+ });
2660
+ this.arrayEditingRows[formName][arrayPath] = newEditingSet;
2661
+ }
2662
+ // 存储 FormArray 的 itemConfig(用于回填时重建行结构)
2663
+ _ensureArrayItemConfig(formName, arrayPath, itemConfig) {
2664
+ if (!this.arrayItemConfigs[formName]) {
2665
+ this.arrayItemConfigs[formName] = {};
2666
+ }
2667
+ this.arrayItemConfigs[formName][arrayPath] = itemConfig;
2668
+ }
2669
+ // 初始化 arrayEditingRows 和 arrayRowSnapshots 对应路径(若不存在则创建)
2670
+ _ensureArrayState(formName, arrayPath) {
2671
+ if (!this.arrayEditingRows[formName]) {
2672
+ this.arrayEditingRows[formName] = {};
2673
+ }
2674
+ if (!this.arrayEditingRows[formName][arrayPath]) {
2675
+ this.arrayEditingRows[formName][arrayPath] = new Set();
2676
+ }
2677
+ if (!this.arrayRowSnapshots[formName]) {
2678
+ this.arrayRowSnapshots[formName] = {};
2679
+ }
2680
+ if (!this.arrayRowSnapshots[formName][arrayPath]) {
2681
+ this.arrayRowSnapshots[formName][arrayPath] = new Map();
2682
+ }
2683
+ }
2684
+ // ==================== FormArray 行级编辑 ====================
2685
+ // 开启指定行的编辑模式(保存当前值为快照)
2686
+ startRowEdit(formName, arrayPath, index) {
2687
+ const formArray = this.getFormArray(formName, arrayPath);
2688
+ if (!formArray)
2689
+ return;
2690
+ const rowControl = formArray.at(index);
2691
+ if (!rowControl) {
2692
+ console.warn(`[AntdFormService] startRowEdit: row ${index} not found in "${arrayPath}".`);
2693
+ return;
2694
+ }
2695
+ this._ensureArrayState(formName, arrayPath);
2696
+ // 保存快照(仅当快照不存在时,避免重复编辑覆盖原始值)
2697
+ const snapshotMap = this.arrayRowSnapshots[formName][arrayPath];
2698
+ if (!snapshotMap.has(index)) {
2699
+ snapshotMap.set(index, JSON.parse(JSON.stringify(rowControl.getRawValue())));
2700
+ }
2701
+ this.arrayEditingRows[formName][arrayPath].add(index);
2702
+ }
2703
+ // 确认保存指定行(验证通过则清除快照并退出编辑状态,返回行数据;否则返回 false)
2704
+ confirmRowEdit(formName, arrayPath, index) {
2705
+ const formArray = this.getFormArray(formName, arrayPath);
2706
+ if (!formArray)
2707
+ return false;
2708
+ const rowControl = formArray.at(index);
2709
+ if (!rowControl) {
2710
+ console.warn(`[AntdFormService] confirmRowEdit: row ${index} not found in "${arrayPath}".`);
2711
+ return false;
2712
+ }
2713
+ if (rowControl.invalid) {
2714
+ this.markAllControlsAsDirty(rowControl, true, false);
2715
+ return false;
2716
+ }
2717
+ this._ensureArrayState(formName, arrayPath);
2718
+ this.arrayRowSnapshots[formName][arrayPath].delete(index);
2719
+ this.arrayEditingRows[formName][arrayPath].delete(index);
2720
+ return this.excludeInternalFields(rowControl.getRawValue());
2721
+ }
2722
+ // 取消编辑指定行(从快照恢复值;若无快照则说明是新增行,直接删除该行)
2723
+ cancelRowEdit(formName, arrayPath, index) {
2724
+ const formArray = this.getFormArray(formName, arrayPath);
2725
+ if (!formArray)
2726
+ return;
2727
+ this._ensureArrayState(formName, arrayPath);
2728
+ const snapshotMap = this.arrayRowSnapshots[formName][arrayPath];
2729
+ if (snapshotMap.has(index)) {
2730
+ // 已有行:从快照恢复值
2731
+ const snapshot = snapshotMap.get(index);
2732
+ const rowControl = formArray.at(index);
2733
+ if (rowControl) {
2734
+ rowControl.patchValue(snapshot);
2735
+ rowControl.markAsPristine();
2736
+ rowControl.markAsUntouched();
2737
+ }
2738
+ snapshotMap.delete(index);
2739
+ this.arrayEditingRows[formName][arrayPath].delete(index);
2740
+ }
2741
+ else {
2742
+ // 新增行:直接删除
2743
+ this.removeArrayRow(formName, arrayPath, index);
2744
+ }
2745
+ }
2746
+ // 查询指定行是否处于编辑状态
2747
+ isRowEditing(formName, arrayPath, index) {
2748
+ return (this.arrayEditingRows[formName]?.[arrayPath]?.has(index) ?? false);
2749
+ }
2750
+ // 获取所有正在编辑的行索引列表
2751
+ getEditingRowIndices(formName, arrayPath) {
2752
+ const editingSet = this.arrayEditingRows[formName]?.[arrayPath];
2753
+ return editingSet ? Array.from(editingSet).sort((a, b) => a - b) : [];
2754
+ }
2444
2755
  // ==================== 工具方法 ====================
2445
2756
  // 获取表单类名
2446
2757
  getFormClassName(name) {