From 20680d3bc708cc78c9799bf3f3b3f9caca02a48c Mon Sep 17 00:00:00 2001
From: chenos <chenlinxh@gmail.com>
Date: Thu, 7 Apr 2022 17:34:29 +0800
Subject: [PATCH] feat: improve filter component

---
 .../antd/filter/DynamicComponent.tsx          |   6 +
 .../antd/filter/Filter.Action.Designer.tsx    |  75 ++++++----
 .../antd/filter/FilterAction.tsx              |   7 +-
 .../antd/filter/FilterGroup.tsx               | 129 +++++++++---------
 .../schema-component/antd/filter/context.ts   |   1 +
 .../antd/filter/useFilterActionProps.ts       |  43 +++++-
 .../schema-component/antd/filter/useValues.ts |   7 +-
 .../src/schema-settings/SchemaSettings.tsx    |  12 +-
 8 files changed, 183 insertions(+), 97 deletions(-)

diff --git a/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx b/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx
index 61e00673b..d8a6cad3f 100644
--- a/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx
+++ b/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx
@@ -1,5 +1,6 @@
 import { createForm, onFieldValueChange } from '@formily/core';
 import { FieldContext, FormContext } from '@formily/react';
+import { merge } from '@formily/shared';
 import React, { useContext, useMemo } from 'react';
 import { SchemaComponent } from '../../core';
 import { useComponent } from '../../hooks';
