Преглед изворни кода

feat(merge): merge xyh

feat(菜单管理): 修改菜单Icon大小
Lind пре 3 година
родитељ
комит
8e97501c7d

+ 1 - 1
config/defaultSettings.ts

@@ -18,7 +18,7 @@ const Settings: LayoutSettings & {
   title: 'Jetlinks',
   pwa: false,
   logo: '/logo.svg',
-  iconfontUrl: '',
+  iconfontUrl: '/icons/iconfont.js',
 };
 
 export default Settings;

+ 212 - 0
public/icons/iconfont.css

@@ -0,0 +1,212 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 3183515 */
+  src: url('iconfont.woff2?t=1649052654664') format('woff2'),
+       url('iconfont.woff?t=1649052654664') format('woff'),
+       url('iconfont.ttf?t=1649052654664') format('truetype'),
+       url('iconfont.svg?t=1649052654664#iconfont') format('svg');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-shujumoni:before {
+  content: "\e665";
+}
+
+.icon-tongzhiguanli:before {
+  content: "\e666";
+}
+
+.icon-rizhifuwu:before {
+  content: "\e667";
+}
+
+.icon-keshihua:before {
+  content: "\e668";
+}
+
+.icon-yuanchengshengji:before {
+  content: "\e669";
+}
+
+.icon-shebei:before {
+  content: "\e66a";
+}
+
+.icon-wangluozujian:before {
+  content: "\e66b";
+}
+
+.icon-shipinwangguan:before {
+  content: "\e66c";
+}
+
+.icon-wumoxing:before {
+  content: "\e66d";
+}
+
+.icon-wangguanzishebei:before {
+  content: "\e66e";
+}
+
+.icon-zhihuishequ:before {
+  content: "\e66f";
+}
+
+.icon-chanpinfenlei:before {
+  content: "\e670";
+}
+
+.icon-zhengshuguanli:before {
+  content: "\e671";
+}
+
+.icon-zhihuigongye:before {
+  content: "\e672";
+}
+
+.icon-chanpin:before {
+  content: "\e673";
+}
+
+.icon-changjingliandong:before {
+  content: "\e674";
+}
+
+.icon-zhilianshebei:before {
+  content: "\e676";
+}
+
+.icon-zidingyiguize:before {
+  content: "\e677";
+}
+
+.icon-zhihuichengshi:before {
+  content: "\e678";
+}
+
+.icon-chajianguanli:before {
+  content: "\e679";
+}
+
+.icon-zhihuiyuanqu:before {
+  content: "\e67a";
+}
+
+.icon-Subtract:before {
+  content: "\e664";
+}
+
+.icon-a-Frame2125:before {
+  content: "\e660";
+}
+
+.icon-setting1:before {
+  content: "\e661";
+}
+
+.icon-a-Frame2126:before {
+  content: "\e662";
+}
+
+.icon-caozuo1:before {
+  content: "\e663";
+}
+
+.icon-tiaoshi:before {
+  content: "\e65f";
+}
+
+.icon-caozuo:before {
+  content: "\e65a";
+}
+
+.icon-colum-height1:before {
+  content: "\e65b";
+}
+
+.icon-reload1:before {
+  content: "\e65c";
+}
+
+.icon-setting:before {
+  content: "\e65d";
+}
+
+.icon-a-Group1451:before {
+  content: "\e65e";
+}
+
+.icon-colum-height-1:before {
+  content: "\e649";
+}
+
+.icon-bianji:before {
+  content: "\e64a";
+}
+
+.icon-bianji-1:before {
+  content: "\e64b";
+}
+
+.icon-colum-height:before {
+  content: "\e64c";
+}
+
+.icon-fullscreen-1:before {
+  content: "\e64d";
+}
+
+.icon-reload-1:before {
+  content: "\e64e";
+}
+
+.icon-reload:before {
+  content: "\e64f";
+}
+
+.icon-setting-1:before {
+  content: "\e650";
+}
+
+.icon-setting-2:before {
+  content: "\e651";
+}
+
+.icon-setting-4:before {
+  content: "\e652";
+}
+
+.icon-setting-3:before {
+  content: "\e653";
+}
+
+.icon-setting-7:before {
+  content: "\e654";
+}
+
+.icon-setting-9:before {
+  content: "\e655";
+}
+
+.icon-fullscreen:before {
+  content: "\e656";
+}
+
+.icon-setting-6:before {
+  content: "\e657";
+}
+
+.icon-setting-5:before {
+  content: "\e658";
+}
+
+.icon-setting-8:before {
+  content: "\e659";
+}
+

Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
public/icons/iconfont.js


