xyh пре 2 година
родитељ
комит
a1842d7da3
25 измењених фајлова са 213 додато и 102 уклоњено
  1. 2 0
      packages/app/public/index.html
  2. 3 1
      packages/app/src/apis/finishProduct.ts
  3. 2 1
      packages/app/src/components/error-boundary/index.tsx
  4. 2 2
      packages/app/src/components/modal-field/Select.tsx
  5. 2 0
      packages/app/src/models/request/menu.ts
  6. 3 0
      packages/app/src/models/response/menu.ts
  7. 5 2
      packages/app/src/pages/home/index.tsx
  8. 1 0
      packages/app/src/pages/home/menu/hooks.tsx
  9. 1 0
      packages/app/src/pages/login/info/index.module.css
  10. 6 0
      packages/app/src/pages/menu-id/table/modal/Info.tsx
  11. 21 43
      packages/app/src/pages/menu-id/table/modal/hooks.ts
  12. 6 3
      packages/app/src/pages/menu/table/modal/Info.tsx
  13. 21 43
      packages/app/src/pages/menu/table/modal/hooks.ts
  14. 2 2
      packages/app/src/pages/pda-menu/table/modal/hooks.ts
  15. 2 0
      packages/app/src/pages/product-in-stream/index.tsx
  16. 14 0
      packages/app/src/pages/product-in-stream/table/hooks.ts
  17. 37 0
      packages/app/src/pages/product-in-stream/table/index.tsx
  18. 2 2
      packages/app/src/pages/product-out-stream/filter/hooks.ts
  19. 2 0
      packages/app/src/pages/product-out-stream/index.tsx
  20. 14 0
      packages/app/src/pages/product-out-stream/table/hooks.ts
  21. 34 0
      packages/app/src/pages/product-out-stream/table/index.tsx
  22. 3 0
      packages/app/src/pages/stock-operation/hooks.ts
  23. 8 1
      packages/app/src/pages/stock-operation/index.tsx
  24. 9 0
      packages/app/src/utils/sortMenu/index.test.ts
  25. 11 2
      packages/app/src/utils/sortMenu/index.ts

+ 2 - 0
packages/app/public/index.html

@@ -10,6 +10,8 @@
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta http-equiv="Pragma" content="no-cache" />
   <meta http-equiv="Cache-Control" content="no-cache" />
+  <link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.3.0/css/all.min.css" rel="stylesheet">
+
   <title>
     <%= htmlWebpackPlugin.options.title %>
   </title>

+ 3 - 1
packages/app/src/apis/finishProduct.ts

@@ -28,6 +28,7 @@ export function getFinishProductOutStreamExport(
     method: 'GET',
     data,
     url: `${BASE_URL}/getRemovalHalfExcel`,
+    skipError: true,
   });
 }
 
@@ -36,7 +37,7 @@ export function getFinishProductInStreamList(
   data: GetFinishProductListStreamParams,
 ): BaseListResult<FinishProductListStreamInListData> {
   return request({
-    method: 'POST',
+    method: 'GET',
     data,
     url: `${BASE_URL2}/getHalf`,
   });
@@ -50,5 +51,6 @@ export function getFinishProductInStreamExport(
     method: 'GET',
     data,
     url: `${BASE_URL2}/getHalfExcel`,
+    skipError: true,
   });
 }

+ 2 - 1
packages/app/src/components/error-boundary/index.tsx