@@ -27,6 +28,11 @@ export const DynamicComponent = (props) => {
           schema={{
             'x-component': 'Input',
             ...props.schema,
+            'x-component-props': merge(props?.schema?.['x-component-props'] || {}, {
+              style: {
+                minWidth: 150,
+              },
+            }),
             name: 'value',
             'x-read-pretty': false,
             'x-validator': undefined,
diff --git a/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx b/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx
index bfe4b4d08..912f2f8e5 100644
--- a/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx
+++ b/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx
@@ -1,16 +1,66 @@
 import { ISchema, useField, useFieldSchema } from '@formily/react';
 import React from 'react';
 import { useDesignable } from '../..';
+import { useCollection, useCollectionManager } from '../../../collection-manager';
 import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 
+export const useFilterableFields = (collectionName: string) => {
+  const { getCollectionFields, getInterface } = useCollectionManager();
+  const fields = getCollectionFields(collectionName);
+  return fields?.filter?.((field) => {
+    if (!field.interface) {
+      return false;
+    }
+    const fieldInterface = getInterface(field.interface);
+    if (!fieldInterface.filterable) {
+      return false;
+    }
+    return true;
+  });
+};
+
 export const FilterActionDesigner = (props) => {
-  const initialValue = {};
   const field = useField();
   const fieldSchema = useFieldSchema();
   const { dn } = useDesignable();
-  const isPopupAction = ['create', 'update', 'view'].includes(fieldSchema['x-action'] || '');
+  const { name } = useCollection();
+  const fields = useFilterableFields(name);
+  const fieldNames = fieldSchema?.['x-component-props']?.fieldNames || [];
   return (
     <GeneralSchemaDesigner {...props}>
+      <SchemaSettings.ItemGroup title={'可筛选字段'}>
+        {fields.map((field) => {
+          const checked = fieldNames.includes(field.name);
+          return (
+            <SchemaSettings.SwitchItem
+              checked={checked}
+              title={field?.uiSchema?.title}
+              onChange={(value) => {
+                fieldSchema['x-component-props'] = fieldSchema?.['x-component-props'] || {};
+                const fieldNames = fieldSchema?.['x-component-props']?.fieldNames || [];
+                console.log('fieldNames.checked', value)
+                if (value) {
+                  fieldNames.push(field.name);
+                } else {
+                  const index = fieldNames.indexOf(field.name);
+                  fieldNames.splice(index, 1);
+                }
+                fieldSchema['x-component-props'].fieldNames = fieldNames;
+                dn.emit('patch', {
+                  schema: {
+                    ['x-uid']: fieldSchema['x-uid'],
+                    'x-component-props': {
+                      ...fieldSchema['x-component-props'],
+                    },
+                  },
+                });
+                dn.refresh();
+              }}
+            />
+          );
+        })}
+      </SchemaSettings.ItemGroup>
+      <SchemaSettings.Divider />
       <SchemaSettings.ModalItem
         title={'编辑'}
         schema={
@@ -57,27 +107,6 @@ export const FilterActionDesigner = (props) => {
           }
         }}
       />
-      {isPopupAction && (
-        <SchemaSettings.SelectItem
-          title={'打开方式'}
-          options={[
-            { label: '抽屉', value: 'drawer' },
-            { label: '对话框', value: 'modal' },
-          ]}
-          value={field.componentProps.openMode}
-          onChange={(value) => {
-            field.componentProps.openMode = value;
-            fieldSchema['x-component-props']['openMode'] = value;
-            dn.emit('patch', {
-              schema: {
-                'x-uid': fieldSchema['x-uid'],
-                'x-component-props': fieldSchema['x-component-props'],
-              },
-            });
-            dn.refresh();
-          }}
-        />
-      )}
       <SchemaSettings.Divider />
       <SchemaSettings.Remove
         removeParentsIfNoChildren
diff --git a/packages/client/src/schema-component/antd/filter/FilterAction.tsx b/packages/client/src/schema-component/antd/filter/FilterAction.tsx
index c56e4f820..2269d6528 100644
--- a/packages/client/src/schema-component/antd/filter/FilterAction.tsx
+++ b/packages/client/src/schema-component/antd/filter/FilterAction.tsx
@@ -17,7 +17,7 @@ export const FilterAction = observer((props: any) => {
   const [visible, setVisible] = useState(false);
   const { designable, dn } = useDesignable();
   const fieldSchema = useFieldSchema();
-  const form = useMemo<Form>(() => props.form || createForm(), [visible]);
+  const form = useMemo<Form>(() => props.form || createForm(), []);
   const { options, onSubmit, onReset, ...others } = useProps(props);
   return (
     <FilterActionContext.Provider value={{ field, fieldSchema, designable, dn }}>
@@ -58,7 +58,7 @@ export const FilterAction = observer((props: any) => {
                   <Button
                     onClick={async () => {
                       await form.reset();
-                      onReset?.();
+                      onReset?.(form.values);
                       field.title = t('Filter');
                       setVisible(false);
                     }}
@@ -72,9 +72,6 @@ export const FilterAction = observer((props: any) => {
                       e.preventDefault();
                       e.stopPropagation();
                       onSubmit?.(form.values);
-                      const items = form.values?.filter?.$and || form.values?.filter?.$or;
-                      console.log('form.values.filter', form.values, items);
-                      field.title = t('{{count}} filter items', { count: items?.length || 0 });
                       setVisible(false);
                     }}
                   >
diff --git a/packages/client/src/schema-component/antd/filter/FilterGroup.tsx b/packages/client/src/schema-component/antd/filter/FilterGroup.tsx
index b2ded2510..bc053bf9f 100644
--- a/packages/client/src/schema-component/antd/filter/FilterGroup.tsx
+++ b/packages/client/src/schema-component/antd/filter/FilterGroup.tsx
@@ -4,7 +4,7 @@ import { ArrayField, connect, useField } from '@formily/react';
 import { Select, Space } from 'antd';
 import React, { useContext } from 'react';
 import { Trans, useTranslation } from 'react-i18next';
-import { RemoveConditionContext } from './context';
+import { FilterLogicContext, RemoveConditionContext } from './context';
 import { FilterItems } from './FilterItems';
 
 export const FilterGroup = connect((props) => {
@@ -16,73 +16,76 @@ export const FilterGroup = connect((props) => {
   const setLogic = (value) => {
     const obj = field.value || {};
     field.value = {
-      [value]: obj[logic] || [],
+      [value]: [...(obj[logic] || [])],
     };
   };
+  console.log('logic', logic);
   return (
-    <div
-      style={{
-        position: 'relative',
-        border: '1px dashed #dedede',
-        padding: 14,
-        marginBottom: 8,
-      }}
-    >
-      {remove && (
-        <CloseCircleOutlined
-          style={{
-            position: 'absolute',
-            right: 10,
-          }}
-          onClick={() => remove()}
-        />
-      )}
-      <div style={{ marginBottom: 8 }}>
-        <Trans>
-          {'Meet '}
-          <Select
-            value={logic}
-            onChange={(value) => {
-              setLogic(value);
+    <FilterLogicContext.Provider value={logic}>
+      <div
+        style={{
+          position: 'relative',
+          border: '1px dashed #dedede',
+          padding: 14,
+          marginBottom: 8,
+        }}
+      >
+        {remove && (
+          <CloseCircleOutlined
+            style={{
+              position: 'absolute',
+              right: 10,
+            }}
+            onClick={() => remove()}
+          />
+        )}
+        <div style={{ marginBottom: 8 }}>
+          <Trans>
+            {'Meet '}
+            <Select
+              value={logic}
+              onChange={(value) => {
+                setLogic(value);
+              }}
+            >
+              <Select.Option value={'$and'}>All</Select.Option>
+              <Select.Option value={'$or'}>Any</Select.Option>
+            </Select>
+            {' conditions in the group'}
+          </Trans>
+        </div>
+        <div>
+          <ArrayField name={logic} component={[FilterItems]} />
+        </div>
+        <Space size={16} style={{ marginTop: 8, marginBottom: 8 }}>
+          <a
+            onClick={() => {
+              const value = field.value || {};
+              const items = value[logic] || [];
+              items.push({});
+              field.value = {
+                [logic]: items,
+              };
             }}
           >
-            <Select.Option value={'$and'}>All</Select.Option>
-            <Select.Option value={'$or'}>Any</Select.Option>
-          </Select>
-          {' conditions in the group'}
-        </Trans>
+            {t('Add condition')}
+          </a>
+          <a
+            onClick={() => {
+              const value = field.value || {};
+              const items = value[logic] || [];
+              items.push({
+                $and: [{}],
+              });
+              field.value = {
+                [logic]: items,
+              };
+            }}
+          >
+            {t('Add condition group')}
+          </a>
+        </Space>
       </div>
-      <div>
-        <ArrayField name={logic} component={[FilterItems]} />
-      </div>
-      <Space size={16} style={{ marginTop: 8, marginBottom: 8 }}>
-        <a
-          onClick={() => {
-            const value = field.value || {};
-            const items = value[logic] || [];
-            items.push({});
-            field.value = {
-              [logic]: items,
-            };
-          }}
-        >
-          {t('Add condition')}
-        </a>
-        <a
-          onClick={() => {
-            const value = field.value || {};
-            const items = value[logic] || [];
-            items.push({
-              $and: [{}],
-            });
-            field.value = {
-              [logic]: items,
-            };
-          }}
-        >
-          {t('Add condition group')}
-        </a>
-      </Space>
-    </div>
+    </FilterLogicContext.Provider>
   );
 });
diff --git a/packages/client/src/schema-component/antd/filter/context.ts b/packages/client/src/schema-component/antd/filter/context.ts
index 87f36caee..ccb5733f1 100644
--- a/packages/client/src/schema-component/antd/filter/context.ts
+++ b/packages/client/src/schema-component/antd/filter/context.ts
@@ -11,3 +11,4 @@ export interface FilterContextProps {
 
 export const RemoveConditionContext = createContext(null);
 export const FilterContext = createContext<FilterContextProps>(null);
+export const FilterLogicContext = createContext(null);
\ No newline at end of file
diff --git a/packages/client/src/schema-component/antd/filter/useFilterActionProps.ts b/packages/client/src/schema-component/antd/filter/useFilterActionProps.ts
index 02f181a8f..04870a70c 100644
--- a/packages/client/src/schema-component/antd/filter/useFilterActionProps.ts
+++ b/packages/client/src/schema-component/antd/filter/useFilterActionProps.ts
@@ -1,10 +1,19 @@
+import { Field } from '@formily/core';
+import { useField, useFieldSchema } from '@formily/react';
+import flat from 'flat';
+import { useTranslation } from 'react-i18next';
 import { useBlockRequestContext } from '../../../block-provider';
 import { useCollection, useCollectionManager } from '../../../collection-manager';
 
 export const useFilterOptions = (collectionName: string) => {
+  const fieldSchema = useFieldSchema();
+  const fieldNames = fieldSchema?.['x-component-props']?.fieldNames || [];
   const { getCollectionFields, getInterface } = useCollectionManager();
   const fields = getCollectionFields(collectionName);
   const field2option = (field, nochildren) => {
+    if (fieldNames.length && !nochildren && !fieldNames.includes(field.name)) {
+      return;
+    }
     if (!field.interface) {
       return;
     }
@@ -46,15 +55,45 @@ export const useFilterOptions = (collectionName: string) => {
   return getOptions(fields);
 };
 
+const isEmpty = (obj) => {
+  return obj && Object.keys(obj).length === 0 && Object.getPrototypeOf(obj) === Object.prototype;
+};
+
+const removeNullCondition = (filter) => {
+  const items = flat(filter);
+  console.log('filter', items);
+  const values = {};
+  for (const key in items) {
+    const value = items[key];
+    if (value !== null && !isEmpty(value)) {
+      values[key] = value;
+    }
+  }
+  return flat.unflatten(values);
+};
+
 export const useFilterActionProps = () => {
   const { name } = useCollection();
   const options = useFilterOptions(name);
   const { service } = useBlockRequestContext();
+  const field = useField<Field>();
+  const { t } = useTranslation();
   return {
     options,
-    onSubmit({ filter }) {
-      console.log('filter', filter)
+    onSubmit(values) {
+      const filter = removeNullCondition(values?.filter);
       service.run({ ...service.params?.[0], filter });
+      const items = filter?.$and || filter?.$or;
+      if (items?.length) {
+        field.title = t('{{count}} filter items', { count: items?.length || 0 });
+      } else {
+        field.title = t('Filter');
+      }
+    },
+    onReset(values) {
+      const filter = removeNullCondition(values?.filter);
+      service.run({ ...service.params?.[0], filter });
+      field.title = t('Filter');
     },
   };
 };
diff --git a/packages/client/src/schema-component/antd/filter/useValues.ts b/packages/client/src/schema-component/antd/filter/useValues.ts
index 9a0cee894..2ddf9d4f5 100644
--- a/packages/client/src/schema-component/antd/filter/useValues.ts
+++ b/packages/client/src/schema-component/antd/filter/useValues.ts
@@ -3,7 +3,7 @@ import { merge } from '@formily/shared';
 import flat from 'flat';
 import get from 'lodash/get';
 import { useContext, useEffect } from 'react';
-import { FilterContext } from './context';
+import { FilterContext, FilterLogicContext } from './context';
 
 // import { useValues } from './useValues';
 const findOption = (dataIndex = [], options) => {
@@ -21,6 +21,7 @@ const findOption = (dataIndex = [], options) => {
 
 export const useValues = () => {
   const field = useField<any>();
+  const logic = useContext(FilterLogicContext);
   const { options } = useContext(FilterContext);
   const data2value = () => {
     field.value = flat.unflatten({
@@ -55,7 +56,7 @@ export const useValues = () => {
   };
   useEffect(() => {
     value2data();
-  }, []);
+  }, [logic]);
   return {
     fields: options,
     ...field.data,
@@ -75,7 +76,7 @@ export const useValues = () => {
       const operator = field.data?.operators?.find?.((item) => item.value === operatorValue);
       field.data.operator = operator;
       field.data.schema = merge(field.data.schema, operator.schema);
-      field.data.value = null;
+      field.data.value = operator.noValue ? true : null;
       data2value();
       console.log('setOperator', field.data);
     },
diff --git a/packages/client/src/schema-settings/SchemaSettings.tsx b/packages/client/src/schema-settings/SchemaSettings.tsx
index 86c287d07..cc5dd51b4 100644
--- a/packages/client/src/schema-settings/SchemaSettings.tsx
+++ b/packages/client/src/schema-settings/SchemaSettings.tsx
@@ -200,6 +200,10 @@ SchemaSettings.Item = (props) => {
   );
 };
 
+SchemaSettings.ItemGroup = (props) => {
+  return <Menu.ItemGroup {...props} />;
+};
+
 SchemaSettings.SubMenu = (props) => {
   return <Menu.SubMenu {...props} />;
 };
@@ -245,7 +249,13 @@ SchemaSettings.SelectItem = (props) => {
     <SchemaSettings.Item {...others}>
       <div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
         {title}
-        <Select bordered={false} defaultValue={value} onChange={onChange} options={options} style={{ textAlign: 'right', minWidth: 100 }} />
+        <Select
+          bordered={false}
+          defaultValue={value}
+          onChange={onChange}
+          options={options}
+          style={{ textAlign: 'right', minWidth: 100 }}
+        />
       </div>
     </SchemaSettings.Item>
   );