+ 352 - 0
public/icons/iconfont.json

@@ -0,0 +1,352 @@
+{
+  "id": "3183515",
+  "name": "2.0",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon-",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "28693148",
+      "name": "数据模拟",
+      "font_class": "shujumoni",
+      "unicode": "e665",
+      "unicode_decimal": 58981
+    },
+    {
+      "icon_id": "28693150",
+      "name": "通知管理",
+      "font_class": "tongzhiguanli",
+      "unicode": "e666",
+      "unicode_decimal": 58982
+    },
+    {
+      "icon_id": "28693151",
+      "name": "日志服务",
+      "font_class": "rizhifuwu",
+      "unicode": "e667",
+      "unicode_decimal": 58983
+    },
+    {
+      "icon_id": "28693152",
+      "name": "可视化",
+      "font_class": "keshihua",
+      "unicode": "e668",
+      "unicode_decimal": 58984
+    },
+    {
+      "icon_id": "28693153",
+      "name": "远程升级",
+      "font_class": "yuanchengshengji",
+      "unicode": "e669",
+      "unicode_decimal": 58985
+    },
+    {
+      "icon_id": "28693154",
+      "name": "设备",
+      "font_class": "shebei",
+      "unicode": "e66a",
+      "unicode_decimal": 58986
+    },
+    {
+      "icon_id": "28693155",
+      "name": "网络组件",
+      "font_class": "wangluozujian",
+      "unicode": "e66b",
+      "unicode_decimal": 58987
+    },
+    {
+      "icon_id": "28693156",
+      "name": "视频网关",
+      "font_class": "shipinwangguan",
+      "unicode": "e66c",
+      "unicode_decimal": 58988
+    },
+    {
+      "icon_id": "28693157",
+      "name": "物模型",
+      "font_class": "wumoxing",
+      "unicode": "e66d",
+      "unicode_decimal": 58989
+    },
+    {
+      "icon_id": "28693158",
+      "name": "网关子设备",
+      "font_class": "wangguanzishebei",
+      "unicode": "e66e",
+      "unicode_decimal": 58990
+    },
+    {
+      "icon_id": "28693159",
+      "name": "智慧社区",
+      "font_class": "zhihuishequ",
+      "unicode": "e66f",
+      "unicode_decimal": 58991
+    },
+    {
+      "icon_id": "28693164",
+      "name": "产品分类",
+      "font_class": "chanpinfenlei",
+      "unicode": "e670",
+      "unicode_decimal": 58992
+    },
+    {
+      "icon_id": "28693165",
+      "name": "证书管理",
+      "font_class": "zhengshuguanli",
+      "unicode": "e671",
+      "unicode_decimal": 58993
+    },
+    {
+      "icon_id": "28693166",
+      "name": "智慧工业",
+      "font_class": "zhihuigongye",
+      "unicode": "e672",
+      "unicode_decimal": 58994
+    },
+    {
+      "icon_id": "28693167",
+      "name": "产品",
+      "font_class": "chanpin",
+      "unicode": "e673",
+      "unicode_decimal": 58995
+    },
+    {
+      "icon_id": "28693168",
+      "name": "场景联动",
+      "font_class": "changjingliandong",
+      "unicode": "e674",
+      "unicode_decimal": 58996
+    },
+    {
+      "icon_id": "28693171",
+      "name": "直连设备",
+      "font_class": "zhilianshebei",
+      "unicode": "e676",
+      "unicode_decimal": 58998
+    },
+    {
+      "icon_id": "28693173",
+      "name": "自定义规则",
+      "font_class": "zidingyiguize",
+      "unicode": "e677",
+      "unicode_decimal": 58999
+    },
+    {
+      "icon_id": "28693174",
+      "name": "智慧城市",
+      "font_class": "zhihuichengshi",
+      "unicode": "e678",
+      "unicode_decimal": 59000
+    },
+    {
+      "icon_id": "28693175",
+      "name": "插件管理",
+      "font_class": "chajianguanli",
+      "unicode": "e679",
+      "unicode_decimal": 59001
+    },
+    {
+      "icon_id": "28693176",
+      "name": "智慧园区",
+      "font_class": "zhihuiyuanqu",
+      "unicode": "e67a",
+      "unicode_decimal": 59002
+    },
+    {
+      "icon_id": "27891438",
+      "name": "重新下发",
+      "font_class": "Subtract",
+      "unicode": "e664",
+      "unicode_decimal": 58980
+    },
+    {
+      "icon_id": "27891166",
+      "name": "消息",
+      "font_class": "a-Frame2125",
+      "unicode": "e660",
+      "unicode_decimal": 58976
+    },
+    {
+      "icon_id": "27891167",
+      "name": "下载",
+      "font_class": "setting1",
+      "unicode": "e661",
+      "unicode_decimal": 58977
+    },
+    {
+      "icon_id": "27891168",
+      "name": "发布",
+      "font_class": "a-Frame2126",
+      "unicode": "e662",
+      "unicode_decimal": 58978
+    },
+    {
+      "icon_id": "27891170",
+      "name": "通知记录",
+      "font_class": "caozuo1",
+      "unicode": "e663",
+      "unicode_decimal": 58979
+    },
+    {
+      "icon_id": "27891146",
+      "name": "调试",
+      "font_class": "tiaoshi",
+      "unicode": "e65f",
+      "unicode_decimal": 58975
+    },
+    {
+      "icon_id": "27612044",
+      "name": "更多",
+      "font_class": "caozuo",
+      "unicode": "e65a",
+      "unicode_decimal": 58970
+    },
+    {
+      "icon_id": "27612045",
+      "name": "展开",
+      "font_class": "colum-height1",
+      "unicode": "e65b",
+      "unicode_decimal": 58971
+    },
+    {
+      "icon_id": "27612046",
+      "name": "展开",
+      "font_class": "reload1",
+      "unicode": "e65c",
+      "unicode_decimal": 58972
+    },
+    {
+      "icon_id": "27612049",
+      "name": "展开",
+      "font_class": "setting",
+      "unicode": "e65d",
+      "unicode_decimal": 58973
+    },
+    {
+      "icon_id": "27612050",
+      "name": "帮助",
+      "font_class": "a-Group1451",
+      "unicode": "e65e",
+      "unicode_decimal": 58974
+    },
+    {
+      "icon_id": "27611908",
+      "name": "上移",
+      "font_class": "colum-height-1",
+      "unicode": "e649",
+      "unicode_decimal": 58953
+    },
+    {
+      "icon_id": "27611909",
+      "name": "编辑",
+      "font_class": "bianji",
+      "unicode": "e64a",
+      "unicode_decimal": 58954
+    },
+    {
+      "icon_id": "27611910",
+      "name": "处理",
+      "font_class": "bianji-1",
+      "unicode": "e64b",
+      "unicode_decimal": 58955
+    },
+    {
+      "icon_id": "27611911",
+      "name": "等高",
+      "font_class": "colum-height",
+      "unicode": "e64c",
+      "unicode_decimal": 58956
+    },
+    {
+      "icon_id": "27611912",
+      "name": "下移",
+      "font_class": "fullscreen-1",
+      "unicode": "e64d",
+      "unicode_decimal": 58957
+    },
+    {
+      "icon_id": "27611913",
+      "name": "解绑",
+      "font_class": "reload-1",
+      "unicode": "e64e",
+      "unicode_decimal": 58958
+    },
+    {
+      "icon_id": "27611914",
+      "name": "刷新",
+      "font_class": "reload",
+      "unicode": "e64f",
+      "unicode_decimal": 58959
+    },
+    {
+      "icon_id": "27611915",
+      "name": "添加",
+      "font_class": "setting-1",
+      "unicode": "e650",
+      "unicode_decimal": 58960
+    },
+    {
+      "icon_id": "27611916",
+      "name": "资产",
+      "font_class": "setting-2",
+      "unicode": "e651",
+      "unicode_decimal": 58961
+    },
+    {
+      "icon_id": "27611917",
+      "name": "删除",
+      "font_class": "setting-4",
+      "unicode": "e652",
+      "unicode_decimal": 58962
+    },
+    {
+      "icon_id": "27611918",
+      "name": "人员",
+      "font_class": "setting-3",
+      "unicode": "e653",
+      "unicode_decimal": 58963
+    },
+    {
+      "icon_id": "27611919",
+      "name": "查看",
+      "font_class": "setting-7",
+      "unicode": "e654",
+      "unicode_decimal": 58964
+    },
+    {
+      "icon_id": "27611920",
+      "name": "禁用",
+      "font_class": "setting-9",
+      "unicode": "e655",
+      "unicode_decimal": 58965
+    },
+    {
+      "icon_id": "27611921",
+      "name": "最大化",
+      "font_class": "fullscreen",
+      "unicode": "e656",
+      "unicode_decimal": 58966
+    },
+    {
+      "icon_id": "27611922",
+      "name": "日历",
+      "font_class": "setting-6",
+      "unicode": "e657",
+      "unicode_decimal": 58967
+    },
+    {
+      "icon_id": "27611924",
+      "name": "属性配置",
+      "font_class": "setting-5",
+      "unicode": "e658",
+      "unicode_decimal": 58968
+    },
+    {
+      "icon_id": "27611925",
+      "name": "按钮管理",
+      "font_class": "setting-8",
+      "unicode": "e659",
+      "unicode_decimal": 58969
+    }
+  ]
+}