@@ -8,7 +8,8 @@ const ErrorBoundaryComponent: FC<FallbackProps> = function({error, resetErrorBou
   return (
     <Result
       status='error'
-      title={error?.message}
+      title='出错了...'
+      subTitle={error?.message}
       extra={
         <Button type='primary' onClick={resetErrorBoundary}>重试</Button>
       }

+ 2 - 2
packages/app/src/components/modal-field/Select.tsx

@@ -20,7 +20,7 @@ const ModalSelect: FC<Props> = function({
   label,
   placeholder,
   data,
-  required,
+  required = true,
   showSearch,
 }) {
   return (
@@ -30,7 +30,7 @@ const ModalSelect: FC<Props> = function({
           className={
             classNames([
               css.textRight,
-              {[css.fieldRequired]: required ?? true},
+              {[css.fieldRequired]: required},
             ])
           }
           htmlFor={`operation_${name}`}

+ 2 - 0
packages/app/src/models/request/menu.ts

@@ -10,6 +10,8 @@ export type AddMenuParams = {
   pId: string;
   /** 排序 */
   orderBy: string;
+  /** 菜单图标 */
+  img: string;
 };
 
 /** 修改菜单 */

+ 3 - 0
packages/app/src/models/response/menu.ts

@@ -9,6 +9,8 @@ export type MenuListData = {
   url: string;
   /** 排序 */
   orderBy: string;
+  /** 菜单图标 */
+  img: string;
 };
 
 /** 属性菜单 */
@@ -29,4 +31,5 @@ export type TreeRoleMenuData = {
   page: number;
   limit: number;
   orderBy: string;
+  img: string;
 };

+ 5 - 2
packages/app/src/pages/home/index.tsx

@@ -19,8 +19,11 @@ const Home: FC = function() {
         <User />
       </Layout.Header>
 
-      <Layout>
-        <Layout.Sider className={css.sliderBackground}>
+      <Layout hasSider>
+        <Layout.Sider
+          className={css.sliderBackground}
+          breakpoint='lg'
+        >
           <Menu />
         </Layout.Sider>
 

+ 1 - 0
packages/app/src/pages/home/menu/hooks.tsx

@@ -25,6 +25,7 @@ export function useMenu() {
             page: 1,
             limit: 0,
             orderBy: '0',
+            img: 'house',
           },
           ...data.data,
         ];

+ 1 - 0
packages/app/src/pages/login/info/index.module.css

@@ -4,6 +4,7 @@
   width: 280px;
 
   & h1 {
+    font-weight: normal;
     text-align: center;
   }
 }

+ 6 - 0
packages/app/src/pages/menu-id/table/modal/Info.tsx

@@ -24,6 +24,12 @@ const Info: FC<Props> = function({id}) {
         label='菜单路径'
         control={control}
       />
+      <ModalField
+        name='menuIcon'
+        label='菜单图标'
+        control={control}
+        required={false}
+      />
       <ModalField
         name='menuOrderBy'
         width='300px'

+ 21 - 43
packages/app/src/pages/menu-id/table/modal/hooks.ts

@@ -1,9 +1,8 @@
 import {addMenu, editMenu, getMenuInfo} from '@apis';
 import {yupResolver} from '@hookform/resolvers/yup';
-import {useContextSection, useQueryDataInfo} from '@hooks';
+import {useContextSection, usePutData, useQueryDataInfo} from '@hooks';
+import {AddMenuParams} from '@models';
 import {pIdContext} from '@pages/menu-id/context';
-import {useMutation} from '@tanstack/react-query';
-import {message} from 'antd';
 import {useEffect} from 'react';
 import {useForm, useFormContext} from 'react-hook-form';
 import {object, string} from 'yup';
@@ -12,6 +11,7 @@ type FormState = {
   menuName: string;
   menuUrl: string;
   menuOrderBy: string;
+  menuIcon: string;
 };
 
 const validate = object({
@@ -20,40 +20,6 @@ const validate = object({
   menuOrderBy: string().required('请输入菜单排序'),
 });
 
-function useAdd(onClose: () => void, onFetch: () => void) {
-  const {isLoading, mutate} = useMutation(
-    addMenu,
-    {
-      onSuccess(data) {
-        if (data.msg === '200') {
-          onClose();
-          onFetch();
-          message.success('新增成功');
-        }
-      },
-    },
-  );
-
-  return [isLoading, mutate] as const;
-}
-
-function useEdit(onClose: () => void, onFetch: () => void) {
-  const {isLoading, mutate} = useMutation(
-    editMenu,
-    {
-      onSuccess(data) {
-        if (data.msg === '200') {
-          onClose();
-          onFetch();
-          message.success('修改成功');
-        }
-      },
-    },
-  );
-
-  return [isLoading, mutate] as const;
-}
-
 export function useFormState(
   {onClose, id, visible, onFetch}:
   {
@@ -72,18 +38,29 @@ export function useFormState(
     resolver: yupResolver(validate),
   });
   const {handleSubmit, clearErrors} = formInstance;
-  const [addIsLoading, addMutate] = useAdd(onClose, onFetch);
-  const [editIsLoaindg, editMutate] = useEdit(onClose, onFetch);
-  const isLoading = addIsLoading || editIsLoaindg;
+  const [isLoading, {addMutate, editMutate}] = usePutData({
+    addFn: addMenu,
+    editFn: editMenu,
+    onClose,
+    onFetch,
+  });
 
   useEffect(function() {
     visible && clearErrors();
   }, [clearErrors, visible]);
 
-  const onSubmit = handleSubmit(function({menuName, menuUrl, menuOrderBy}) {
+  const onSubmit = handleSubmit(function({menuName, menuUrl, menuOrderBy, menuIcon}) {
+    const params: AddMenuParams = {
+      name: menuName,
+      url: menuUrl,
+      pId: pid ?? '0',
+      orderBy: menuOrderBy,
+      img: menuIcon,
+    };
+
     isEdit
-      ? editMutate({name: menuName, url: menuUrl, id, pId: pid ?? '0', orderBy: menuOrderBy})
-      : addMutate({name: menuName, url: menuUrl, pId: pid ?? '0', orderBy: menuOrderBy});
+      ? editMutate({id, ...params})
+      : addMutate(params);
   });
 
   return [{formInstance, isLoading}, onSubmit] as const;
@@ -107,5 +84,6 @@ export function useFormValues(id: string) {
     setValue('menuUrl', data?.url ?? '');
     setValue('menuName', data?.name ?? '');
     setValue('menuOrderBy', data?.orderBy ?? '0');
+    setValue('menuIcon', data?.img ?? '');
   }, [data, setValue]);
 }

+ 6 - 3
packages/app/src/pages/menu/table/modal/Info.tsx

@@ -15,19 +15,22 @@ const Info: FC<Props> = function({id}) {
     <>
       <ModalField
         name='menuName'
-        width='300px'
         label='菜单名称'
         control={control}
       />
       <ModalField
         name='menuUrl'
-        width='300px'
         label='菜单路径'
         control={control}
       />
+      <ModalField
+        name='menuIcon'
+        label='菜单图标'
+        control={control}
+        required={false}
+      />
       <ModalField
         name='menuOrderBy'
-        width='300px'
         label='菜单排序'
         control={control}
         type='number'

+ 21 - 43
packages/app/src/pages/menu/table/modal/hooks.ts

@@ -1,8 +1,7 @@
 import {addMenu, editMenu, getMenuInfo} from '@apis';
 import {yupResolver} from '@hookform/resolvers/yup';
-import {useQueryDataInfo} from '@hooks';
-import {useMutation} from '@tanstack/react-query';
-import {message} from 'antd';
+import {usePutData, useQueryDataInfo} from '@hooks';
+import {AddMenuParams} from '@models';
 import {useEffect} from 'react';
 import {useForm, useFormContext} from 'react-hook-form';
 import {object, string} from 'yup';
@@ -11,48 +10,16 @@ type FormState = {
   menuName: string;
   menuUrl: string;
   menuOrderBy: string;
+  menuIcon: string;
 };
 
 const validate = object({
   menuName: string().required('请输入菜单名称'),
   menuUrl: string().required('请输入菜单路径'),
   menuOrderBy: string().required('请输入菜单排序'),
+  menuIcon: string().required('请输入菜单图标'),
 });
 
-function useAdd(onClose: () => void, onFetch: () => void) {
-  const {isLoading, mutate} = useMutation(
-    addMenu,
-    {
-      onSuccess(data) {
-        if (data.msg === '200') {
-          onClose();
-          onFetch();
-          message.success('新增成功');
-        }
-      },
-    },
-  );
-
-  return [isLoading, mutate] as const;
-}
-
-function useEdit(onClose: () => void, onFetch: () => void) {
-  const {isLoading, mutate} = useMutation(
-    editMenu,
-    {
-      onSuccess(data) {
-        if (data.msg === '200') {
-          onClose();
-          onFetch();
-          message.success('修改成功');
-        }
-      },
-    },
-  );
-
-  return [isLoading, mutate] as const;
-}
-
 export function useFormState(
   {onClose, id, visible, onFetch}:
   {
@@ -71,18 +38,28 @@ export function useFormState(
     resolver: yupResolver(validate),
   });
   const {handleSubmit, clearErrors} = formInstance;
-  const [addIsLoading, addMutate] = useAdd(onClose, onFetch);
-  const [editIsLoaindg, editMutate] = useEdit(onClose, onFetch);
-  const isLoading = addIsLoading || editIsLoaindg;
+  const [isLoading, {addMutate, editMutate}] = usePutData({
+    addFn: addMenu,
+    editFn: editMenu,
+    onClose,
+    onFetch,
+  });
 
   useEffect(function() {
     visible && clearErrors();
   }, [clearErrors, visible]);
 
-  const onSubmit = handleSubmit(function({menuName, menuUrl, menuOrderBy}) {
+  const onSubmit = handleSubmit(function({menuName, menuUrl, menuOrderBy, menuIcon}) {
+    const params: AddMenuParams = {
+      name: menuName,
+      url: menuUrl,
+      pId: '0',
+      orderBy: menuOrderBy,
+      img: menuIcon,
+    };
     isEdit
-      ? editMutate({name: menuName, url: menuUrl, id, pId: '0', orderBy: menuOrderBy})
-      : addMutate({name: menuName, url: menuUrl, pId: '0', orderBy: menuOrderBy});
+      ? editMutate({id, ...params})
+      : addMutate(params);
   });
 
   return [{formInstance, isLoading}, onSubmit] as const;
@@ -105,5 +82,6 @@ export function useFormValues(id: string) {
     setValue('menuUrl', data?.url ?? '');
     setValue('menuName', data?.name ?? '');
     setValue('menuOrderBy', data?.orderBy ?? '0');
+    setValue('menuIcon', data?.img ?? '');
   }, [data, setValue]);
 }

+ 2 - 2
packages/app/src/pages/pda-menu/table/modal/hooks.ts

@@ -81,8 +81,8 @@ export function useFormState(
 
   const onSubmit = handleSubmit(function({menuName, menuID, menuOrderBy}) {
     isEdit
-      ? editMutate({name: menuName, url: menuID, id, pId: '0', orderBy: menuOrderBy})
-      : addMutate({name: menuName, url: menuID, pId: '0', orderBy: menuOrderBy});
+      ? editMutate({name: menuName, url: menuID, id, pId: '0', orderBy: menuOrderBy, img: ''})
+      : addMutate({name: menuName, url: menuID, pId: '0', orderBy: menuOrderBy, img: ''});
   });
 
   return [{formInstance, isLoading}, onSubmit] as const;

+ 2 - 0
packages/app/src/pages/product-in-stream/index.tsx

@@ -2,6 +2,7 @@ import {PageProvider, SearchProvider, TableSearchProvider} from '@components';
 import {FC} from 'react';
 import {context, contextState, pageContext, searchContext} from './context';
 import Filter from './filter';
+import TableList from './table';
 
 const ProductInStream: FC = function() {
   return (
@@ -10,6 +11,7 @@ const ProductInStream: FC = function() {
         <SearchProvider context={searchContext}>
           <section className='content-main'>
             <Filter />
+            <TableList />
           </section>
         </SearchProvider>
       </PageProvider>

+ 14 - 0
packages/app/src/pages/product-in-stream/table/hooks.ts

@@ -0,0 +1,14 @@
+import {getFinishProductInStreamList} from '@apis';
+import {useContextSection, useQueryTableList} from '@hooks';
+import {context, contextStateSelector, pageContext, searchContext} from '../context';
+
+export function useList() {
+  const params = useContextSection(context, contextStateSelector);
+
+  return useQueryTableList({
+    queryFn: getFinishProductInStreamList,
+    params,
+    pageContext,
+    searchContext,
+  });
+}

+ 37 - 0
packages/app/src/pages/product-in-stream/table/index.tsx

@@ -0,0 +1,37 @@
+import {FC} from 'react';
+import {useList} from './hooks';
+import {ColumnsType} from 'antd/es/table';
+import {FinishProductListStreamInListData} from '@models';
+import {Card} from 'antd';
+import {Table} from '@components';
+import {pageContext, searchContext} from '../context';
+
+const columns: ColumnsType<FinishProductListStreamInListData> = [
+  {title: '物料名称', dataIndex: 'materialName', key: 'materialName'},
+  {title: '生产批次', dataIndex: 'producBatch', key: 'producBatch'},
+  {title: '数量', dataIndex: 'capacity', key: 'capacity'},
+  {title: '用户名称', dataIndex: 'userName', key: 'userName'},
+  {title: '库位编号', dataIndex: 'storageLocationCode', key: 'storageLocationCode'},
+  {title: '入库时间', dataIndex: 'scrq', key: 'scrq'},
+  {title: '连翻号', dataIndex: 'serial', key: 'serial'},
+  {title: '序列号', dataIndex: 'seq', key: 'seq'},
+  {title: '属性', dataIndex: 'attribute', key: 'attribute'},
+];
+
+const TableList: FC = function() {
+  const [{data, count}] = useList();
+  return (
+    <Card className='table-wrapper'>
+      <Table
+        columns={columns}
+        data={data}
+        data-testid='product_in_stream_table'
+        count={count}
+        pageContext={pageContext}
+        searchContext={searchContext}
+      />
+    </Card>
+  );
+};
+
+export default TableList;

+ 2 - 2
packages/app/src/pages/product-out-stream/filter/hooks.ts

@@ -1,7 +1,7 @@
 import {useContextSection, useExportFile, usePage, useTableSearchState} from '@hooks';
 import {context, contextStateSelector, pageContext, searchContext} from '../context';
 import {useContextSelector} from 'use-context-selector';
-import {getFinishProductInStreamExport} from '@apis';
+import {getFinishProductOutStreamExport} from '@apis';
 
 export function useSearch(code: string, startTime: string, endTime: string) {
   const [isSearching] = useTableSearchState(searchContext);
@@ -16,7 +16,7 @@ export function useSearch(code: string, startTime: string, endTime: string) {
 
 export function useExport() {
   const params = useContextSelector(context, contextStateSelector);
-  const [isExporting, mutate] = useExportFile(getFinishProductInStreamExport);
+  const [isExporting, mutate] = useExportFile(getFinishProductOutStreamExport);
   const [{page, pageSize}] = usePage(pageContext);
 
   function onExport() {

+ 2 - 0
packages/app/src/pages/product-out-stream/index.tsx

@@ -2,6 +2,7 @@ import {PageProvider, SearchProvider, TableSearchProvider} from '@components';
 import {FC} from 'react';
 import {context, contextState, pageContext, searchContext} from './context';
 import Filter from './filter';
+import TableList from './table';
 
 const ProductOutStream: FC = function() {
   return (
@@ -10,6 +11,7 @@ const ProductOutStream: FC = function() {
         <SearchProvider context={searchContext}>
           <section className='content-main'>
             <Filter />
+            <TableList />
           </section>
         </SearchProvider>
       </PageProvider>

+ 14 - 0
packages/app/src/pages/product-out-stream/table/hooks.ts

@@ -0,0 +1,14 @@
+import {useContextSection, useQueryTableList} from '@hooks';
+import {context, contextStateSelector, pageContext, searchContext} from '../context';
+import {getFinishProductOutStreamList} from '@apis';
+
+export function useList() {
+  const params = useContextSection(context, contextStateSelector);
+
+  return useQueryTableList({
+    queryFn: getFinishProductOutStreamList,
+    params,
+    pageContext,
+    searchContext,
+  });
+}

+ 34 - 0
packages/app/src/pages/product-out-stream/table/index.tsx

@@ -0,0 +1,34 @@
+import {FC} from 'react';
+import {useList} from './hooks';
+import {ColumnsType} from 'antd/es/table';
+import {FinishProductListStreamOutListData} from '@models';
+import {Card} from 'antd';
+import {Table} from '@components';
+import {pageContext, searchContext} from '../context';
+
+const columns: ColumnsType<FinishProductListStreamOutListData> = [
+  {title: '物料名称', dataIndex: 'materialName', key: 'materialName'},
+  {title: '出库数量', dataIndex: 'num', key: 'num'},
+  {title: '用户名称', dataIndex: 'userName', key: 'userName'},
+  {title: '出库时间', dataIndex: 'scrq', key: 'scrq'},
+  {title: '公司名称', dataIndex: 'companyName', key: 'companyName'},
+  {title: '客户名称', dataIndex: 'customerName', key: 'customerName'},
+];
+
+const TableList: FC = function() {
+  const [{data, count}] = useList();
+  return (
+    <Card className='table-wrapper'>
+      <Table
+        columns={columns}
+        data={data}
+        data-testid='product_in_stream_table'
+        count={count}
+        pageContext={pageContext}
+        searchContext={searchContext}
+      />
+    </Card>
+  );
+};
+
+export default TableList;

+ 3 - 0
packages/app/src/pages/stock-operation/hooks.ts

@@ -9,6 +9,8 @@ type FormState = {
   operationNum: number;
   /** wbs编号 */
   wbsCode: string;
+  /** 库位编号 */
+  locationCode: string;
 };
 
 const validate = object({
@@ -22,6 +24,7 @@ export function useFormState() {
       materialCode: '',
       operationNum: 0,
       wbsCode: '',
+      locationCode: '',
     },
     resolver: yupResolver(validate),
   });

+ 8 - 1
packages/app/src/pages/stock-operation/index.tsx

@@ -5,12 +5,13 @@ import {useFormState} from './hooks';
 import Field from './field';
 import {ModalBtnGroup} from '@components';
 import Select from './select';
-import {useDictionaryOptions} from '@hooks';
+import {useDictionaryOptions, useStorageOptions} from '@hooks';
 
 const StockOperation: FC = function() {
   const {type} = useParams<{type: 'in' | 'out'}>();
   const [{control}, {onSubmit}] = useFormState();
   const options = useDictionaryOptions('物料字典');
+  const locationOptions = useStorageOptions();
 
   return (
     <section className='content-main'>
@@ -23,6 +24,12 @@ const StockOperation: FC = function() {
               name='materialCode'
               data={options}
             />
+            <Select
+              control={control}
+              label='所在仓库'
+              name='locationCode'
+              data={locationOptions}
+            />
             <Field
               control={control}
               label={type === 'in' ? '入库数量' : '出库数量'}

+ 9 - 0
packages/app/src/utils/sortMenu/index.test.ts

@@ -21,6 +21,7 @@ describe('sortMenu', function() {
       page: 0,
       limit: 0,
       orderBy: '',
+      img: '',
     }, {
       id: '23',
       name: '用户管理',
@@ -28,6 +29,7 @@ describe('sortMenu', function() {
       pId: '15',
       page: 0,
       limit: 0,
+      img: '',
       orderBy: '',
     }, {
       id: '26',
@@ -36,6 +38,7 @@ describe('sortMenu', function() {
       pId: '25',
       page: 0,
       limit: 0,
+      img: '',
       orderBy: '',
     }, {
       id: '15',
@@ -44,6 +47,7 @@ describe('sortMenu', function() {
       pId: '0',
       page: 0,
       limit: 0,
+      img: '',
       orderBy: '',
     }, {
       id: '16',
@@ -52,6 +56,7 @@ describe('sortMenu', function() {
       pId: '15',
       page: 0,
       limit: 0,
+      img: '',
       orderBy: '',
     }, {
       id: '27',
@@ -60,6 +65,7 @@ describe('sortMenu', function() {
       pId: '25',
       page: 0,
       limit: 0,
+      img: '',
       orderBy: '',
     }, {
       id: '24',
@@ -68,6 +74,7 @@ describe('sortMenu', function() {
       pId: '15',
       page: 0,
       limit: 0,
+      img: '',
       orderBy: '',
     }, {
       id: '25',
@@ -76,6 +83,7 @@ describe('sortMenu', function() {
       pId: '0',
       page: 0,
       limit: 0,
+      img: '',
       orderBy: '',
     }, {
       id: '13',
@@ -84,6 +92,7 @@ describe('sortMenu', function() {
       pId: '0',
       page: 0,
       limit: 0,
+      img: '',
       orderBy: '',
     }]);
 

+ 11 - 2
packages/app/src/utils/sortMenu/index.ts

@@ -1,4 +1,5 @@
 import {TreeRoleMenuData} from '@models';
+import {ReactNode} from 'react';
 
 export type ParseMenuType = {
   key: string,
@@ -6,6 +7,7 @@ export type ParseMenuType = {
   label: string,
   pid: string;
   url: string;
+  icon: ReactNode,
   children?: ParseMenuType[],
 };
 
@@ -14,8 +16,15 @@ export function sortMenu(menus: TreeRoleMenuData[]) {
         childMenu: ParseMenuType[] = [];
 
   // 拆分一级菜单和二级菜单
-  menus.forEach(function({id, pId, name, url}) {
-    const data = {key: id, title: name, label: name, pid: pId, url};
+  menus.forEach(function({id, pId, name, url, img}) {
+    const data = {
+      key: id,
+      title: name,
+      label: name,
+      pid: pId,
+      url,
+      icon: img && <i className={`fa-solid fa-${img}`} />,
+    };
 
     pId === '0'
       ? topMenu.push(data)