From 41d3b12ed7053208bcf432147d723fee18571059 Mon Sep 17 00:00:00 2001
From: liaoxuezhi <2betop.cn@gmail.com>
Date: Wed, 31 Jan 2024 16:09:30 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E9=80=89=E9=A1=B9=E7=B1=BB=E7=9A=84?=
 =?UTF-8?q?=E7=BB=84=E4=BB=B6=20autoFill=20=E6=94=AF=E6=8C=81=E9=85=8D?=
 =?UTF-8?q?=E7=BD=AE=20initAutoFill=20=E5=B1=9E=E6=80=A7=20Close:=20#9391?=
 =?UTF-8?q?=20(#9547)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 docs/zh-CN/components/form/options.md         |  10 +
 packages/amis-core/src/renderers/Options.tsx  |  47 +++-
 .../renderers/Form/autoFill.test.tsx          | 203 ++++++++++++++++++
 .../__tests__/renderers/Form/options.test.tsx |   1 +
 4 files changed, 251 insertions(+), 10 deletions(-)

diff --git a/docs/zh-CN/components/form/options.md b/docs/zh-CN/components/form/options.md
index 13e86c266e8..7c224d5713e 100755
--- a/docs/zh-CN/components/form/options.md
+++ b/docs/zh-CN/components/form/options.md
@@ -1558,6 +1558,16 @@ order: 2
 }
 ```
 
+**初始不填充**
+
+从 3.1.0 版本开始,表单初始化时,选项有值时也会执行「自动填充」逻辑,从版本 6.1.0 版本开始 可以通过 `initAutoFill` 配置成 `false` 来关闭。
+
+`initAutoFill` 有三种值分别如下,默认为 `fillIfNotSet`。
+
+- `fillIfNotSet` 如果目标值不存在则填充,如果目标值存在则不填充。
+- `true` 总是填充,如果目标值存在则覆盖。
+- `false` 总是不填充,如果目标值存在则不填充。
+
 ## 控制选项高度
 
 > 1.10.0 及以上版本
diff --git a/packages/amis-core/src/renderers/Options.tsx b/packages/amis-core/src/renderers/Options.tsx
index a216fe0a73d..5f34211a5fd 100644
--- a/packages/amis-core/src/renderers/Options.tsx
+++ b/packages/amis-core/src/renderers/Options.tsx
@@ -54,7 +54,7 @@ import isPlainObject from 'lodash/isPlainObject';
 import {normalizeOptions} from '../utils/normalizeOptions';
 import {optionValueCompare} from '../utils/optionValueCompare';
 import type {Option} from '../types';
-import {resolveEventData} from '../utils';
+import {deleteVariable, resolveEventData} from '../utils';
 
 export {Option};
 
@@ -203,6 +203,12 @@ export interface FormOptionsControl extends FormBaseControl {
   autoFill?: {
     [propName: string]: string;
   };
+
+  /**
+   * @default fillIfNotSet
+   * 初始化时是否把其他字段同步到表单内部。
+   */
+  initAutoFill?: boolean | 'fillIfNotSet';
 }
 
 export interface OptionsBasicConfig extends FormItemBasicConfig {
@@ -303,6 +309,7 @@ export function registerOptionsControl(config: OptionsConfig) {
       placeholder: 'Select.placeholder',
       resetValue: '',
       deleteConfirmText: 'deleteConfirm',
+      initAutoFill: 'fillIfNotSet',
       ...Control.defaultProps
     };
     static propsList: any = (Control as any).propsList
@@ -314,6 +321,7 @@ export function registerOptionsControl(config: OptionsConfig) {
 
     input: any;
     mounted = false;
+    initedFilled = false;
 
     constructor(props: OptionsProps) {
       super(props);
@@ -356,16 +364,27 @@ export function registerOptionsControl(config: OptionsConfig) {
               JSON.stringify(formItem.getSelectedOptions(formItem.tmpValue)),
             () =>
               this.mounted &&
+              this.initedFilled &&
               this.syncAutoFill(formItem.getSelectedOptions(formItem.tmpValue))
           )
         );
 
-        if (
-          options &&
-          formItem.tmpValue &&
-          formItem.getSelectedOptions(formItem.tmpValue).length
-        ) {
-          this.syncAutoFill(formItem.getSelectedOptions(formItem.tmpValue));
+        if (formInited || !addHook) {
+          this.initedFilled = true;
+          this.props.initAutoFill !== false &&
+            this.syncAutoFill(
+              formItem.getSelectedOptions(formItem.tmpValue),
+              this.props.initAutoFill === 'fillIfNotSet'
+            );
+        } else if (addHook) {
+          addHook(() => {
+            this.initedFilled = true;
+            this.props.initAutoFill !== false &&
+              this.syncAutoFill(
+                formItem.getSelectedOptions(formItem.tmpValue),
+                this.props.initAutoFill === 'fillIfNotSet'
+              );
+          }, 'init');
         }
 
         // 默认全选。这里会和默认值\回填值逻辑冲突,所以如果有配置source则不执行默认全选
@@ -530,7 +549,7 @@ export function registerOptionsControl(config: OptionsConfig) {
       }
     }
 
-    syncAutoFill(selectedOptions: Array<any>) {
+    syncAutoFill(selectedOptions: Array<any>, skipIfExits = false) {
       const {autoFill, multiple, onBulkChange, data} = this.props;
       const formItem = this.props.formItem as IFormItemStore;
       // 参照录入|自动填充
@@ -579,13 +598,21 @@ export function registerOptionsControl(config: OptionsConfig) {
 
         Object.keys(autoFill).forEach(key => {
           const keys = keyToPath(key);
+          let value = getVariable(toSync, key);
+
+          if (skipIfExits) {
+            const originValue = getVariable(data, key);
+            if (typeof originValue !== 'undefined') {
+              value = originValue;
+            }
+          }
+
+          setVariable(result, key, value);
 
           // 如果左边的 key 是一个路径
           // 这里不希望直接把原始对象都给覆盖没了
           // 而是保留原始的对象,只修改指定的属性
           if (keys.length > 1 && isPlainObject(tmpData[keys[0]])) {
-            const value = getVariable(toSync, key);
-
             // 存在情况:依次更新同一子路径的多个key,eg: a.b.c1 和 a.b.c2,所以需要同步更新data
             setVariable(tmpData, key, value);
             result[keys[0]] = tmpData[keys[0]];
diff --git a/packages/amis/__tests__/renderers/Form/autoFill.test.tsx b/packages/amis/__tests__/renderers/Form/autoFill.test.tsx
index d57e744cb8c..c9358fb4894 100644
--- a/packages/amis/__tests__/renderers/Form/autoFill.test.tsx
+++ b/packages/amis/__tests__/renderers/Form/autoFill.test.tsx
@@ -399,3 +399,206 @@ test('Form:options:autoFill:validation', async () => {
   expect(screen.queryByText(validationMsg1)).not.toBeInTheDocument();
   expect(screen.queryByText(validationMsg2)).not.toBeInTheDocument();
 });
+
+test('6. AutoFill initAutoFill fillIfNotSet', async () => {
+  const onSubmit = jest.fn();
+  const {debug, container, getByText, findByText} = render(
+    amisRender(
+      {
+        type: 'page',
+        body: [
+          {
+            type: 'form',
+            title: 'The form',
+            controls: [
+              {
+                type: 'hidden',
+                name: 'aId',
+                value: 123
+              },
+              {
+                type: 'hidden',
+                name: 'bId'
+              },
+              {
+                type: 'radios',
+                name: 'a',
+                autoFill: {
+                  aValue: '${value}',
+                  aLabel: '${label}',
+                  aId: '${id}',
+                  bId: '${id}'
+                },
+                value: 'a',
+                options: [
+                  {
+                    label: 'OptionA',
+                    value: 'a',
+                    id: 233
+                  },
+                  {
+                    label: 'OptionB',
+                    value: 'b'
+                  }
+                ]
+              }
+            ],
+            submitText: 'Submit'
+          }
+        ]
+      },
+      {
+        onSubmit: onSubmit
+      },
+      makeEnv()
+    )
+  );
+
+  await wait(200);
+  fireEvent.click(getByText(/Submit/));
+  await wait(200);
+
+  expect(onSubmit).toBeCalledTimes(1);
+  expect(onSubmit.mock.calls[0][0]).toMatchObject({
+    aId: 123,
+    a: 'a',
+    aValue: 'a',
+    aLabel: 'OptionA',
+    bId: 233
+  });
+});
+
+test('7. AutoFill initAutoFill false', async () => {
+  const onSubmit = jest.fn();
+  const {debug, container, getByText, findByText} = render(
+    amisRender(
+      {
+        type: 'page',
+        body: [
+          {
+            type: 'form',
+            title: 'The form',
+            controls: [
+              {
+                type: 'hidden',
+                name: 'aId',
+                value: 123
+              },
+              {
+                type: 'hidden',
+                name: 'bId'
+              },
+              {
+                type: 'radios',
+                name: 'a',
+                autoFill: {
+                  aValue: '${value}',
+                  aLabel: '${label}',
+                  aId: '${id}',
+                  bId: '${id}'
+                },
+                initAutoFill: false,
+                value: 'a',
+                options: [
+                  {
+                    label: 'OptionA',
+                    value: 'a',
+                    id: 233
+                  },
+                  {
+                    label: 'OptionB',
+                    value: 'b'
+                  }
+                ]
+              }
+            ],
+            submitText: 'Submit'
+          }
+        ]
+      },
+      {
+        onSubmit: onSubmit
+      },
+      makeEnv()
+    )
+  );
+
+  await wait(200);
+  fireEvent.click(getByText(/Submit/));
+  await wait(200);
+
+  expect(onSubmit).toBeCalledTimes(1);
+  expect(onSubmit.mock.calls[0][0]).toMatchObject({
+    aId: 123,
+    a: 'a'
+  });
+});
+
+test('8. AutoFill initAutoFill true', async () => {
+  const onSubmit = jest.fn();
+  const {debug, container, getByText, findByText} = render(
+    amisRender(
+      {
+        type: 'page',
+        body: [
+          {
+            type: 'form',
+            title: 'The form',
+            controls: [
+              {
+                type: 'hidden',
+                name: 'aId',
+                value: 123
+              },
+              {
+                type: 'hidden',
+                name: 'bId'
+              },
+              {
+                type: 'radios',
+                name: 'a',
+                autoFill: {
+                  aValue: '${value}',
+                  aLabel: '${label}',
+                  aId: '${id}',
+                  bId: '${id}'
+                },
+                initAutoFill: true,
+                value: 'a',
+                options: [
+                  {
+                    label: 'OptionA',
+                    value: 'a',
+                    id: 233
+                  },
+                  {
+                    label: 'OptionB',
+                    value: 'b'
+                  }
+                ]
+              }
+            ],
+            submitText: 'Submit'
+          }
+        ]
+      },
+      {
+        onSubmit: onSubmit
+      },
+      makeEnv()
+    )
+  );
+
+  await wait(200);
+  fireEvent.click(getByText(/Submit/));
+  await wait(200);
+
+  expect(onSubmit).toBeCalledTimes(1);
+  expect(onSubmit.mock.calls[0][0]).toMatchObject({
+    aId: 233,
+    a: 'a',
+    aValue: 'a',
+    aLabel: 'OptionA',
+    bId: 233
+  });
+});
diff --git a/packages/amis/__tests__/renderers/Form/options.test.tsx b/packages/amis/__tests__/renderers/Form/options.test.tsx
index d82e9c3485c..fdbef14ad0a 100644
--- a/packages/amis/__tests__/renderers/Form/options.test.tsx
+++ b/packages/amis/__tests__/renderers/Form/options.test.tsx
@@ -283,6 +283,7 @@ test('options:autoFill-merge', async () => {
       makeEnv({})
     )
   );
+  await wait(200);
 
   fireEvent.click(getByText('选项1'));
   await wait(300);