Разлика између датотеке није приказан због своје велике величине
+ 117 - 0
public/icons/iconfont.svg


BIN
public/icons/iconfont.ttf


BIN
public/icons/iconfont.woff


BIN
public/icons/iconfont.woff2


+ 0 - 29
src/components/ProTableCard/CardItems/device.tsx

@@ -47,34 +47,5 @@ export default (props: DeviceCardProps) => {
         </div>
       </div>
     </TableCard>
-    // <Card style={{ width: '100%' }} cover={null} actions={props.actions}>
-    //   <div className={'pro-table-card-item'}>
-    //     <div className={'card-item-avatar'}>
-    //       <Avatar size={props.avatarSize || 64} src={props.photoUrl} />
-    //     </div>
-    //     <div className={'card-item-body'}>
-    //       <div className={'card-item-header'}>
-    //         <span className={'card-item-header-name ellipsis'}>{props.name}</span>
-    //         <BadgeStatus
-    //           status={props.state.value}
-    //           text={props.state.text}
-    //           statusNames={{
-    //             online: StatusColorEnum.success,
-    //             offline: StatusColorEnum.error,
-    //             notActive: StatusColorEnum.processing,
-    //           }}
-    //         />
-    //       </div>
-    //       <div className={'card-item-content'}>
-    //         <label>设备类型:</label>
-    //         <span className={'ellipsis'}>{props.deviceType ? props.deviceType.text : '--'}</span>
-    //       </div>
-    //       <div className={'card-item-content'}>
-    //         <label>产品名称:</label>
-    //         <span className={'ellipsis'}>{props.productName || '--'}</span>
-    //       </div>
-    //     </div>
-    //   </div>
-    // </Card>
   );
 };

