Procházet zdrojové kódy

导出添加状态显示

xyh před 2 roky
rodič
revize
c56365d918

+ 13 - 2
packages/app/src/components/filter-button-group/index.tsx

@@ -7,6 +7,8 @@ type Props = {
   onSearch: () => void;
   onExport?: () => void;
   searchTestId?: string;
+  isLoading?: boolean;
+  isExporting?: boolean;
 };
 
 const colStyle: CSSProperties = {textAlign: 'right'};
@@ -17,13 +19,22 @@ const FiterButtonGroup: FC<Props> = function({
   offset,
   disableAlign,
   searchTestId,
+  isLoading,
+  isExporting,
 }) {
   return (
     <>
       <Col span={6} style={!disableAlign ? colStyle : void 0} offset={offset}>
         <Space>
-          <Button type='primary' onClick={onSearch} data-testid={searchTestId ?? 'search_btn'}>查询</Button>
-          {onExport && <Button type='default' onClick={onExport}>导出</Button>}
+          <Button
+            loading={isLoading}
+            type='primary'
+            onClick={onSearch}
+            data-testid={searchTestId ?? 'search_btn'}
+          >
+            查询
+          </Button>
+          {onExport && <Button loading={isExporting} type='default' onClick={onExport}>导出</Button>}
         </Space>
       </Col>
     </>

+ 1 - 0
packages/app/src/hooks/index.ts

@@ -1,2 +1,3 @@
 export * from './usePageContext';
 export * from './useContextSection';
+export * from './useExportFile';

+ 74 - 0
packages/app/src/hooks/useExportFile/index.test.tsx

@@ -0,0 +1,74 @@
+import {render, waitFor} from '@testing-library/react';
+import {useExportFile} from '.';
+import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
+import {FC} from 'react';
+import {act} from 'react-dom/test-utils';
+
+const fn = jest.fn();
+function mockAsync() {
+  return new Promise(function(res) {
+    setTimeout(function() {
+      fn();
+      res(1);
+    }, 200);
+  });
+}
+
+describe('useExportFile', function() {
+  Object.assign(global, {
+    URL: {
+      createObjectURL(val: string) {
+        return val;
+      },
+    },
+    open() {
+      /** mock fn */
+    },
+  });
+
+  beforeEach(function() {
+    fn.mockClear();
+    jest.useFakeTimers();
+  });
+
+  it('定义正确', function() {
+    expect(useExportFile).toBeDefined();
+
+    expect(useExportFile).toBeInstanceOf(Function);
+  });
+
+  it('测试组件操作', async function() {
+    const client = new QueryClient();
+
+    const App: FC = function() {
+      const [isExporting, mutate] = useExportFile(mockAsync);
+
+      return (
+        <>
+          <p data-testid='state'>{isExporting.toString()}</p>
+          <button onClick={mutate} data-testid='btn' />
+        </>
+      );
+    };
+
+    const {getByTestId} = render(<QueryClientProvider client={client}>
+      <App />
+    </QueryClientProvider>);
+
+    expect(getByTestId('state').innerHTML).toBe('false');
+
+    act(function() {
+      getByTestId('btn').click();
+    });
+
+    await waitFor(function() {
+      expect(getByTestId('state').innerHTML).toBe('true');
+    });
+
+    await waitFor(function() {
+      expect(getByTestId('state').innerHTML).toBe('false');
+    });
+
+    expect(fn).toBeCalled();
+  });
+});

+ 19 - 0
packages/app/src/hooks/useExportFile/index.ts

@@ -0,0 +1,19 @@
+import {useMutation} from '@tanstack/react-query';
+import {EXPORT_ERROR_TIPS, exportFile} from '@utils';
+import {message} from 'antd';
+
+export function useExportFile<P>(fn: (params: P) => Promise<any>) {
+  const {mutate, isLoading} = useMutation(
+    fn,
+    {
+      onSuccess(data) {
+        !process.env.IS_E2E && exportFile(data as Blob);
+      },
+      onError() {
+        message.error(EXPORT_ERROR_TIPS);
+      },
+    },
+  );
+
+  return [isLoading, mutate] as const;
+}

+ 14 - 14
packages/app/src/pages/department/filter/hooks.ts

@@ -1,9 +1,7 @@
 import {useState} from 'react';
 import {context, pageContext} from '../context';
 import {exportDepartment} from '@apis';
-import {useContextSection, usePage} from '@hooks';
-import {EXPORT_ERROR_TIPS, exportFile} from '@utils';
-import {message} from 'antd';
+import {useContextSection, useExportFile, usePage} from '@hooks';
 
 export function useField() {
   const [state, setState] = useState({name: '', code: ''});
@@ -17,29 +15,31 @@ export function useField() {
   return [state, onChange] as const;
 }
 
-export function useHandle(name: string, code: string) {
+export function useSearch(name: string, code: string) {
   const dispatch = useContextSection(context, state => state[1]);
-  const [{page, pageSize}, {onCurrentPageChange}] = usePage(pageContext);
+  const [, {onCurrentPageChange}] = usePage(pageContext);
 
   function onSearch() {
     onCurrentPageChange(1);
     dispatch({type: 'SEARCH', payload: {name, code}});
   }
 
+  return onSearch;
+}
+
+export function useExport() {
+  const [{page, pageSize}] = usePage(pageContext);
+  const [isExporting, mutate] = useExportFile(exportDepartment);
+  const {code, name} = useContextSection(context, state => state[0]);
+
   function onExport() {
-    exportDepartment({
+    mutate({
       page: page.toString(),
       limit: pageSize.toString(),
       code,
       departmentName: name,
-    })
-      .then(function(res: Blob) {
-        exportFile(res);
-      })
-      .catch(function() {
-        message.error(EXPORT_ERROR_TIPS);
-      });
+    });
   }
 
-  return {onSearch, onExport};
+  return [isExporting, onExport] as const;
 }

+ 9 - 3
packages/app/src/pages/department/filter/index.tsx

@@ -1,18 +1,24 @@
 import {FilterButtonGroup, FilterField} from '@components';
 import {Card, Row} from 'antd';
 import {FC} from 'react';
-import {useField, useHandle} from './hooks';
+import {useExport, useField, useSearch} from './hooks';
 
 const Filter: FC = function() {
   const [{name, code}, onChange] = useField();
-  const {onSearch, onExport} = useHandle(name, code);
+  const onSearch = useSearch(name, code);
+  const [isExporting, onExport] = useExport();
 
   return (
     <Card>
       <Row>
         <FilterField name='departmentCode' label='部门编号' value={code} onChange={onChange('code')} />
         <FilterField name='departmentName' label='部门名称' value={name} onChange={onChange('name')} />
-        <FilterButtonGroup onSearch={onSearch} onExport={onExport} offset={6} />
+        <FilterButtonGroup
+          isExporting={isExporting}
+          onSearch={onSearch}
+          onExport={onExport}
+          offset={6}
+        />
       </Row>
     </Card>
   );

+ 7 - 6
packages/app/src/pages/goods/filter/hooks.ts

@@ -1,8 +1,7 @@
-import {useContextSection, usePage} from '@hooks';
+import {useContextSection, useExportFile, usePage} from '@hooks';
 import {useState} from 'react';
 import {context, pageContext} from '../context';
 import {goodsExport} from '@apis';
-import {exportFile} from '@utils';
 
 export function useSearch() {
   const [value, setValue] = useState('');
@@ -17,16 +16,18 @@ export function useSearch() {
   return [{value}, {onSearch, onChange: setValue}] as const;
 }
 
-export function useExport(code: string) {
+export function useExport() {
   const [{page, pageSize}] = usePage(pageContext);
+  const [isExporting, mutate] = useExportFile(goodsExport);
+  const code = useContextSection(context, ([{number}]) => number);
 
   function onExport() {
-    goodsExport({
+    mutate({
       itemNumber: code,
       page: String(page),
       limit: String(pageSize),
-    }).then((res: Blob) => exportFile(res));
+    });
   }
 
-  return onExport;
+  return [isExporting, onExport] as const;
 }

+ 7 - 2
packages/app/src/pages/goods/filter/index.tsx

@@ -5,13 +5,18 @@ import {useExport, useSearch} from './hooks';
 
 const Filter: FC = function() {
   const [{value}, {onSearch, onChange}] = useSearch();
-  const onExport = useExport(value);
+  const [isExporting, onExport] = useExport();
 
   return (
     <Card>
       <Row>
         <FilterField name='goodsCode' label='品号' value={value} onChange={onChange} />
-        <FilterButtonGroup onSearch={onSearch} onExport={onExport} offset={12} />
+        <FilterButtonGroup
+          isExporting={isExporting}
+          onSearch={onSearch}
+          onExport={onExport}
+          offset={12}
+        />
       </Row>
     </Card>
   );

+ 14 - 9
packages/app/src/pages/role/filter/hooks.ts

@@ -1,8 +1,7 @@
 import {useState} from 'react';
-import {useContext, usePage} from '@hooks';
+import {useContext, useContextSection, useExportFile, usePage} from '@hooks';
 import {context, pageContext} from '../context';
 import {exportRole} from '@apis';
-import {exportFile} from '@utils';
 
 export function useField() {
   const [state, setState] = useState('');
@@ -14,24 +13,30 @@ export function useField() {
   return [state, onChange] as const;
 }
 
-export function useHandle(value: string) {
-  const [{name}, dispatch] = useContext(context);
-  const [{page, pageSize}, {onCurrentPageChange}] = usePage(pageContext);
+export function useSearch(value: string) {
+  const [, dispatch] = useContext(context);
+  const [, {onCurrentPageChange}] = usePage(pageContext);
 
   function onSearch() {
     onCurrentPageChange(1);
     dispatch({type: 'SEARCH', payload: {name: value}});
   }
 
+  return onSearch;
+}
+
+export function useExport() {
+  const name = useContextSection(context, state => state[0].name);
+  const [{page, pageSize}] = usePage(pageContext);
+  const [isExporting, mutate] = useExportFile(exportRole);
+
   function onExport() {
-    exportRole({
+    mutate({
       page: String(page),
       limit: String(pageSize),
       roleName: name,
-    }).then(function(res: Blob) {
-      exportFile(res);
     });
   }
 
-  return {onSearch, onExport};
+  return [isExporting, onExport] as const;
 }

+ 9 - 3
packages/app/src/pages/role/filter/index.tsx

@@ -1,17 +1,23 @@
 import {FilterButtonGroup, FilterField} from '@components';
 import {Card, Row} from 'antd';
 import {FC} from 'react';
-import {useField, useHandle} from './hooks';
+import {useExport, useField, useSearch} from './hooks';
 
 const Filter: FC = function() {
   const [value, onChange] = useField();
-  const {onSearch, onExport} = useHandle(value);
+  const onSearch = useSearch(value);
+  const [isExporting, onExport] = useExport();
 
   return (
     <Card>
       <Row>
         <FilterField name='roleName' label='角色名称' value={value} onChange={onChange} />
-        <FilterButtonGroup onSearch={onSearch} onExport={onExport} offset={12} />
+        <FilterButtonGroup
+          isExporting={isExporting}
+          onSearch={onSearch}
+          onExport={onExport}
+          offset={12}
+        />
       </Row>
     </Card>
   );

+ 9 - 6
packages/app/src/pages/storage/filter/hooks.ts

@@ -1,9 +1,8 @@
 import {useState} from 'react';
 import {useContextSelector} from 'use-context-selector';
 import {context, pageContext} from '../context';
-import {usePage} from '@hooks';
+import {useExportFile, usePage} from '@hooks';
 import {exportStorage} from '@apis';
-import {exportFile} from '@utils';
 
 export function useField() {
   const [fields, setFields] = useState({
@@ -40,19 +39,23 @@ export function useSearch(state: {
 }
 
 export function useExport() {
-  const {name, code, type, isNotDisable} = useContextSelector(context, ([state]) => state);
+  const {name, code, type, isNotDisable} = useContextSelector(
+    context,
+    ([state]) => state,
+  );
   const [{page, pageSize}] = usePage(pageContext);
+  const [isExporting, mutate] = useExportFile(exportStorage);
 
   function onExport() {
-    exportStorage({
+    mutate({
       page: String(page),
       limit: String(pageSize),
       storageLocationName: name,
       storageLocationCode: code,
       storageLocationType: type,
       isNotDisable,
-    }).then((res: Blob) => exportFile(res));
+    });
   }
 
-  return onExport;
+  return [isExporting, onExport] as const;
 }

+ 7 - 2
packages/app/src/pages/storage/filter/index.tsx

@@ -13,7 +13,7 @@ const Filter: FC = function() {
   const [fields, onChange] = useField();
   const {code, name, type, isNotDisable} = fields;
   const onSearch = useSearch(fields);
-  const onExport = useExport();
+  const [isExporting, onExport] = useExport();
 
   return (
     <Card>
@@ -31,7 +31,12 @@ const Filter: FC = function() {
           />
         </Row>
         <Row>
-          <FilterButtonGroup onSearch={onSearch} onExport={onExport} disableAlign />
+          <FilterButtonGroup
+            isExporting={isExporting}
+            onSearch={onSearch}
+            onExport={onExport}
+            disableAlign
+          />
         </Row>
       </Space>
     </Card>

+ 12 - 8
packages/app/src/pages/user/filter/hooks.ts

@@ -1,19 +1,18 @@
-import {useContextSection, usePage} from '@hooks';
+import {useContextSection, useExportFile, usePage} from '@hooks';
 import {useState} from 'react';
 import {context, pageContext} from '../context';
 import {exportUser} from '@apis';
-import {exportFile} from '@utils';
 
 export function useField() {
   const [state, setState] = useState({name: '', code: ''});
 
-  function onChnge(key: 'name' | 'code') {
+  function onChange(key: 'name' | 'code') {
     return function(value: string) {
       setState(prev => ({...prev, [key]: value}));
     };
   }
 
-  return [state, onChnge] as const;
+  return [state, onChange] as const;
 }
 
 export function useSearch(name: string, code: string) {
@@ -28,17 +27,22 @@ export function useSearch(name: string, code: string) {
   return onSearch;
 }
 
-export function useExport(name: string, code: string) {
+export function useExport() {
+  const [isExporting, mutate] = useExportFile(exportUser);
+  const {name, code} = useContextSection(
+    context,
+    ([state]) => state,
+  );
   const [{page, pageSize}] = usePage(pageContext);
 
   function onExport() {
-    exportUser({
+    mutate({
       page: String(page),
       limit: String(pageSize),
       userName: name,
       code,
-    }).then((res: Blob) => exportFile(res));
+    });
   }
 
-  return onExport;
+  return [isExporting, onExport] as const;
 }

+ 7 - 2
packages/app/src/pages/user/filter/index.tsx

@@ -6,14 +6,19 @@ import {useSearch, useField, useExport} from './hooks';
 const Filter: FC = function() {
   const [{name, code}, onChange] = useField();
   const onSearch = useSearch(name, code);
-  const onExport = useExport(name, code);
+  const [isExporting, onExport] = useExport();
 
   return (
     <Card>
       <Row>
         <FilterField name='useName' label='用户名称' value={name} onChange={onChange('name')} />
         <FilterField name='useCode' label='用户编号' value={code} onChange={onChange('code')} />
-        <FilterButtonGroup onSearch={onSearch} onExport={onExport} offset={6} />
+        <FilterButtonGroup
+          onSearch={onSearch}
+          onExport={onExport}
+          offset={6}
+          isExporting={isExporting}
+        />
       </Row>
     </Card>
   );

+ 1 - 1
packages/app/src/utils/exportFile.ts

@@ -1,4 +1,4 @@
 export function exportFile(blob: Blob) {
   const url = URL.createObjectURL(blob);
-  window.open(url, '_blank');
+  open(url, '_blank');
 }