Jelajahi Sumber

feat(search): searchComponent

Lind 3 tahun lalu
induk
melakukan
7ed970da43

+ 0 - 7
config/routes.ts

@@ -51,13 +51,6 @@
         component: './system/Permission',
         component: './system/Permission',
       },
       },
       {
       {
-        path: '/system/org',
-        name: 'org',
-        icon: 'smile',
-        access: 'organization',
-        component: './system/Org',
-      },
-      {
         path: '/system/open-api',
         path: '/system/open-api',
         name: 'open-api',
         name: 'open-api',
         icon: 'smile',
         icon: 'smile',

+ 1 - 2
package.json

@@ -62,7 +62,6 @@
     "@ant-design/pro-descriptions": "^1.6.8",
     "@ant-design/pro-descriptions": "^1.6.8",
     "@ant-design/pro-form": "^1.18.3",
     "@ant-design/pro-form": "^1.18.3",
     "@ant-design/pro-layout": "^6.27.2",
     "@ant-design/pro-layout": "^6.27.2",
-    "@dabeng/react-orgchart": "^1.0.0",
     "@formily/antd": "2.0.0-rc.17",
     "@formily/antd": "2.0.0-rc.17",
     "@formily/core": "2.0.0-rc.17",
     "@formily/core": "2.0.0-rc.17",
     "@formily/json-schema": "2.0.0-rc.17",
     "@formily/json-schema": "2.0.0-rc.17",
@@ -71,7 +70,7 @@
     "@formily/reactive-react": "2.0.0-rc.17",
     "@formily/reactive-react": "2.0.0-rc.17",
     "@formily/shared": "2.0.0-rc.17",
     "@formily/shared": "2.0.0-rc.17",
     "@jetlinks/pro-list": "^1.10.8",
     "@jetlinks/pro-list": "^1.10.8",
-    "@jetlinks/pro-table": "^2.43.8",
+    "@jetlinks/pro-table": "^2.63.8",
     "@umijs/route-utils": "^1.0.36",
     "@umijs/route-utils": "^1.0.36",
     "ahooks": "^2.10.9",
     "ahooks": "^2.10.9",
     "antd": "^4.17.0-alpha.9",
     "antd": "^4.17.0-alpha.9",

+ 22 - 3
src/components/BaseCrud/index.tsx

@@ -1,5 +1,5 @@
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import { Button, Dropdown } from 'antd';
+import { Button, Card, Divider, Dropdown } from 'antd';
 import ProTable from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import type { ProColumns, ActionType, RequestData } from '@jetlinks/pro-table';
 import type { ProColumns, ActionType, RequestData } from '@jetlinks/pro-table';
 
 
@@ -12,8 +12,11 @@ import { CurdModel } from '@/components/BaseCrud/model';
 import type { ISchemaFieldProps } from '@formily/react/lib/types';
 import type { ISchemaFieldProps } from '@formily/react/lib/types';
 import type { ModalProps } from 'antd/lib/modal/Modal';
 import type { ModalProps } from 'antd/lib/modal/Modal';
 import type { TablePaginationConfig } from 'antd/lib/table/interface';
 import type { TablePaginationConfig } from 'antd/lib/table/interface';
-import type { SearchConfig } from '@jetlinks/pro-table/lib/components/Form/FormRender';
 import type { Form } from '@formily/core';
 import type { Form } from '@formily/core';
+import SearchComponent from '@/components/SearchComponent';
+import { useRef, useState } from 'react';
+import type { ProFormInstance } from '@ant-design/pro-form';
+import type { SearchConfig } from '@ant-design/pro-form/lib/components/Submitter';
 
 
 export type Option = {
 export type Option = {
   model: 'edit' | 'preview' | 'add';
   model: 'edit' | 'preview' | 'add';
@@ -41,11 +44,13 @@ export type Props<T> = {
   search?: false | SearchConfig;
   search?: false | SearchConfig;
   formEffect?: () => void; // 与form参数 只有一个生效
   formEffect?: () => void; // 与form参数 只有一个生效
   form?: Form;
   form?: Form;
+  /** @name 用于存储搜索历史记录的标记*/
+  moduleName: string; //
 };
 };
 
 
 const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
 const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
   const intl = useIntl();
   const intl = useIntl();
-
+  const ref = useRef<ProFormInstance>();
   const {
   const {
     columns,
     columns,
     service,
     service,
@@ -62,11 +67,25 @@ const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
     search,
     search,
     formEffect,
     formEffect,
     form,
     form,
+    moduleName,
   } = props;
   } = props;
 
 
+  const [param, setParam] = useState({});
   return (
   return (
     <>
     <>
+      <Card>
+        <SearchComponent<T>
+          field={columns}
+          onSearch={async (data) => {
+            setParam({ terms: data });
+          }}
+          target={moduleName}
+        />
+      </Card>
+      <Divider />
       <ProTable<T>
       <ProTable<T>
+        params={param}
+        formRef={ref}
         columns={columns}
         columns={columns}
         actionRef={actionRef}
         actionRef={actionRef}
         options={{ fullScreen: true }}
         options={{ fullScreen: true }}

+ 9 - 1
src/components/SearchComponent/GroupNameControl.tsx

@@ -1,7 +1,13 @@
 import { ArrayItems } from '@formily/antd';
 import { ArrayItems } from '@formily/antd';
 import { Select } from 'antd';
 import { Select } from 'antd';
 
 
-const GroupNameControl = (props: { name: string }) => {
+interface Props {
+  name?: string;
+  value: string;
+  onChange: () => void;
+}
+
+const GroupNameControl = (props: Props) => {
   const index = ArrayItems.useIndex!();
   const index = ArrayItems.useIndex!();
   return (
   return (
     <>
     <>
@@ -9,6 +15,8 @@ const GroupNameControl = (props: { name: string }) => {
         <div style={{ textAlign: 'center', fontWeight: 600 }}>{props?.name || '第一组'}</div>
         <div style={{ textAlign: 'center', fontWeight: 600 }}>{props?.name || '第一组'}</div>
       ) : (
       ) : (
         <Select
         <Select
+          onChange={props.onChange}
+          value={props.value}
           options={[
           options={[
             { label: '并且', value: 'and' },
             { label: '并且', value: 'and' },
             { label: '或者', value: 'or' },
             { label: '或者', value: 'or' },

+ 91 - 65
src/components/SearchComponent/index.tsx

@@ -14,12 +14,14 @@ import {
 import { createForm } from '@formily/core';
 import { createForm } from '@formily/core';
 import GroupNameControl from '@/components/SearchComponent/GroupNameControl';
 import GroupNameControl from '@/components/SearchComponent/GroupNameControl';
 import { DeleteOutlined, DoubleRightOutlined } from '@ant-design/icons';
 import { DeleteOutlined, DoubleRightOutlined } from '@ant-design/icons';
-import { Button, Dropdown, Menu, message, Popconfirm, Popover, Input as AInput } from 'antd';
+import { Button, Dropdown, Input as AInput, Menu, message, Popconfirm, Popover } from 'antd';
 import { useState } from 'react';
 import { useState } from 'react';
 import type { ProColumns } from '@jetlinks/pro-table';
 import type { ProColumns } from '@jetlinks/pro-table';
 import type { EnumData } from '@/utils/typings';
 import type { EnumData } from '@/utils/typings';
 import styles from './index.less';
 import styles from './index.less';
 import Service from '@/components/SearchComponent/service';
 import Service from '@/components/SearchComponent/service';
+import _ from 'lodash';
+import { useIntl } from '@@/plugin-locale/localeExports';
 
 
 const ui2Server = (source: SearchTermsUI): SearchTermsServer => [
 const ui2Server = (source: SearchTermsUI): SearchTermsServer => [
   { terms: source.terms1, type: source.type },
   { terms: source.terms1, type: source.type },
@@ -49,52 +51,43 @@ const termType = [
   { label: '<=', value: 'lte' },
   { label: '<=', value: 'lte' },
   { label: '属于', value: 'in' },
   { label: '属于', value: 'in' },
   { label: '不属于', value: 'not in' },
   { label: '不属于', value: 'not in' },
-
-  // { label: '为空', value: '=\'\'' },
-  // { label: '不为空', value: '!=\'\'' },
-  // { label: 'isnull', value: 'is null' },
-  // { label: 'notnull', value: 'not null' },
-  // { label: '介于', value: 'between' },
-  // { label: '不介于', value: 'not between' },
 ];
 ];
 
 
 const service = new Service();
 const service = new Service();
+const defaultTerm = { termType: 'like' };
+
 const SearchComponent = <T extends Record<string, any>>({ field, onSearch, target }: Props<T>) => {
 const SearchComponent = <T extends Record<string, any>>({ field, onSearch, target }: Props<T>) => {
+  const intl = useIntl();
   const [expand, setExpand] = useState<boolean>(true);
   const [expand, setExpand] = useState<boolean>(true);
+  const initForm = server2Ui([{ terms: [defaultTerm], type: 'and' }, { terms: [defaultTerm] }]);
   const [logVisible, setLogVisible] = useState<boolean>(false);
   const [logVisible, setLogVisible] = useState<boolean>(false);
   const [alias, setAlias] = useState<string>('');
   const [alias, setAlias] = useState<string>('');
   const [aliasVisible, setAliasVisible] = useState<boolean>(false);
   const [aliasVisible, setAliasVisible] = useState<boolean>(false);
-  const [initParams, setInitParams] = useState<SearchTermsServer>([
-    { terms: [{ column: null }], type: 'and' },
-    { terms: [{ column: null }] },
-  ]);
+  const [initParams, setInitParams] = useState<SearchTermsUI>(initForm);
   const [history, setHistory] = useState([]);
   const [history, setHistory] = useState([]);
+
   const form = createForm<SearchTermsUI>({
   const form = createForm<SearchTermsUI>({
     validateFirst: true,
     validateFirst: true,
-    initialValues: server2Ui(initParams),
+    initialValues: initParams,
   });
   });
 
 
   const queryHistory = async () => {
   const queryHistory = async () => {
-    const response = await service.history.query(target);
+    const response = await service.history.query(`${target}-search`);
     if (response.status === 200) {
     if (response.status === 200) {
       setHistory(response.result);
       setHistory(response.result);
     }
     }
   };
   };
 
 
-  // useEffect(() => {
-  //   (queryHistory)();
-  // }, [target]);
-
   const handleExpand = () => {
   const handleExpand = () => {
     const value = form.values;
     const value = form.values;
     if (!expand) {
     if (!expand) {
       value.terms1.splice(1, 2);
       value.terms1.splice(1, 2);
       value.terms2.splice(1, 2);
       value.terms2.splice(1, 2);
     } else {
     } else {
-      value.terms2.push({ column: null }, { column: null });
-      value.terms1.push({ column: null }, { column: null });
+      value.terms2.push(defaultTerm, defaultTerm);
+      value.terms1.push(defaultTerm, defaultTerm);
     }
     }
-    setInitParams(ui2Server(value));
+    setInitParams(value);
     setExpand(!expand);
     setExpand(!expand);
   };
   };
   const SchemaField = createSchemaField({
   const SchemaField = createSchemaField({
@@ -123,7 +116,7 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
     },
     },
     'x-component': 'ArrayItems',
     'x-component': 'ArrayItems',
     type: 'array',
     type: 'array',
-    'x-value': new Array(expand ? 1 : 3).fill({ column: null }),
+    'x-value': new Array(expand ? 1 : 3).fill({ termType: 'like' }),
     items: {
     items: {
       type: 'object',
       type: 'object',
       'x-component': 'FormGrid',
       'x-component': 'FormGrid',
@@ -211,61 +204,87 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
     },
     },
   };
   };
 
 
-  const menu = () => {
-    return (
-      <Menu>
-        {history.map((item: any) => (
-          <Menu.Item onClick={() => message.success(item.name)} key={item.id}>
-            <div
-              style={{
-                display: 'flex',
-                justifyContent: 'space-between',
-                alignItems: 'center',
+  const handleHistory = (item: SearchHistory) => {
+    const log = JSON.parse(item.content) as SearchTermsUI;
+    form.setValues(log);
+    setExpand(!(log.terms1?.length > 1 || log.terms2?.length > 1));
+    setInitParams(log);
+  };
+  const historyDom = (
+    <Menu>
+      {history.map((item: SearchHistory) => (
+        <Menu.Item onClick={() => handleHistory(item)} key={item.id}>
+          <div
+            style={{
+              display: 'flex',
+              justifyContent: 'space-between',
+              alignItems: 'center',
+            }}
+          >
+            <span style={{ marginRight: '5px' }}>{item.name}</span>
+            <Popconfirm
+              onConfirm={async () => {
+                const response = await service.history.remove(target, item.key);
+                if (response.status === 200) {
+                  message.success('操作成功');
+                  const temp = history.filter((h: any) => h.key !== item.key);
+                  setHistory(temp);
+                }
               }}
               }}
+              title={'确认删除吗?'}
             >
             >
-              <span style={{ marginRight: '5px' }}>{item.name}</span>
-              <Popconfirm
-                onConfirm={async () => {
-                  const response = await service.history.remove(target, item.key);
-                  if (response.status === 200) {
-                    message.success('操作成功');
-                    const temp = history.filter((h: any) => h.key !== item.key);
-                    setHistory(temp);
-                  }
-                }}
-                title={'确认删除吗?'}
-              >
-                <DeleteOutlined />
-              </Popconfirm>
-            </div>
-          </Menu.Item>
-        ))}
-      </Menu>
-    );
-  };
+              <DeleteOutlined />
+            </Popconfirm>
+          </div>
+        </Menu.Item>
+      ))}
+    </Menu>
+  );
+
+  const formatValue = (value: SearchTermsUI): SearchTermsServer =>
+    ui2Server(value).map((term) => {
+      term.terms.map((item) => {
+        if (item.termType === 'like') {
+          item.value = `%${item.value}%`;
+          return item;
+        }
+        return item;
+      });
+      return term;
+    });
 
 
   const handleSearch = async () => {
   const handleSearch = async () => {
-    const value = await form.submit<SearchTermsUI>();
-    // TODO
-    value.terms1 = value.terms1.filter((item) => item.column != null).filter((item) => item.value);
-    value.terms2 = value.terms2.filter((item) => item.column != null).filter((item) => item.value);
-    onSearch(value);
+    const value = form.values;
+    setInitParams(value);
+    const filterTerms = (data: Partial<Term>[]) =>
+      data.filter((item) => item.column != null).filter((item) => item.value);
+    const temp = _.cloneDeep(value);
+    temp.terms1 = filterTerms(temp.terms1);
+    temp.terms2 = filterTerms(temp.terms2);
+    onSearch(formatValue(temp));
   };
   };
 
 
   const handleSaveLog = async () => {
   const handleSaveLog = async () => {
     const value = await form.submit<SearchTermsUI>();
     const value = await form.submit<SearchTermsUI>();
-    const response = await service.history.save(target, {
+    const response = await service.history.save(`${target}-search`, {
       name: alias,
       name: alias,
-      content: JSON.stringify(ui2Server(value)),
+      content: JSON.stringify(value),
     });
     });
     if (response.status === 200) {
     if (response.status === 200) {
       message.success('保存成功!');
       message.success('保存成功!');
     } else {
     } else {
-      message.success('保存失败');
+      message.error('保存失败');
     }
     }
     setAliasVisible(!aliasVisible);
     setAliasVisible(!aliasVisible);
   };
   };
 
 
+  const resetForm = async () => {
+    const temp = initParams;
+    temp.terms1 = temp.terms1.map(() => defaultTerm);
+    temp.terms2 = temp.terms2.map(() => defaultTerm);
+    setInitParams(temp);
+    await form.reset();
+  };
   return (
   return (
     <div>
     <div>
       <Form form={form} labelCol={4} wrapperCol={18}>
       <Form form={form} labelCol={4} wrapperCol={18}>
@@ -273,6 +292,7 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
         <div className={styles.action}>
         <div className={styles.action}>
           <FormButtonGroup.FormItem labelCol={10} wrapperCol={14}>
           <FormButtonGroup.FormItem labelCol={10} wrapperCol={14}>
             <Dropdown.Button
             <Dropdown.Button
+              placement={'bottomLeft'}
               trigger={['click']}
               trigger={['click']}
               onClick={handleSearch}
               onClick={handleSearch}
               visible={logVisible}
               visible={logVisible}
@@ -280,11 +300,10 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
                 setLogVisible(visible);
                 setLogVisible(visible);
                 if (visible) {
                 if (visible) {
                   await queryHistory();
                   await queryHistory();
-                  console.log('test');
                 }
                 }
               }}
               }}
               type="primary"
               type="primary"
-              overlay={menu}
+              overlay={historyDom}
             >
             >
               搜索
               搜索
             </Dropdown.Button>
             </Dropdown.Button>
@@ -304,15 +323,22 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
               visible={aliasVisible}
               visible={aliasVisible}
               onVisibleChange={(visible) => {
               onVisibleChange={(visible) => {
                 setAlias('');
                 setAlias('');
-                setInitParams(ui2Server(form.values));
+                setInitParams(form.values);
                 setAliasVisible(visible);
                 setAliasVisible(visible);
               }}
               }}
               title="搜索名称"
               title="搜索名称"
               trigger="click"
               trigger="click"
             >
             >
-              <Button block>保存</Button>
+              <Button block>
+                {intl.formatMessage({
+                  id: 'pages.data.option.save',
+                  defaultMessage: '保存',
+                })}
+              </Button>
             </Popover>
             </Popover>
-            <Button block>重置</Button>
+            <Button block onClick={resetForm}>
+              重置
+            </Button>
           </FormButtonGroup.FormItem>
           </FormButtonGroup.FormItem>
           <div>
           <div>
             <DoubleRightOutlined
             <DoubleRightOutlined

+ 10 - 0
src/components/SearchComponent/typings.d.ts

@@ -15,3 +15,13 @@ type SearchTermsServer = {
   terms: Partial<Term>[];
   terms: Partial<Term>[];
   type?: 'or' | 'and';
   type?: 'or' | 'and';
 }[];
 }[];
+
+type SearchHistory = {
+  id: string;
+  key: string;
+  name: string;
+  type: string;
+  userId: string;
+  createTime: number;
+  content: string;
+};

+ 1 - 0
src/locales/en-US/pages.ts

@@ -27,6 +27,7 @@ export default {
   'pages.data.option.detail': 'Detail',
   'pages.data.option.detail': 'Detail',
   'pages.data.option.download': 'Download',
   'pages.data.option.download': 'Download',
   'pages.data.option.record': 'Records',
   'pages.data.option.record': 'Records',
+  'pages.data.option.save': 'Save',
   'pages.searchTable.new': 'New',
   'pages.searchTable.new': 'New',
   'pages.searchTable.titleStatus': 'Status',
   'pages.searchTable.titleStatus': 'Status',
   'pages.searchTable.titleStatus.all': 'All',
   'pages.searchTable.titleStatus.all': 'All',

+ 1 - 0
src/locales/zh-CN/pages.ts

@@ -27,6 +27,7 @@ export default {
   'pages.data.option.detail': '详情',
   'pages.data.option.detail': '详情',
   'pages.data.option.download': '下载',
   'pages.data.option.download': '下载',
   'pages.data.option.record': '通知记录',
   'pages.data.option.record': '通知记录',
+  'pages.data.option.save': '保存',
   'pages.searchTable.new': '新建',
   'pages.searchTable.new': '新建',
   'pages.searchTable.titleStatus': '状态',
   'pages.searchTable.titleStatus': '状态',
   'pages.searchTable.titleStatus.all': '全部',
   'pages.searchTable.titleStatus.all': '全部',

+ 7 - 12
src/pages/system/User/index.tsx

@@ -6,7 +6,7 @@ import {
   CloseCircleOutlined,
   CloseCircleOutlined,
   PlayCircleOutlined,
   PlayCircleOutlined,
 } from '@ant-design/icons';
 } from '@ant-design/icons';
-import { Tooltip, Popconfirm, message, Drawer, Card, Divider } from 'antd';
+import { Tooltip, Popconfirm, message, Drawer } from 'antd';
 import type { ProColumns, ActionType } from '@jetlinks/pro-table';
 import type { ProColumns, ActionType } from '@jetlinks/pro-table';
 import BaseCrud from '@/components/BaseCrud';
 import BaseCrud from '@/components/BaseCrud';
 import { CurdModel } from '@/components/BaseCrud/model';
 import { CurdModel } from '@/components/BaseCrud/model';
@@ -18,7 +18,7 @@ import { useIntl } from '@@/plugin-locale/localeExports';
 import type { ISchema } from '@formily/json-schema';
 import type { ISchema } from '@formily/json-schema';
 import Authorization from '@/components/Authorization';
 import Authorization from '@/components/Authorization';
 import autzModel from '@/components/Authorization/autz';
 import autzModel from '@/components/Authorization/autz';
-import SearchComponent from '@/components/SearchComponent';
+// import SearchComponent from '@/components/SearchComponent';
 
 
 export const service = new BaseService<UserItem>('user');
 export const service = new BaseService<UserItem>('user');
 const User = observer(() => {
 const User = observer(() => {
@@ -289,18 +289,12 @@ const User = observer(() => {
     },
     },
   };
   };
 
 
+  intl.formatMessage({
+    id: 'pages.system.user',
+    defaultMessage: '默认值',
+  });
   return (
   return (
     <PageContainer>
     <PageContainer>
-      <Card>
-        <SearchComponent<UserItem>
-          field={columns}
-          onSearch={async (data) => {
-            message.success(JSON.stringify(data));
-          }}
-          target="user-search"
-        />
-      </Card>
-      <Divider />
       <BaseCrud<UserItem>
       <BaseCrud<UserItem>
         actionRef={actionRef}
         actionRef={actionRef}
         columns={columns}
         columns={columns}
@@ -310,6 +304,7 @@ const User = observer(() => {
           id: 'pages.system.user',
           id: 'pages.system.user',
           defaultMessage: '用户管理',
           defaultMessage: '用户管理',
         })}
         })}
+        moduleName="user"
         schema={schema}
         schema={schema}
       />
       />
       <Drawer
       <Drawer

+ 2 - 2
src/utils/BaseService.ts

@@ -19,8 +19,8 @@ class BaseService<T> implements IBaseService<T> {
     this.uri = `/${SystemConst.API_BASE}/${uri}`;
     this.uri = `/${SystemConst.API_BASE}/${uri}`;
   }
   }
 
 
-  query(params: any): Promise<any> {
-    return request(`${this.uri}/_query/`, { params, method: 'GET' });
+  query(data: any): Promise<any> {
+    return request(`${this.uri}/_query/`, { data, method: 'POST' });
   }
   }
 
 
   queryNoPaging(params: any): Promise<unknown> {
   queryNoPaging(params: any): Promise<unknown> {