+ 28 - 36
src/components/ProTableCard/CardItems/product.tsx

@@ -1,62 +1,54 @@
-import { Avatar, Card } from 'antd';
 import React from 'react';
 import type { ProductItem } from '@/pages/device/Product/typings';
-import { BadgeStatus } from '@/components';
 import { StatusColorEnum } from '@/components/BadgeStatus';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import { ModelEnum } from '@/pages/device/Product/Detail';
-import { Link, useIntl } from 'umi';
+import { useIntl } from 'umi';
+import { TableCard } from '@/components';
+import '@/style/common.less';
 import '../index.less';
 
 export interface ProductCardProps extends ProductItem {
+  detail?: React.ReactNode;
   actions?: React.ReactNode[];
   avatarSize?: number;
 }
+const defaultImage = require('/public/images/device-type-3-big.png');
 
 export default (props: ProductCardProps) => {
   const intl = useIntl();
-
   return (
-    <Card style={{ width: '100%' }} cover={null} actions={props.actions}>
+    <TableCard
+      detail={props.detail}
+      actions={props.actions}
+      status={props.state}
+      statusText={intl.formatMessage({
+        id: `pages.system.tenant.assetInformation.${props.state ? 'published' : 'unpublished'}`,
+        defaultMessage: '已发布',
+      })}
+      statusNames={{
+        0: StatusColorEnum.error,
+        1: StatusColorEnum.processing,
+      }}
+    >
       <div className={'pro-table-card-item'}>
         <div className={'card-item-avatar'}>
-          <Avatar size={props.avatarSize || 64} src={props.photoUrl} />
+          <img width={88} height={88} src={props.photoUrl || defaultImage} alt={''} />
         </div>
         <div className={'card-item-body'}>
           <div className={'card-item-header'}>
             <span className={'card-item-header-name ellipsis'}>{props.name}</span>
-            <BadgeStatus
-              status={props.state}
-              text={intl.formatMessage({
-                id: `pages.system.tenant.assetInformation.${
-                  props.state ? 'published' : 'unpublished'
-                }`,
-                defaultMessage: '已发布',
-              })}
-              statusNames={{
-                0: StatusColorEnum.error,
-                1: StatusColorEnum.processing,
-              }}
-            />
-          </div>
-          <div className={'card-item-content'}>
-            <label>设备类型:</label>
-            <span className={'ellipsis'}>{props.deviceType ? props.deviceType.text : '--'}</span>
           </div>
           <div className={'card-item-content'}>
-            <label>接入方式:</label>
-            <span className={'ellipsis'}>
-              <Link
-                to={`${getMenuPathByParams(MENUS_CODE['device/Product/Detail'], props.id)}?type=${
-                  ModelEnum.access
-                }`}
-              >
-                {props.transportProtocol || '--'}
-              </Link>
-            </span>
+            <div>
+              <label>设备类型</label>
+              <div className={'ellipsis'}>{props.deviceType ? props.deviceType.text : '--'}</div>
+            </div>
+            <div>
+              <label>接入方式</label>
+              <div className={'ellipsis'}>{props.transportProtocol || '--'}</div>
+            </div>
           </div>
         </div>
       </div>
-    </Card>
+    </TableCard>
   );
 };

