浏览代码

update: 库位和物料绑定增加模板上传功能

xyh 2 年之前
父节点
当前提交
debd102c7f

二进制
packages/app/public/库位信息模板.xlsx


二进制
packages/app/public/物料用户绑定模板.xlsx


+ 34 - 1
packages/app/src/apis/request.ts

@@ -1,9 +1,10 @@
-import {BaseResultContent, GSBaseResult} from '@models';
+import {BaseResult, BaseResultContent, GSBaseResult} from '@models';
 import {userStore} from '@stores';
 import {E2E_NETWORK_URL, NETWORK_ERROR_TIPS, NETWORK_URL} from '@utils';
 import {message} from 'antd';
 import axios from 'axios';
 
+// 普通请求
 const http = axios.create({
   baseURL: process.env.IS_E2E ? E2E_NETWORK_URL : NETWORK_URL,
   headers: {
@@ -63,6 +64,7 @@ export async function request<T, R extends BaseResultContent<any>>(options: {
   return res;
 }
 
+// gs请求
 const GSHttp = axios.create({
   baseURL: (process.env.IS_E2E ? E2E_NETWORK_URL : NETWORK_URL) + '/gsAccess',
   headers: {
@@ -101,3 +103,34 @@ export async function gsRequest<T>(url: string, data: T, signal?: AbortSignal) {
 
   return res;
 }
+
+// 上传文件
+const uploadHttp = axios.create({
+  baseURL: process.env.IS_E2E ? E2E_NETWORK_URL : NETWORK_URL,
+  headers: {
+    'Content-Type': 'multipart/form-data',
+    'Cache-Control': 'no-cache',
+  },
+});
+
+uploadHttp.interceptors.request.use(function (config) {
+  const {token, id} = userStore.getState();
+  config.headers.token = token;
+  config.headers.userId = String(id);
+
+  return config;
+});
+
+export async function uploadRequest(url: string, data: FormData): BaseResult {
+  let res: BaseResultContent<unknown>;
+
+  try {
+    res = (await GSHttp.post(url, data)).data;
+  } catch (error: any) {
+    res = {msg: '510', errMsg: NETWORK_ERROR_TIPS};
+
+    error.code !== 'ERR_CANCELED' && message.error(res.errMsg);
+  }
+
+  return res;
+}

+ 6 - 0
packages/app/src/apis/upload.ts

@@ -0,0 +1,6 @@
+import {uploadRequest} from './request';
+
+/** 库位导入 */
+export function uploadStorage(data: FormData) {
+  return uploadRequest('/storage/import', data);
+}

+ 34 - 7
packages/app/src/components/table-tools/index.tsx

@@ -1,6 +1,12 @@
-import {FileAdditionOne, FileExcel, Refresh} from '@icon-park/react';
+import {
+  Download,
+  FileAdditionOne,
+  FileExcel,
+  Refresh,
+  Upload,
+} from '@icon-park/react';
 import {ChildrenFC} from '@utils';
-import {Button, Space} from 'antd';
+import {Button, Space, Upload as AntUpload, UploadProps} from 'antd';
 import css from './index.module.css';
 import {useLocation} from 'react-router-dom';
 import {useMemo} from 'react';
@@ -8,25 +14,28 @@ import {useStore} from 'zustand';
 import {menuStore} from '@stores';
 
 type Props = {
-  /** @deprecated use onAdd */
-  onClick?: () => void;
   onAdd?: () => void;
   onRefresh?: () => void;
   onExport?: () => void;
   isExporting?: boolean;
   isRefreshing?: boolean;
   title?: string;
+  onDownload?: () => void;
+  uploadProps?: UploadProps;
+  isUploading?: boolean;
 };
 
 const TableTools: ChildrenFC<Props> = function ({
   children,
-  onClick,
   onAdd,
   onRefresh,
   onExport,
   isExporting,
   isRefreshing,
   title,
+  onDownload,
+  uploadProps,
+  isUploading,
 }) {
   const pages = useStore(menuStore, state => state.menus);
   const {pathname} = useLocation();
@@ -41,10 +50,10 @@ const TableTools: ChildrenFC<Props> = function ({
     <section className='table-tool'>
       <h1 className={css.title}>{title ?? menuTitle}</h1>
       <Space>
-        {(onClick || onAdd) && (
+        {onAdd && (
           <Button
             type='primary'
-            onClick={onClick || onAdd}
+            onClick={onAdd}
             data-testid='add_btn'
             icon={<FileAdditionOne theme='outline' className='anticon' />}
           >
@@ -61,6 +70,24 @@ const TableTools: ChildrenFC<Props> = function ({
             刷新
           </Button>
         )}
+        {onDownload && (
+          <Button
+            onClick={onDownload}
+            icon={<Download theme='outline' className='anticon' />}
+          >
+            模板下载
+          </Button>
+        )}
+        {uploadProps && (
+          <AntUpload {...uploadProps}>
+            <Button
+              loading={isUploading}
+              icon={<Upload theme='outline' className='anticon' />}
+            >
+              模板上传
+            </Button>
+          </AntUpload>
+        )}
         {onExport && (
           <Button
             onClick={onExport}

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

@@ -16,3 +16,5 @@ export * from './use-supertube';
 export * from './use-mutate-add-order';
 export * from './use-filter-db';
 export * from './use-select-filter-options';
+export * from './use-download';
+export * from './use-upload';

+ 12 - 0
packages/app/src/hooks/use-download/index.ts

@@ -0,0 +1,12 @@
+import {useCallback} from 'react';
+
+export function useDownlaod(url: string) {
+  const onClick = useCallback(
+    function () {
+      open(url);
+    },
+    [url],
+  );
+
+  return onClick;
+}

+ 50 - 0
packages/app/src/hooks/use-upload/index.ts

@@ -0,0 +1,50 @@
+import {BaseResultContent} from '@models';
+import {userStore} from '@stores';
+import {NETWORK_URL} from '@utils';
+import {useBoolean} from 'ahooks';
+import {UploadProps, message} from 'antd';
+import {useMemo} from 'react';
+import {useStore} from 'zustand';
+
+export function useUpload(url: string, onFetch: () => void) {
+  const {id, token} = useStore(userStore);
+  const [isUploading, {set}] = useBoolean();
+  const props = useMemo<UploadProps<BaseResultContent<unknown>>>(
+    function () {
+      return {
+        name: 'file',
+        action: `${NETWORK_URL}${url}`,
+        headers: {
+          token,
+          userId: String(id),
+        },
+        showUploadList: false,
+        onChange(info) {
+          set(info.file.status === 'uploading');
+
+          if (info.file.status === 'error') {
+            message.error('上传失败,请稍后再试');
+          }
+
+          if (info.file.status === 'done') {
+            if (info.file.response) {
+              const {msg} = info.file.response;
+
+              if (msg === '200') {
+                message.success('上传成功');
+                onFetch();
+              } else {
+                message.error(info.file.response.errMsg);
+              }
+            } else {
+              message.error('上传失败');
+            }
+          }
+        },
+      };
+    },
+    [id, onFetch, set, token, url],
+  );
+
+  return {uploadProps: props, isUploading};
+}

+ 6 - 0
packages/app/src/pages/material-bind/table/index.tsx

@@ -7,8 +7,10 @@ import {context, pageContext, searchContext} from '../context';
 import {exportUserBind, getMaterialBindList} from '@apis';
 import {
   useContextSection,
+  useDownlaod,
   useQueryTableList,
   useTableExportEvent,
+  useUpload,
 } from '@hooks';
 
 const TableList: FC = function () {
@@ -26,6 +28,8 @@ const TableList: FC = function () {
     context,
     fn: exportUserBind,
   });
+  const onDownload = useDownlaod('/物料用户绑定模板.xlsx');
+  const uploadProps = useUpload('/userMaterial/import', refetch);
 
   return (
     <>
@@ -36,6 +40,8 @@ const TableList: FC = function () {
           onExport={onExport}
           isRefreshing={isFetching}
           isExporting={isExporting}
+          onDownload={onDownload}
+          {...uploadProps}
         />
 
         <Table

+ 6 - 0
packages/app/src/pages/storage/table/index.tsx

@@ -7,8 +7,10 @@ import {Table, TableTools} from '@components';
 import {exportStorage, getStorageList} from '@apis';
 import {
   useContextSection,
+  useDownlaod,
   useQueryTableList,
   useTableExportEvent,
+  useUpload,
 } from '@hooks';
 
 const TableList: FC = function () {
@@ -26,6 +28,8 @@ const TableList: FC = function () {
     context,
     fn: exportStorage,
   });
+  const onDownload = useDownlaod('/库位信息模板.xlsx');
+  const uploadProps = useUpload('/storage/import', refetch);
 
   return (
     <>
@@ -37,6 +41,8 @@ const TableList: FC = function () {
           onExport={onExport}
           isRefreshing={isFetching}
           isExporting={isExporting}
+          onDownload={onDownload}
+          {...uploadProps}
         />
 
         <Table