+ 4 - 4
src/components/ProTableCard/TableCard.tsx

@@ -35,16 +35,16 @@ function getAction(actions: React.ReactNode[]) {
 export default (props: TableCardProps) => {
   const [maskShow, setMaskShow] = useState(false);
 
-  const handleStatusColor = (): StatusColorType | undefined => {
-    if ('statusNames' in props && props.status) {
-      return props.statusNames![props.status];
+  const handleStatusColor = (data: TableCardProps): StatusColorType | undefined => {
+    if ('statusNames' in data && data.status !== undefined) {
+      return data.statusNames![data.status];
     }
     return StatusColorEnum.default;
   };
 
   const statusNode =
     props.showStatus === false ? null : (
-      <div className={classNames('card-state', handleStatusColor())}>
+      <div className={classNames('card-state', handleStatusColor(props))}>
         <div className={'card-state-content'}>
           <BadgeStatus
             status={props.status !== undefined ? props.status : ''}

+ 1 - 1
src/components/Upload/Image/index.less

@@ -1,4 +1,4 @@
-@import '../../../../node_modules/antd/lib/style/themes/variable';
+@import '~antd/es/style/themes/default.less';
 
 @border: 1px dashed @border-color-base;
 @mask-color: rgba(#000, 0.35);

+ 92 - 1
src/pages/device/Product/index.tsx

@@ -343,7 +343,98 @@ const Product = observer(() => {
             <Button style={{ marginLeft: 12 }}>导入</Button>
           </Upload>,
         ]}
-        cardRender={(record) => <ProductCard {...record} actions={tools(record)} />}
+        cardRender={(record) => (
+          <ProductCard
+            {...record}
+            detail={
+              <div
+                style={{ fontSize: 18, padding: 8 }}
+                onClick={() => {
+                  productModel.current = record;
+                  history.push(
+                    `${getMenuPathByParams(MENUS_CODE['device/Product/Detail'], record.id)}`,
+                  );
+                }}
+              >
+                <EyeOutlined />
+              </div>
+            }
+            actions={[
+              <Button
+                key="edit"
+                onClick={() => {
+                  setCurrent(record);
+                  setVisible(true);
+                }}
+                type={'link'}
+                style={{ padding: 0 }}
+              >
+                <EditOutlined />
+                {intl.formatMessage({
+                  id: 'pages.data.option.edit',
+                  defaultMessage: '编辑',
+                })}
+              </Button>,
+              <Button type={'link'} key={'download'} style={{ padding: 0 }}>
+                <DownloadOutlined
+                  onClick={async () => {
+                    downloadObject(
+                      record,
+                      intl.formatMessage({
+                        id: 'pages.device.product',
+                        defaultMessage: '产品',
+                      }),
+                    );
+                    message.success('操作成功');
+                  }}
+                />
+                {intl.formatMessage({
+                  id: 'pages.data.option.download',
+                  defaultMessage: '下载',
+                })}
+              </Button>,
+              <Popconfirm
+                key={'state'}
+                title={intl.formatMessage({
+                  id: `pages.data.option.${record.state ? 'disabled' : 'enabled'}.tips`,
+                  defaultMessage: '是否启用?',
+                })}
+                onConfirm={() => {
+                  changeDeploy(record.id, record.state ? 'undeploy' : 'deploy');
+                }}
+              >
+                <Button style={{ padding: 0 }} type={'link'}>
+                  {record.state ? <StopOutlined /> : <PlayCircleOutlined />}
+                  {intl.formatMessage({
+                    id: `pages.data.option.${record.state ? 'disabled' : 'enabled'}`,
+                    defaultMessage: record.state ? '禁用' : '启用',
+                  })}
+                </Button>
+              </Popconfirm>,
+              <Popconfirm
+                key="delete"
+                title={intl.formatMessage({
+                  id:
+                    record.state === 1
+                      ? 'pages.device.productDetail.deleteTip'
+                      : 'page.table.isDelete',
+                  defaultMessage: '是否删除?',
+                })}
+                onConfirm={async () => {
+                  if (record.state === 0) {
+                    await deleteItem(record.id);
+                  } else {
+                    message.error('已发布的产品不能进行删除操作');
+                  }
+                }}
+              >
+                <Button type={'link'} style={{ padding: 0 }}>
+                  <DeleteOutlined />
+                </Button>
+              </Popconfirm>,
+            ]}
+          />
+        )}
       />
       <Save
         model={!current ? 'add' : 'edit'}

+ 4 - 3
src/pages/system/Menu/Detail/edit.tsx

@@ -20,7 +20,7 @@ import { useHistory, useRequest } from 'umi';
 import type { MenuItem } from '@/pages/system/Menu/typing';
 // import { debounce } from 'lodash';
 import Title from '../components/Title';
-import { UploadImage } from '@/components';
+import Icons from '../components/Icons';
 import { QuestionCircleFilled } from '@ant-design/icons';
 import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 
@@ -137,11 +137,12 @@ export default (props: EditProps) => {
                 required={true}
                 rules={[{ required: true, message: '请上传图标' }]}
               >
-                <UploadImage
+                {/* <UploadImage
                   types={['image/png']}
                   disabled={disabled}
                   style={{ width: 140, height: 130 }}
-                />
+                /> */}
+                <Icons disabled={disabled} />
               </Form.Item>
             </Col>
             <Col span={21}>

+ 25 - 0
src/pages/system/Menu/components/Icons/icon.ts

@@ -0,0 +1,25 @@
+export default [
+  'icon-shujumoni',
+  'icon-tongzhiguanli',
+  'icon-rizhifuwu',
+  'icon-keshihua',
+  'icon-yuanchengshengji',
+  'icon-shebei',
+  'icon-wangluozujian',
+  'icon-shipinwangguan',
+  'icon-wumoxing',
+  'icon-wangguanzishebei',
+  'icon-zhihuishequ',
+  'icon-chanpinfenlei',
+  'icon-zhengshuguanli',
+  'icon-zhihuigongye',
+  'icon-chanpin',
+  'icon-changjingliandong',
+  'icon-zhilianshebei',
+  'icon-zidingyiguize',
+  'icon-zhihuichengshi',
+  'icon-chajianguanli',
+  'icon-zhihuiyuanqu',
+  'icon-Subtract',
+  'icon-a-Frame2125',
+]

+ 111 - 0
src/pages/system/Menu/components/Icons/index.less

@@ -0,0 +1,111 @@
+@import '~antd/es/style/themes/default.less';
+
+@border: 1px dashed @border-color-base;
+@mask-color: rgba(#000, 0.35);
+@with: 160px;
+@height: 150px;
+
+.flex-center() {
+  align-items: center;
+  justify-content: center;
+}
+
+.menu-icon {
+  display: flex;
+
+  .menu-icon-border {
+    position: relative;
+    overflow: hidden;
+    border: @border;
+    transition: all 0.3s;
+
+    &:hover {
+      border-color: @primary-color-hover;
+    }
+
+    .menu-icon-disabled {
+      position: absolute;
+      top: 0;
+      left: 0;
+      z-index: 2;
+      width: 100%;
+      height: 100%;
+      background-color: rgba(#000, 0.1);
+      cursor: not-allowed;
+    }
+
+    .menu-icon-content {
+      .flex-center();
+
+      position: relative;
+      display: flex;
+      flex-direction: column;
+      width: @with;
+      height: @height;
+      padding: 8px;
+      background-color: rgba(#000, 0.06);
+      cursor: pointer;
+      > span {
+        font-size: 30px;
+      }
+
+      .menu-select-icon {
+        font-size: 90px;
+      }
+
+      .menu-icon-mask {
+        .flex-center();
+
+        position: absolute;
+        top: 0;
+        left: 0;
+        display: none;
+        width: 100%;
+        height: 100%;
+        color: #fff;
+        font-size: 16px;
+        background-color: @mask-color;
+
+        &.show {
+          display: flex;
+        }
+      }
+    }
+  }
+}
+
+.menu-icon-items {
+  display: grid;
+  grid-gap: 20px;
+  grid-template-columns: repeat(6, 1fr);
+  max-height: 500px;
+  overflow-y: auto;
+
+  .icon-item {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 100px;
+    font-size: 40px;
+    border: 2px solid #efefef;
+    border-radius: 2px;
+    cursor: pointer;
+
+    &:hover {
+      border-color: @primary-color-hover;
+    }
+
+    &.active {
+      color: @primary-color-active;
+      border-color: @primary-color-active;
+    }
+  }
+}
+
+.icon {
+  width: 1em;
+  height: 1em;
+  overflow: hidden;
+  vertical-align: -0.15em;
+  fill: currentColor;
+}

+ 87 - 0
src/pages/system/Menu/components/Icons/index.tsx

@@ -0,0 +1,87 @@
+import { useState, useCallback, useEffect } from 'react';
+import IconList from './icon';
+import { Modal } from 'antd';
+import classNames from 'classnames';
+import { PlusOutlined } from '@ant-design/icons';
+import './index.less';
+
+export interface IconProps {
+  value?: string;
+  onChange?: (icon: string) => void;
+  disabled?: boolean;
+}
+
+export default (props: IconProps) => {
+  const [visible, setVisible] = useState(false);
+  const [icon, setIcon] = useState(props.value || '');
+  const [showMask, setShowMask] = useState(false);
+
+  useEffect(() => {
+    setIcon(props.value || '');
+  }, [visible]);
+
+  const closeModal = () => {
+    setVisible(false);
+  };
+
+  const onSubmit = useCallback(() => {
+    if (props.onChange) {
+      props.onChange(icon);
+    }
+    closeModal();
+  }, [icon]);
+
+  return (
+    <div className="menu-icon">
+      <div className="menu-icon-border">
+        <Modal title="菜单图标" visible={visible} width={800} onOk={onSubmit} onCancel={closeModal}>
+          <div className="menu-icon-items">
+            {IconList.map((item) => (
+              <div
+                className={classNames('icon-item', { active: icon === item })}
+                onClick={() => {
+                  setIcon(item);
+                }}
+              >
+                <svg className="icon" aria-hidden="true">
+                  <use xlinkHref={`#${item}`}></use>
+                </svg>
+              </div>
+            ))}
+          </div>
+        </Modal>
+        <div
+          className="menu-icon-content"
+          onClick={() => {
+            if (!props.disabled) {
+              setVisible(true);
+            }
+          }}
+          onMouseEnter={() => {
+            setShowMask(true);
+          }}
+          onMouseLeave={() => {
+            setShowMask(false);
+          }}
+        >
+          {props.value ? (
+            <>
+              <span className="menu-select-icon">
+                <svg className="icon" aria-hidden="true">
+                  <use xlinkHref={`#${props.value}`}></use>
+                </svg>
+              </span>
+              <div className={classNames('menu-icon-mask', { show: showMask })}>点击修改</div>
+            </>
+          ) : (
+            <>
+              <PlusOutlined />
+              <div>点击选择图标</div>
+            </>
+          )}
+        </div>
+        {props.disabled && <div className="menu-icon-disabled"></div>}
+      </div>
+    </div>
+  );
+};