From 7791e469495e0455e6bd48c29d99100a008978d9 Mon Sep 17 00:00:00 2001
From: powersir <1576775122@qq.com>
Date: Tue, 12 Sep 2023 00:06:43 +0800
Subject: [PATCH] layout struct
---
README.md | 63 +-
index.html | 2 +-
package.json | 1 +
public/images/login-right-after.svg | 69 ++
public/images/login-right-before.svg | 65 ++
public/images/login-right-bg.svg | 39 ++
src/App.tsx | 86 ++-
src/assets/antd-icons/index.tsx | 846 ++++++++++++++++++++++++
src/assets/css/overwrite.css | 148 +++++
src/assets/icons/3.tsx | 22 +
src/assets/icons/buguang.tsx | 22 +
src/assets/icons/fangdajing.tsx | 22 +
src/assets/icons/jiaretaiyang.tsx | 22 +
src/assets/icons/moon.tsx | 22 +
src/assets/icons/shuyi_fanyi-36.tsx | 22 +
src/assets/icons/sun.tsx | 22 +
src/assets/locales/en-US.ts | 78 +++
src/assets/locales/zh-CN.ts | 78 +++
src/assets/react.svg | 1 -
src/components/draggable-tab/index.css | 15 +
src/components/draggable-tab/index.tsx | 88 +++
src/components/global-loading/index.css | 47 ++
src/components/global-loading/index.tsx | 13 +
src/components/loading/index.tsx | 20 +
src/default-setting.ts | 14 +
src/hooks/use-match-router/index.tsx | 47 ++
src/hooks/use-pc-screen/index.tsx | 6 +
src/hooks/use-request/index.ts | 77 +++
src/hooks/use-tabs/index.tsx | 106 +++
src/layout/404.tsx | 17 +
src/layout/content/index.tsx | 41 ++
src/layout/content/tab.tsx | 0
src/layout/header/index.tsx | 175 +++++
src/layout/index.css | 24 +
src/layout/index.tsx | 168 +++++
src/layout/slide/index.tsx | 77 +++
src/layout/slide/menus.tsx | 311 +++++++++
src/layout/tabs-context.tsx | 18 +
src/layout/tabs-layout.tsx | 124 ++++
src/main.tsx | 24 +-
src/models/index.ts | 6 +
src/models/user.ts | 108 +++
src/pages/login/index.css | 43 ++
src/pages/login/index.tsx | 148 +++++
src/request/index.ts | 176 +++++
src/request/service/login.ts | 15 +
src/request/service/user.ts | 5 +
src/router/router-error-element.tsx | 8 +
src/router/router.tsx | 89 +++
src/store/global/index.ts | 53 ++
src/store/global/user.ts | 24 +
src/store/index.ts | 0
src/utils/antd.ts | 26 +
src/utils/i18n.ts | 27 +
src/utils/utils.ts | 12 +
tsconfig.json | 6 +-
56 files changed, 3732 insertions(+), 56 deletions(-)
create mode 100644 public/images/login-right-after.svg
create mode 100644 public/images/login-right-before.svg
create mode 100644 public/images/login-right-bg.svg
create mode 100644 src/assets/antd-icons/index.tsx
create mode 100644 src/assets/css/overwrite.css
create mode 100644 src/assets/icons/3.tsx
create mode 100644 src/assets/icons/buguang.tsx
create mode 100644 src/assets/icons/fangdajing.tsx
create mode 100644 src/assets/icons/jiaretaiyang.tsx
create mode 100644 src/assets/icons/moon.tsx
create mode 100644 src/assets/icons/shuyi_fanyi-36.tsx
create mode 100644 src/assets/icons/sun.tsx
create mode 100644 src/assets/locales/en-US.ts
create mode 100644 src/assets/locales/zh-CN.ts
delete mode 100644 src/assets/react.svg
create mode 100644 src/components/draggable-tab/index.css
create mode 100644 src/components/draggable-tab/index.tsx
create mode 100644 src/components/global-loading/index.css
create mode 100644 src/components/global-loading/index.tsx
create mode 100644 src/components/loading/index.tsx
create mode 100644 src/default-setting.ts
create mode 100644 src/hooks/use-match-router/index.tsx
create mode 100644 src/hooks/use-pc-screen/index.tsx
create mode 100644 src/hooks/use-request/index.ts
create mode 100644 src/hooks/use-tabs/index.tsx
create mode 100644 src/layout/404.tsx
create mode 100644 src/layout/content/index.tsx
create mode 100644 src/layout/content/tab.tsx
create mode 100644 src/layout/header/index.tsx
create mode 100644 src/layout/index.css
create mode 100644 src/layout/index.tsx
create mode 100644 src/layout/slide/index.tsx
create mode 100644 src/layout/slide/menus.tsx
create mode 100644 src/layout/tabs-context.tsx
create mode 100644 src/layout/tabs-layout.tsx
create mode 100644 src/models/index.ts
create mode 100644 src/models/user.ts
create mode 100644 src/pages/login/index.css
create mode 100644 src/pages/login/index.tsx
create mode 100644 src/request/index.ts
create mode 100644 src/request/service/login.ts
create mode 100644 src/request/service/user.ts
create mode 100644 src/router/router-error-element.tsx
create mode 100644 src/router/router.tsx
create mode 100644 src/store/global/index.ts
create mode 100644 src/store/global/user.ts
create mode 100644 src/store/index.ts
create mode 100644 src/utils/antd.ts
create mode 100644 src/utils/i18n.ts
create mode 100644 src/utils/utils.ts
diff --git a/README.md b/README.md
index 1ebe379..b68daaa 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,54 @@
-# React + TypeScript + Vite
+# VOGOCM-ERP 后台管理系统Web端
-This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+## 技术框架
-Currently, two official plugins are available:
+技术框架: React + TypeScript + antd + Vite
-- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
-- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
-## Expanding the ESLint configuration
+## 项目目录结构
-If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
+```
+├── public
+│ └── vite.svg
+├── src
+│ └── assets 公共资源
+│ ├── components 页面组件
+│ ├── router 路由模块
+│ ├── hooks react hooks
+│ ├── layout 页面布局
+│ ├── models 数据模型
+│ ├── pages 业务代码
+│ ├── request 处理axios的封装和调用
+│ ├── store 数据持久化和状态管理
+│ ├── utils 公共方法或常量
+│ ├── App.css
+│ ├── App.tsx
+│ ├── index.css
+│ └── main.tsx
+├── index.html
+├── package.json
+└── vite.config.js
+```
-- Configure the top-level `parserOptions` property like this:
+## 测试 & 构建
-```js
- parserOptions: {
- ecmaVersion: 'latest',
- sourceType: 'module',
- project: ['./tsconfig.json', './tsconfig.node.json'],
- tsconfigRootDir: __dirname,
- },
+### 安装依赖
+```
+pnpm install(推荐使用pnpm)
+```
+### 启动
+```
+pnpm start 或 pnpm run dev
+```
+### 构建
```
+pnpm build
+```
+### 预览build产物
+```
+pnpm preview
+```
+
+## 关键技术点说明
+
-- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
-- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
-- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
diff --git a/index.html b/index.html
index e4b78ea..1d90108 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Vite + React + TS
+ vogocm erp admin
diff --git a/package.json b/package.json
index 257eeb6..c98862e 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
+ "start": "vite",
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
diff --git a/public/images/login-right-after.svg b/public/images/login-right-after.svg
new file mode 100644
index 0000000..c724e0a
--- /dev/null
+++ b/public/images/login-right-after.svg
@@ -0,0 +1,69 @@
+
diff --git a/public/images/login-right-before.svg b/public/images/login-right-before.svg
new file mode 100644
index 0000000..6c9fe3e
--- /dev/null
+++ b/public/images/login-right-before.svg
@@ -0,0 +1,65 @@
+
diff --git a/public/images/login-right-bg.svg b/public/images/login-right-bg.svg
new file mode 100644
index 0000000..aa0e4ab
--- /dev/null
+++ b/public/images/login-right-bg.svg
@@ -0,0 +1,39 @@
+
diff --git a/src/App.tsx b/src/App.tsx
index afe48ac..474d38a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,34 +1,66 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from '/vite.svg'
-import './App.css'
+import { useEffect, useMemo } from 'react';
+import { ConfigProvider, ThemeConfig, theme, App as AntdApp } from 'antd'
+import zhCN from 'antd/locale/zh_CN';
+import enUS from 'antd/locale/en_US';
+
+import { useGlobalStore } from './store/global'
+
+import { i18n } from './utils/i18n';
+import Router from './router/router';
function App() {
- const [count, setCount] = useState(0)
+
+ const { darkMode, lang } = useGlobalStore();
+
+ useEffect(() => {
+ if (darkMode) {
+ document.body.classList.remove('light');
+ document.body.classList.add('dark');
+ } else {
+ document.body.classList.remove('dark');
+ document.body.classList.add('light');
+ }
+ }, [darkMode]);
+
+ useEffect(() => {
+ i18n.changeLanguage(lang);
+ }, [lang]);
+
+
+ const curTheme: ThemeConfig = useMemo(() => {
+ if (darkMode) {
+ return {
+ token: {
+ colorPrimary: 'rgb(124, 77, 255)',
+ colorBgBase: 'rgb(17, 25, 54)',
+ colorBgContainer: 'rgb(26, 34, 63)',
+ colorBorder: 'rgba(189, 200, 240, 0.157)',
+ colorBgTextHover: 'rgba(124, 77, 255, 0.082)',
+ colorTextHover: 'rgba(124, 77, 255, 0.082)',
+ controlItemBgActive: 'rgba(33, 150, 243, 0.16)',
+ colorBgElevated: 'rgb(33, 41, 70)'
+ },
+ algorithm: theme.darkAlgorithm,
+ }
+ } else {
+ return {
+ token: {
+ colorPrimary: 'rgb(124, 77, 255)',
+ },
+ }
+ }
+ }, [darkMode]);
return (
- <>
-
- Vite + React
-
-
-
- Edit src/App.tsx
and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
- >
+
+
+
+
+
)
}
diff --git a/src/assets/antd-icons/index.tsx b/src/assets/antd-icons/index.tsx
new file mode 100644
index 0000000..10bc1e5
--- /dev/null
+++ b/src/assets/antd-icons/index.tsx
@@ -0,0 +1,846 @@
+import {
+ StepBackwardOutlined,
+ StepForwardOutlined,
+ FastBackwardOutlined,
+ FastForwardOutlined,
+ ShrinkOutlined,
+ ArrowsAltOutlined,
+ DownOutlined,
+ UpOutlined,
+ LeftOutlined,
+ RightOutlined,
+ CaretUpOutlined,
+ CaretDownOutlined,
+ CaretLeftOutlined,
+ CaretRightOutlined,
+ UpCircleOutlined,
+ DownCircleOutlined,
+ LeftCircleOutlined,
+ RightCircleOutlined,
+ DoubleRightOutlined,
+ DoubleLeftOutlined,
+ VerticalLeftOutlined,
+ VerticalRightOutlined,
+ VerticalAlignTopOutlined,
+ VerticalAlignMiddleOutlined,
+ VerticalAlignBottomOutlined,
+ ForwardOutlined,
+ BackwardOutlined,
+ RollbackOutlined,
+ EnterOutlined,
+ RetweetOutlined,
+ SwapOutlined,
+ SwapLeftOutlined,
+ SwapRightOutlined,
+ ArrowUpOutlined,
+ ArrowDownOutlined,
+ ArrowLeftOutlined,
+ ArrowRightOutlined,
+ PlayCircleOutlined,
+ UpSquareOutlined,
+ DownSquareOutlined,
+ LeftSquareOutlined,
+ RightSquareOutlined,
+ LoginOutlined,
+ LogoutOutlined,
+ MenuFoldOutlined,
+ MenuUnfoldOutlined,
+ BorderBottomOutlined,
+ BorderHorizontalOutlined,
+ BorderInnerOutlined,
+ BorderOuterOutlined,
+ BorderLeftOutlined,
+ BorderRightOutlined,
+ BorderTopOutlined,
+ BorderVerticleOutlined,
+ PicCenterOutlined,
+ PicLeftOutlined,
+ PicRightOutlined,
+ RadiusBottomleftOutlined,
+ RadiusBottomrightOutlined,
+ RadiusUpleftOutlined,
+ RadiusUprightOutlined,
+ FullscreenOutlined,
+ FullscreenExitOutlined,
+ QuestionOutlined,
+ QuestionCircleOutlined,
+ PlusOutlined,
+ PlusCircleOutlined,
+ PauseOutlined,
+ PauseCircleOutlined,
+ MinusOutlined,
+ MinusCircleOutlined,
+ PlusSquareOutlined,
+ MinusSquareOutlined,
+ InfoOutlined,
+ InfoCircleOutlined,
+ ExclamationOutlined,
+ ExclamationCircleOutlined,
+ CloseOutlined,
+ CloseCircleOutlined,
+ CloseSquareOutlined,
+ CheckOutlined,
+ CheckCircleOutlined,
+ CheckSquareOutlined,
+ ClockCircleOutlined,
+ WarningOutlined,
+ IssuesCloseOutlined,
+ StopOutlined,
+ EditOutlined,
+ FormOutlined,
+ CopyOutlined,
+ ScissorOutlined,
+ DeleteOutlined,
+ SnippetsOutlined,
+ DiffOutlined,
+ HighlightOutlined,
+ AlignCenterOutlined,
+ AlignLeftOutlined,
+ AlignRightOutlined,
+ BgColorsOutlined,
+ BoldOutlined,
+ ItalicOutlined,
+ UnderlineOutlined,
+ StrikethroughOutlined,
+ RedoOutlined,
+ UndoOutlined,
+ ZoomInOutlined,
+ ZoomOutOutlined,
+ FontColorsOutlined,
+ FontSizeOutlined,
+ LineHeightOutlined,
+ DashOutlined,
+ SmallDashOutlined,
+ SortAscendingOutlined,
+ SortDescendingOutlined,
+ DragOutlined,
+ OrderedListOutlined,
+ UnorderedListOutlined,
+ RadiusSettingOutlined,
+ ColumnWidthOutlined,
+ ColumnHeightOutlined,
+ AreaChartOutlined,
+ PieChartOutlined,
+ BarChartOutlined,
+ DotChartOutlined,
+ LineChartOutlined,
+ RadarChartOutlined,
+ HeatMapOutlined,
+ FallOutlined,
+ RiseOutlined,
+ StockOutlined,
+ BoxPlotOutlined,
+ FundOutlined,
+ SlidersOutlined,
+ AndroidOutlined,
+ AppleOutlined,
+ WindowsOutlined,
+ IeOutlined,
+ ChromeOutlined,
+ GithubOutlined,
+ AliwangwangOutlined,
+ DingdingOutlined,
+ WeiboSquareOutlined,
+ WeiboCircleOutlined,
+ TaobaoCircleOutlined,
+ Html5Outlined,
+ WeiboOutlined,
+ TwitterOutlined,
+ WechatOutlined,
+ YoutubeOutlined,
+ AlipayCircleOutlined,
+ TaobaoOutlined,
+ SkypeOutlined,
+ QqOutlined,
+ MediumWorkmarkOutlined,
+ GitlabOutlined,
+ MediumOutlined,
+ LinkedinOutlined,
+ GooglePlusOutlined,
+ DropboxOutlined,
+ FacebookOutlined,
+ CodepenOutlined,
+ CodeSandboxOutlined,
+ AmazonOutlined,
+ GoogleOutlined,
+ CodepenCircleOutlined,
+ AlipayOutlined,
+ AntDesignOutlined,
+ AntCloudOutlined,
+ AliyunOutlined,
+ ZhihuOutlined,
+ SlackOutlined,
+ SlackSquareOutlined,
+ BehanceOutlined,
+ BehanceSquareOutlined,
+ DribbbleOutlined,
+ DribbbleSquareOutlined,
+ InstagramOutlined,
+ YuqueOutlined,
+ AlibabaOutlined,
+ YahooOutlined,
+ RedditOutlined,
+ SketchOutlined,
+ AccountBookOutlined,
+ AimOutlined,
+ AlertOutlined,
+ ApartmentOutlined,
+ ApiOutlined,
+ AppstoreAddOutlined,
+ AppstoreOutlined,
+ AudioOutlined,
+ AudioMutedOutlined,
+ AuditOutlined,
+ BankOutlined,
+ BarcodeOutlined,
+ BarsOutlined,
+ BellOutlined,
+ BlockOutlined,
+ BookOutlined,
+ BorderOutlined,
+ BorderlessTableOutlined,
+ BranchesOutlined,
+ BugOutlined,
+ BuildOutlined,
+ BulbOutlined,
+ CalculatorOutlined,
+ CalendarOutlined,
+ CameraOutlined,
+ CarOutlined,
+ CarryOutOutlined,
+ CiCircleOutlined,
+ CiOutlined,
+ ClearOutlined,
+ CloudDownloadOutlined,
+ CloudOutlined,
+ CloudServerOutlined,
+ CloudSyncOutlined,
+ CloudUploadOutlined,
+ ClusterOutlined,
+ CodeOutlined,
+ CoffeeOutlined,
+ CommentOutlined,
+ CompassOutlined,
+ CompressOutlined,
+ ConsoleSqlOutlined,
+ ContactsOutlined,
+ ContainerOutlined,
+ ControlOutlined,
+ CopyrightOutlined,
+ CreditCardOutlined,
+ CrownOutlined,
+ CustomerServiceOutlined,
+ DashboardOutlined,
+ DatabaseOutlined,
+ DeleteColumnOutlined,
+ DeleteRowOutlined,
+ DeliveredProcedureOutlined,
+ DeploymentUnitOutlined,
+ DesktopOutlined,
+ DingtalkOutlined,
+ DisconnectOutlined,
+ DislikeOutlined,
+ DollarCircleOutlined,
+ DollarOutlined,
+ DownloadOutlined,
+ EllipsisOutlined,
+ EnvironmentOutlined,
+ EuroCircleOutlined,
+ EuroOutlined,
+ ExceptionOutlined,
+ ExpandAltOutlined,
+ ExpandOutlined,
+ ExperimentOutlined,
+ ExportOutlined,
+ EyeOutlined,
+ EyeInvisibleOutlined,
+ FieldBinaryOutlined,
+ FieldNumberOutlined,
+ FieldStringOutlined,
+ FieldTimeOutlined,
+ FileAddOutlined,
+ FileDoneOutlined,
+ FileExcelOutlined,
+ FileExclamationOutlined,
+ FileOutlined,
+ FileGifOutlined,
+ FileImageOutlined,
+ FileJpgOutlined,
+ FileMarkdownOutlined,
+ FilePdfOutlined,
+ FilePptOutlined,
+ FileProtectOutlined,
+ FileSearchOutlined,
+ FileSyncOutlined,
+ FileTextOutlined,
+ FileUnknownOutlined,
+ FileWordOutlined,
+ FileZipOutlined,
+ FilterOutlined,
+ FireOutlined,
+ FlagOutlined,
+ FolderAddOutlined,
+ FolderOutlined,
+ FolderOpenOutlined,
+ FolderViewOutlined,
+ ForkOutlined,
+ FormatPainterOutlined,
+ FrownOutlined,
+ FunctionOutlined,
+ FundProjectionScreenOutlined,
+ FundViewOutlined,
+ FunnelPlotOutlined,
+ GatewayOutlined,
+ GifOutlined,
+ GiftOutlined,
+ GlobalOutlined,
+ GoldOutlined,
+ GroupOutlined,
+ HddOutlined,
+ HeartOutlined,
+ HistoryOutlined,
+ HolderOutlined,
+ HomeOutlined,
+ HourglassOutlined,
+ IdcardOutlined,
+ ImportOutlined,
+ InboxOutlined,
+ InsertRowAboveOutlined,
+ InsertRowBelowOutlined,
+ InsertRowLeftOutlined,
+ InsertRowRightOutlined,
+ InsuranceOutlined,
+ InteractionOutlined,
+ KeyOutlined,
+ LaptopOutlined,
+ LayoutOutlined,
+ LikeOutlined,
+ LineOutlined,
+ LinkOutlined,
+ Loading3QuartersOutlined,
+ LoadingOutlined,
+ LockOutlined,
+ MacCommandOutlined,
+ MailOutlined,
+ ManOutlined,
+ MedicineBoxOutlined,
+ MehOutlined,
+ MenuOutlined,
+ MergeCellsOutlined,
+ MessageOutlined,
+ MobileOutlined,
+ MoneyCollectOutlined,
+ MonitorOutlined,
+ MoreOutlined,
+ NodeCollapseOutlined,
+ NodeExpandOutlined,
+ NodeIndexOutlined,
+ NotificationOutlined,
+ NumberOutlined,
+ OneToOneOutlined,
+ PaperClipOutlined,
+ PartitionOutlined,
+ PayCircleOutlined,
+ PercentageOutlined,
+ PhoneOutlined,
+ PictureOutlined,
+ PlaySquareOutlined,
+ PoundCircleOutlined,
+ PoundOutlined,
+ PoweroffOutlined,
+ PrinterOutlined,
+ ProfileOutlined,
+ ProjectOutlined,
+ PropertySafetyOutlined,
+ PullRequestOutlined,
+ PushpinOutlined,
+ QrcodeOutlined,
+ ReadOutlined,
+ ReconciliationOutlined,
+ RedEnvelopeOutlined,
+ ReloadOutlined,
+ RestOutlined,
+ RobotOutlined,
+ RocketOutlined,
+ RotateLeftOutlined,
+ RotateRightOutlined,
+ SafetyCertificateOutlined,
+ SafetyOutlined,
+ SaveOutlined,
+ ScanOutlined,
+ ScheduleOutlined,
+ SearchOutlined,
+ SecurityScanOutlined,
+ SelectOutlined,
+ SendOutlined,
+ SettingOutlined,
+ ShakeOutlined,
+ ShareAltOutlined,
+ ShopOutlined,
+ ShoppingCartOutlined,
+ ShoppingOutlined,
+ SisternodeOutlined,
+ SkinOutlined,
+ SmileOutlined,
+ SolutionOutlined,
+ SoundOutlined,
+ SplitCellsOutlined,
+ StarOutlined,
+ SubnodeOutlined,
+ SwitcherOutlined,
+ SyncOutlined,
+ TableOutlined,
+ TabletOutlined,
+ TagOutlined,
+ TagsOutlined,
+ TeamOutlined,
+ ThunderboltOutlined,
+ ToTopOutlined,
+ ToolOutlined,
+ TrademarkCircleOutlined,
+ TrademarkOutlined,
+ TransactionOutlined,
+ TranslationOutlined,
+ TrophyOutlined,
+ UngroupOutlined,
+ UnlockOutlined,
+ UploadOutlined,
+ UsbOutlined,
+ UserAddOutlined,
+ UserDeleteOutlined,
+ UserOutlined,
+ UserSwitchOutlined,
+ UsergroupAddOutlined,
+ UsergroupDeleteOutlined,
+ VerifiedOutlined,
+ VideoCameraAddOutlined,
+ VideoCameraOutlined,
+ WalletOutlined,
+ WhatsAppOutlined,
+ WifiOutlined,
+ WomanOutlined,
+} from '@ant-design/icons'
+
+
+export const antdIcons: any = {
+ StepBackwardOutlined,
+ StepForwardOutlined,
+ FastBackwardOutlined,
+ FastForwardOutlined,
+ ShrinkOutlined,
+ ArrowsAltOutlined,
+ DownOutlined,
+ UpOutlined,
+ LeftOutlined,
+ RightOutlined,
+ CaretUpOutlined,
+ CaretDownOutlined,
+ CaretLeftOutlined,
+ CaretRightOutlined,
+ UpCircleOutlined,
+ DownCircleOutlined,
+ LeftCircleOutlined,
+ RightCircleOutlined,
+ DoubleRightOutlined,
+ DoubleLeftOutlined,
+ VerticalLeftOutlined,
+ VerticalRightOutlined,
+ VerticalAlignTopOutlined,
+ VerticalAlignMiddleOutlined,
+ VerticalAlignBottomOutlined,
+ ForwardOutlined,
+ BackwardOutlined,
+ RollbackOutlined,
+ EnterOutlined,
+ RetweetOutlined,
+ SwapOutlined,
+ SwapLeftOutlined,
+ SwapRightOutlined,
+ ArrowUpOutlined,
+ ArrowDownOutlined,
+ ArrowLeftOutlined,
+ ArrowRightOutlined,
+ PlayCircleOutlined,
+ UpSquareOutlined,
+ DownSquareOutlined,
+ LeftSquareOutlined,
+ RightSquareOutlined,
+ LoginOutlined,
+ LogoutOutlined,
+ MenuFoldOutlined,
+ MenuUnfoldOutlined,
+ BorderBottomOutlined,
+ BorderHorizontalOutlined,
+ BorderInnerOutlined,
+ BorderOuterOutlined,
+ BorderLeftOutlined,
+ BorderRightOutlined,
+ BorderTopOutlined,
+ BorderVerticleOutlined,
+ PicCenterOutlined,
+ PicLeftOutlined,
+ PicRightOutlined,
+ RadiusBottomleftOutlined,
+ RadiusBottomrightOutlined,
+ RadiusUpleftOutlined,
+ RadiusUprightOutlined,
+ FullscreenOutlined,
+ FullscreenExitOutlined,
+ QuestionOutlined,
+ QuestionCircleOutlined,
+ PlusOutlined,
+ PlusCircleOutlined,
+ PauseOutlined,
+ PauseCircleOutlined,
+ MinusOutlined,
+ MinusCircleOutlined,
+ PlusSquareOutlined,
+ MinusSquareOutlined,
+ InfoOutlined,
+ InfoCircleOutlined,
+ ExclamationOutlined,
+ ExclamationCircleOutlined,
+ CloseOutlined,
+ CloseCircleOutlined,
+ CloseSquareOutlined,
+ CheckOutlined,
+ CheckCircleOutlined,
+ CheckSquareOutlined,
+ ClockCircleOutlined,
+ WarningOutlined,
+ IssuesCloseOutlined,
+ StopOutlined,
+ EditOutlined,
+ FormOutlined,
+ CopyOutlined,
+ ScissorOutlined,
+ DeleteOutlined,
+ SnippetsOutlined,
+ DiffOutlined,
+ HighlightOutlined,
+ AlignCenterOutlined,
+ AlignLeftOutlined,
+ AlignRightOutlined,
+ BgColorsOutlined,
+ BoldOutlined,
+ ItalicOutlined,
+ UnderlineOutlined,
+ StrikethroughOutlined,
+ RedoOutlined,
+ UndoOutlined,
+ ZoomInOutlined,
+ ZoomOutOutlined,
+ FontColorsOutlined,
+ FontSizeOutlined,
+ LineHeightOutlined,
+ DashOutlined,
+ SmallDashOutlined,
+ SortAscendingOutlined,
+ SortDescendingOutlined,
+ DragOutlined,
+ OrderedListOutlined,
+ UnorderedListOutlined,
+ RadiusSettingOutlined,
+ ColumnWidthOutlined,
+ ColumnHeightOutlined,
+ AreaChartOutlined,
+ PieChartOutlined,
+ BarChartOutlined,
+ DotChartOutlined,
+ LineChartOutlined,
+ RadarChartOutlined,
+ HeatMapOutlined,
+ FallOutlined,
+ RiseOutlined,
+ StockOutlined,
+ BoxPlotOutlined,
+ FundOutlined,
+ SlidersOutlined,
+ AndroidOutlined,
+ AppleOutlined,
+ WindowsOutlined,
+ IeOutlined,
+ ChromeOutlined,
+ GithubOutlined,
+ AliwangwangOutlined,
+ DingdingOutlined,
+ WeiboSquareOutlined,
+ WeiboCircleOutlined,
+ TaobaoCircleOutlined,
+ Html5Outlined,
+ WeiboOutlined,
+ TwitterOutlined,
+ WechatOutlined,
+ YoutubeOutlined,
+ AlipayCircleOutlined,
+ TaobaoOutlined,
+ SkypeOutlined,
+ QqOutlined,
+ MediumWorkmarkOutlined,
+ GitlabOutlined,
+ MediumOutlined,
+ LinkedinOutlined,
+ GooglePlusOutlined,
+ DropboxOutlined,
+ FacebookOutlined,
+ CodepenOutlined,
+ CodeSandboxOutlined,
+ AmazonOutlined,
+ GoogleOutlined,
+ CodepenCircleOutlined,
+ AlipayOutlined,
+ AntDesignOutlined,
+ AntCloudOutlined,
+ AliyunOutlined,
+ ZhihuOutlined,
+ SlackOutlined,
+ SlackSquareOutlined,
+ BehanceOutlined,
+ BehanceSquareOutlined,
+ DribbbleOutlined,
+ DribbbleSquareOutlined,
+ InstagramOutlined,
+ YuqueOutlined,
+ AlibabaOutlined,
+ YahooOutlined,
+ RedditOutlined,
+ SketchOutlined,
+ AccountBookOutlined,
+ AimOutlined,
+ AlertOutlined,
+ ApartmentOutlined,
+ ApiOutlined,
+ AppstoreAddOutlined,
+ AppstoreOutlined,
+ AudioOutlined,
+ AudioMutedOutlined,
+ AuditOutlined,
+ BankOutlined,
+ BarcodeOutlined,
+ BarsOutlined,
+ BellOutlined,
+ BlockOutlined,
+ BookOutlined,
+ BorderOutlined,
+ BorderlessTableOutlined,
+ BranchesOutlined,
+ BugOutlined,
+ BuildOutlined,
+ BulbOutlined,
+ CalculatorOutlined,
+ CalendarOutlined,
+ CameraOutlined,
+ CarOutlined,
+ CarryOutOutlined,
+ CiCircleOutlined,
+ CiOutlined,
+ ClearOutlined,
+ CloudDownloadOutlined,
+ CloudOutlined,
+ CloudServerOutlined,
+ CloudSyncOutlined,
+ CloudUploadOutlined,
+ ClusterOutlined,
+ CodeOutlined,
+ CoffeeOutlined,
+ CommentOutlined,
+ CompassOutlined,
+ CompressOutlined,
+ ConsoleSqlOutlined,
+ ContactsOutlined,
+ ContainerOutlined,
+ ControlOutlined,
+ CopyrightOutlined,
+ CreditCardOutlined,
+ CrownOutlined,
+ CustomerServiceOutlined,
+ DashboardOutlined,
+ DatabaseOutlined,
+ DeleteColumnOutlined,
+ DeleteRowOutlined,
+ DeliveredProcedureOutlined,
+ DeploymentUnitOutlined,
+ DesktopOutlined,
+ DingtalkOutlined,
+ DisconnectOutlined,
+ DislikeOutlined,
+ DollarCircleOutlined,
+ DollarOutlined,
+ DownloadOutlined,
+ EllipsisOutlined,
+ EnvironmentOutlined,
+ EuroCircleOutlined,
+ EuroOutlined,
+ ExceptionOutlined,
+ ExpandAltOutlined,
+ ExpandOutlined,
+ ExperimentOutlined,
+ ExportOutlined,
+ EyeOutlined,
+ EyeInvisibleOutlined,
+ FieldBinaryOutlined,
+ FieldNumberOutlined,
+ FieldStringOutlined,
+ FieldTimeOutlined,
+ FileAddOutlined,
+ FileDoneOutlined,
+ FileExcelOutlined,
+ FileExclamationOutlined,
+ FileOutlined,
+ FileGifOutlined,
+ FileImageOutlined,
+ FileJpgOutlined,
+ FileMarkdownOutlined,
+ FilePdfOutlined,
+ FilePptOutlined,
+ FileProtectOutlined,
+ FileSearchOutlined,
+ FileSyncOutlined,
+ FileTextOutlined,
+ FileUnknownOutlined,
+ FileWordOutlined,
+ FileZipOutlined,
+ FilterOutlined,
+ FireOutlined,
+ FlagOutlined,
+ FolderAddOutlined,
+ FolderOutlined,
+ FolderOpenOutlined,
+ FolderViewOutlined,
+ ForkOutlined,
+ FormatPainterOutlined,
+ FrownOutlined,
+ FunctionOutlined,
+ FundProjectionScreenOutlined,
+ FundViewOutlined,
+ FunnelPlotOutlined,
+ GatewayOutlined,
+ GifOutlined,
+ GiftOutlined,
+ GlobalOutlined,
+ GoldOutlined,
+ GroupOutlined,
+ HddOutlined,
+ HeartOutlined,
+ HistoryOutlined,
+ HolderOutlined,
+ HomeOutlined,
+ HourglassOutlined,
+ IdcardOutlined,
+ ImportOutlined,
+ InboxOutlined,
+ InsertRowAboveOutlined,
+ InsertRowBelowOutlined,
+ InsertRowLeftOutlined,
+ InsertRowRightOutlined,
+ InsuranceOutlined,
+ InteractionOutlined,
+ KeyOutlined,
+ LaptopOutlined,
+ LayoutOutlined,
+ LikeOutlined,
+ LineOutlined,
+ LinkOutlined,
+ Loading3QuartersOutlined,
+ LoadingOutlined,
+ LockOutlined,
+ MacCommandOutlined,
+ MailOutlined,
+ ManOutlined,
+ MedicineBoxOutlined,
+ MehOutlined,
+ MenuOutlined,
+ MergeCellsOutlined,
+ MessageOutlined,
+ MobileOutlined,
+ MoneyCollectOutlined,
+ MonitorOutlined,
+ MoreOutlined,
+ NodeCollapseOutlined,
+ NodeExpandOutlined,
+ NodeIndexOutlined,
+ NotificationOutlined,
+ NumberOutlined,
+ OneToOneOutlined,
+ PaperClipOutlined,
+ PartitionOutlined,
+ PayCircleOutlined,
+ PercentageOutlined,
+ PhoneOutlined,
+ PictureOutlined,
+ PlaySquareOutlined,
+ PoundCircleOutlined,
+ PoundOutlined,
+ PoweroffOutlined,
+ PrinterOutlined,
+ ProfileOutlined,
+ ProjectOutlined,
+ PropertySafetyOutlined,
+ PullRequestOutlined,
+ PushpinOutlined,
+ QrcodeOutlined,
+ ReadOutlined,
+ ReconciliationOutlined,
+ RedEnvelopeOutlined,
+ ReloadOutlined,
+ RestOutlined,
+ RobotOutlined,
+ RocketOutlined,
+ RotateLeftOutlined,
+ RotateRightOutlined,
+ SafetyCertificateOutlined,
+ SafetyOutlined,
+ SaveOutlined,
+ ScanOutlined,
+ ScheduleOutlined,
+ SearchOutlined,
+ SecurityScanOutlined,
+ SelectOutlined,
+ SendOutlined,
+ SettingOutlined,
+ ShakeOutlined,
+ ShareAltOutlined,
+ ShopOutlined,
+ ShoppingCartOutlined,
+ ShoppingOutlined,
+ SisternodeOutlined,
+ SkinOutlined,
+ SmileOutlined,
+ SolutionOutlined,
+ SoundOutlined,
+ SplitCellsOutlined,
+ StarOutlined,
+ SubnodeOutlined,
+ SwitcherOutlined,
+ SyncOutlined,
+ TableOutlined,
+ TabletOutlined,
+ TagOutlined,
+ TagsOutlined,
+ TeamOutlined,
+ ThunderboltOutlined,
+ ToTopOutlined,
+ ToolOutlined,
+ TrademarkCircleOutlined,
+ TrademarkOutlined,
+ TransactionOutlined,
+ TranslationOutlined,
+ TrophyOutlined,
+ UngroupOutlined,
+ UnlockOutlined,
+ UploadOutlined,
+ UsbOutlined,
+ UserAddOutlined,
+ UserDeleteOutlined,
+ UserOutlined,
+ UserSwitchOutlined,
+ UsergroupAddOutlined,
+ UsergroupDeleteOutlined,
+ VerifiedOutlined,
+ VideoCameraAddOutlined,
+ VideoCameraOutlined,
+ WalletOutlined,
+ WhatsAppOutlined,
+ WifiOutlined,
+ WomanOutlined,
+};
\ No newline at end of file
diff --git a/src/assets/css/overwrite.css b/src/assets/css/overwrite.css
new file mode 100644
index 0000000..096e8b5
--- /dev/null
+++ b/src/assets/css/overwrite.css
@@ -0,0 +1,148 @@
+* {
+ margin: 0;
+ box-sizing: border-box;
+}
+
+.ant-menu-item {
+ height: 50px !important;
+ line-height: 50px !important;
+}
+
+.ant-menu-submenu-title {
+ height: 50px !important;
+ line-height: 50px !important;
+}
+
+.ant-menu-item span {
+ transition: none !important;
+}
+
+
+.ant-menu-item:hover {
+ color: rgb(124, 77, 255) !important;
+ background-color: #f0e9f7 !important;
+}
+
+.light .ant-menu-item:hover {
+ color: rgb(124, 77, 255) !important;
+ background-color: #f0e9f7 !important;
+}
+
+.dark .ant-menu-item:hover {
+ color: rgb(124, 77, 255) !important;
+ background-color: rgba(124, 77, 255, 0.082) !important;
+}
+
+.dark .ant-menu-item-selected {
+ color: rgb(124, 77, 255) !important;
+ background-color: rgba(124, 77, 255, 0.082) !important;
+}
+
+
+.ant-menu-submenu-title:hover {
+ color: rgb(124, 77, 255) !important;
+ background-color: #f0e9f7 !important;
+}
+
+.light .ant-menu-submenu-title:hover {
+ color: rgb(124, 77, 255) !important;
+ background-color: #f0e9f7 !important;
+}
+
+.dark .ant-menu-submenu-title:hover {
+ color: rgb(124, 77, 255) !important;
+ background-color: rgba(124, 77, 255, 0.082) !important;
+}
+
+.dark .ant-menu-item-selected {
+ color: rgb(124, 77, 255) !important;
+ background-color: rgba(124, 77, 255, 0.082) !important;
+}
+
+.ant-menu-inline-collapsed .ant-menu-submenu-selected .ant-menu-submenu-title {
+ color: rgb(124, 77, 255) !important;
+ background-color: rgba(124, 77, 255, 0.082) !important;
+}
+
+.ant-menu-submenu-selected .ant-menu-submenu-title {
+ color: rgb(124, 77, 255) !important;
+}
+
+.ant-menu-submenu-selected .ant-menu-title-content a {
+ color: rgb(124, 77, 255) !important;
+}
+
+.ant-menu-submenu-open>.ant-menu-submenu-title .ant-menu-title-content a {
+ color: rgb(124, 77, 255) !important;
+}
+
+.ant-menu-submenu-open>.ant-menu-submenu-title {
+ color: rgb(124, 77, 255) !important;
+}
+
+.ant-menu-submenu-open>.ant-menu-submenu-title .ant-menu-item-icon {
+ color: rgb(124, 77, 255) !important;
+}
+
+/* .ant-menu-submenu-selected .ant-menu-title-content a {
+ color: rgb(124, 77, 255) !important;
+} */
+
+/* .dark .ant-menu-submenu-selected .ant-menu-title-content a {
+ color: rgb(124, 77, 255) !important;
+} */
+
+/*
+.light .ant-menu-submenu-selected .ant-menu-title-content a {
+ color: rgb(124, 77, 255) !important;
+} */
+
+.dark .ant-menu-title-content a {
+ color: #ffffff !important;
+}
+
+.dark .ant-menu-item-selected .ant-menu-title-content a {
+ color: rgb(124, 77, 255) !important;
+}
+
+.ant-menu-title-content a {
+ color: rgba(0, 0, 0, 0.88) !important;
+}
+
+.light .ant-menu-item-selected .ant-menu-title-content a {
+ color: rgb(124, 77, 255) !important;
+}
+
+.light .ant-menu-title-content a {
+ color: rgba(0, 0, 0, 0.88) !important;
+}
+
+.ant-menu-title-content:hover a {
+ color: rgb(124, 77, 255) !important;
+}
+
+
+.ant-menu-sub {
+ background-color: transparent !important;
+}
+
+body.dark {
+ background-color: rgb(17, 25, 54);
+}
+
+.ant-btn-primary {
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px !important;
+}
+
+.ant-menu-item .ant-menu-item-icon {
+ font-size: 18px !important;
+ vertical-align: middle !important;
+}
+
+a {
+ color: rgb(124, 77, 255);
+}
+
+a:hover {
+ color: rgba(124, 77, 255, 0.7);
+}
\ No newline at end of file
diff --git a/src/assets/icons/3.tsx b/src/assets/icons/3.tsx
new file mode 100644
index 0000000..d7037e1
--- /dev/null
+++ b/src/assets/icons/3.tsx
@@ -0,0 +1,22 @@
+import Icon from "@ant-design/icons";
+import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
+
+const SVG3 = () => (
+
+);
+
+export const Icon3 = (props: Partial) => (
+
+);
diff --git a/src/assets/icons/buguang.tsx b/src/assets/icons/buguang.tsx
new file mode 100644
index 0000000..ad47b51
--- /dev/null
+++ b/src/assets/icons/buguang.tsx
@@ -0,0 +1,22 @@
+import Icon from "@ant-design/icons";
+import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
+
+const SVGBuguang = () => (
+
+);
+
+export const IconBuguang = (props: Partial) => (
+
+);
diff --git a/src/assets/icons/fangdajing.tsx b/src/assets/icons/fangdajing.tsx
new file mode 100644
index 0000000..2370a9f
--- /dev/null
+++ b/src/assets/icons/fangdajing.tsx
@@ -0,0 +1,22 @@
+import Icon from "@ant-design/icons";
+import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
+
+const SVGFangdajing = () => (
+
+);
+
+export const IconFangdajing = (props: Partial) => (
+
+);
diff --git a/src/assets/icons/jiaretaiyang.tsx b/src/assets/icons/jiaretaiyang.tsx
new file mode 100644
index 0000000..b827a37
--- /dev/null
+++ b/src/assets/icons/jiaretaiyang.tsx
@@ -0,0 +1,22 @@
+import Icon from "@ant-design/icons";
+import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
+
+const SVGJiaretaiyang = () => (
+
+);
+
+export const IconJiaretaiyang = (props: Partial) => (
+
+);
diff --git a/src/assets/icons/moon.tsx b/src/assets/icons/moon.tsx
new file mode 100644
index 0000000..ce45157
--- /dev/null
+++ b/src/assets/icons/moon.tsx
@@ -0,0 +1,22 @@
+import Icon from "@ant-design/icons";
+import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
+
+const SVGMoon = () => (
+
+);
+
+export const IconMoon = (props: Partial) => (
+
+);
diff --git a/src/assets/icons/shuyi_fanyi-36.tsx b/src/assets/icons/shuyi_fanyi-36.tsx
new file mode 100644
index 0000000..0d2be85
--- /dev/null
+++ b/src/assets/icons/shuyi_fanyi-36.tsx
@@ -0,0 +1,22 @@
+import Icon from "@ant-design/icons";
+import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
+
+const SVGShuyi_fanyi36 = () => (
+
+);
+
+export const IconShuyi_fanyi36 = (props: Partial) => (
+
+);
diff --git a/src/assets/icons/sun.tsx b/src/assets/icons/sun.tsx
new file mode 100644
index 0000000..2fd67cf
--- /dev/null
+++ b/src/assets/icons/sun.tsx
@@ -0,0 +1,22 @@
+import Icon from "@ant-design/icons";
+import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
+
+const SVGSun = () => (
+
+);
+
+export const IconSun = (props: Partial) => (
+
+);
diff --git a/src/assets/locales/en-US.ts b/src/assets/locales/en-US.ts
new file mode 100644
index 0000000..da584f5
--- /dev/null
+++ b/src/assets/locales/en-US.ts
@@ -0,0 +1,78 @@
+export default {
+ wbTMzvDM: "Vogocm Background Management System",
+ wVzXBuYs: "Please enter an account",
+ RNISycbR: "account number",
+ DjMcEMAe: "Please input a password",
+ HplkKxdY: "password",
+ dDdqAAve: "Sign in",
+ wrQwwbSV: "Chinese",
+ hGtEfNnp: "English",
+ jhqxJPbn: "Search Menu",
+ wPqFuoLF: "Log out of login",
+ yAdJryjx: "Indicator Description",
+ nKMAkrqJ: "Total sales revenue",
+ NpRFMJyD: "Weekly YoY",
+ WOQnwYUS: "Daily YoY",
+ ZPCQOWAn: "Daily sales",
+ iLyPEqwQ: "Indicator Description",
+ ftuxZMpL: "Visits",
+ sehypRaO: "Daily Visits",
+ sdOusITo: "Indicator Description",
+ PIYkoguj: "Number of payments",
+ BUjwpMzX: "Conversion rate",
+ fHpiDHYH: "Total growth",
+ yLkZTWbn: "today",
+ QFqMuZiD: "This month",
+ lGOcGyrv: "This year",
+ yzUIyMhr: "Store sales",
+ aSPCUBcK: "today",
+ EhTpnarX: "This month",
+ AGGPEAdX: "This year",
+ jTSvVuJx: "Shanghai Branch",
+ SwsawJhB: "20% profit",
+ JYSgIJHD: "Shanghai Branch",
+ yELACPnu: "20% profit",
+ WAiyAuwV: "Hefei Branch",
+ HpNzGyBz: "6% profit",
+ nGvTAQld: "Beijing Branch",
+ EeunYupT: "8% loss",
+ usCBUdwp: "Suzhou Branch",
+ TacOGPiP: "14% profit",
+ Imkllizi: "Nanjing Branch",
+ MzCxBxLH: "6% loss",
+ LhjNVSoc: "name",
+ MOlwAEMx: "Age",
+ npxxdPKd: "address",
+ YoERuunu: "occupation",
+ QkOmYwne: "operation",
+ EOSDTAVT: "name",
+ hQeqcUTv: "Age",
+ YHapJMTT: "search",
+ uCkoPyVp: "eliminate",
+ qYznwlfj: "user name",
+ gohANZwy: "nickname",
+ yBxFprdB: "Mobile phone number",
+ XWVvMWig: "mailbox",
+ ykrQSYRh: "Gender",
+ AkkyZTUy: "male",
+ yduIcxbx: "female",
+ TMuQjpWo: "Creation time",
+ qEIlwmxC: "edit",
+ JjwFfqHG: "warning",
+ nlZBTfzL: "Are you sure to delete this data?",
+ bvwOSeoJ: "Successfully deleted!",
+ HJYhipnp: "delete",
+ rnyigssw: "nickname",
+ SPsRnpyN: "Mobile phone number",
+ morEPEyc: "Add",
+ wXpnewYo: "edit",
+ VjwnJLPY: "New",
+ NfOSPWDa: "Updated successfully!",
+ JANFdKFM: "Created successfully!",
+ jwGPaPNq: "Cannot be empty",
+ iricpuxB: "Cannot be empty",
+ UdKeETRS: "Cannot be empty",
+ AnDwfuuT: "Incorrect phone number format",
+ QFkffbad: "Cannot be empty",
+ EfwYKLsR: "Incorrect email format",
+};
diff --git a/src/assets/locales/zh-CN.ts b/src/assets/locales/zh-CN.ts
new file mode 100644
index 0000000..41aba95
--- /dev/null
+++ b/src/assets/locales/zh-CN.ts
@@ -0,0 +1,78 @@
+export default {
+ wbTMzvDM: "旺嘉-ERP后台管理系统",
+ wVzXBuYs: "请输入账号",
+ RNISycbR: "账号",
+ DjMcEMAe: "请输入密码",
+ HplkKxdY: "密码",
+ dDdqAAve: "登录",
+ wrQwwbSV: "中文",
+ hGtEfNnp: "英语",
+ jhqxJPbn: "搜索菜单",
+ wPqFuoLF: "退出登录",
+ yAdJryjx: "指标说明",
+ nKMAkrqJ: "总销售额",
+ NpRFMJyD: "周同比",
+ WOQnwYUS: "日同比",
+ ZPCQOWAn: "日销售额",
+ iLyPEqwQ: "指标说明",
+ ftuxZMpL: "访问量",
+ sehypRaO: "日访问量",
+ sdOusITo: "指标说明",
+ PIYkoguj: "支付笔数",
+ BUjwpMzX: "转化率",
+ fHpiDHYH: "总增长",
+ yLkZTWbn: "今日",
+ QFqMuZiD: "本月",
+ lGOcGyrv: "本年",
+ yzUIyMhr: "门店销售额",
+ aSPCUBcK: "今日",
+ EhTpnarX: "本月",
+ AGGPEAdX: "本年",
+ jTSvVuJx: "上海分店",
+ SwsawJhB: "20% 利润",
+ JYSgIJHD: "上海分店",
+ yELACPnu: "20% 利润",
+ WAiyAuwV: "合肥分店",
+ HpNzGyBz: "6% 利润",
+ nGvTAQld: "北京分店",
+ EeunYupT: "8% 亏损",
+ usCBUdwp: "苏州分店",
+ TacOGPiP: "14% 利润",
+ Imkllizi: "南京分店",
+ MzCxBxLH: "6% 亏损",
+ LhjNVSoc: "名称",
+ MOlwAEMx: "年龄",
+ npxxdPKd: "地址",
+ YoERuunu: "职业",
+ QkOmYwne: "操作",
+ EOSDTAVT: "名称",
+ hQeqcUTv: "年龄",
+ YHapJMTT: "搜索",
+ uCkoPyVp: "清除",
+ qYznwlfj: "用户名",
+ gohANZwy: "昵称",
+ yBxFprdB: "手机号",
+ XWVvMWig: "邮箱",
+ ykrQSYRh: "性别",
+ AkkyZTUy: "男",
+ yduIcxbx: "女",
+ TMuQjpWo: "创建时间",
+ qEIlwmxC: "编辑",
+ JjwFfqHG: "警告",
+ nlZBTfzL: "确认删除这条数据?",
+ bvwOSeoJ: "删除成功!",
+ HJYhipnp: "删除",
+ rnyigssw: "昵称",
+ SPsRnpyN: "手机号",
+ morEPEyc: "新增",
+ wXpnewYo: "编辑",
+ VjwnJLPY: "新建",
+ NfOSPWDa: "更新成功!",
+ JANFdKFM: "创建成功!",
+ jwGPaPNq: "不能为空",
+ iricpuxB: "不能为空",
+ UdKeETRS: "不能为空",
+ AnDwfuuT: "手机号格式不正确",
+ QFkffbad: "不能为空",
+ EfwYKLsR: "邮箱格式不正确",
+};
diff --git a/src/assets/react.svg b/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/components/draggable-tab/index.css b/src/components/draggable-tab/index.css
new file mode 100644
index 0000000..a309e91
--- /dev/null
+++ b/src/components/draggable-tab/index.css
@@ -0,0 +1,15 @@
+.tab-layout.ant-tabs-card>.ant-tabs-nav .ant-tabs-tab {
+ transition: none;
+}
+
+.tab-layout.ant-tabs-card>.ant-tabs-nav .ant-tabs-tab-remove:active {
+ color: unset;
+}
+
+.tab-layout.ant-tabs-card>.ant-tabs-nav .ant-tabs-tab-btn:active {
+ color: unset;
+}
+
+.tab-layout.ant-tabs-card>.ant-tabs-nav .ant-tabs-tab-btn:focus:not(:focus-visible) {
+ color: unset;
+}
\ No newline at end of file
diff --git a/src/components/draggable-tab/index.tsx b/src/components/draggable-tab/index.tsx
new file mode 100644
index 0000000..898ffb3
--- /dev/null
+++ b/src/components/draggable-tab/index.tsx
@@ -0,0 +1,88 @@
+import type { DragEndEvent } from '@dnd-kit/core';
+import { DndContext, PointerSensor, useSensor } from '@dnd-kit/core';
+import {
+ arrayMove,
+ horizontalListSortingStrategy,
+ SortableContext,
+ useSortable,
+} from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
+import React, { useEffect, useState } from 'react';
+import { Tabs, TabsProps } from 'antd';
+import {
+ restrictToHorizontalAxis,
+} from '@dnd-kit/modifiers';
+
+import './index.css'
+
+interface DraggableTabPaneProps extends React.HTMLAttributes {
+ 'data-node-key': string;
+}
+
+const DraggableTabNode = (props: DraggableTabPaneProps) => {
+ const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
+ id: props['data-node-key'],
+ });
+
+ const style: React.CSSProperties = {
+ ...props.style,
+ transform: CSS.Transform.toString(transform && { ...transform, scaleX: 1 }),
+ transition,
+ };
+
+ return React.cloneElement(props.children as React.ReactElement, {
+ ref: setNodeRef,
+ style,
+ ...attributes,
+ ...listeners,
+ });
+};
+
+const DraggableTab: React.FC void }> = ({ onItemsChange, ...props }) => {
+ const [items, setItems] = useState(props.items || []);
+
+ const sensor = useSensor(PointerSensor, { activationConstraint: { distance: 10 } });
+
+ const onDragEnd = ({ active, over }: DragEndEvent) => {
+ if (active.id !== over?.id) {
+ setItems((prev) => {
+ const activeIndex = prev.findIndex((i) => i.key === active.id);
+ const overIndex = prev.findIndex((i) => i.key === over?.id);
+ return arrayMove(prev, activeIndex, overIndex);
+ });
+ }
+ };
+
+ useEffect(() => {
+ setItems(props.items || []);
+ }, [props.items]);
+
+ useEffect(() => {
+ if (onItemsChange) {
+ onItemsChange(items);
+ }
+ }, [items]);
+
+ return (
+ (
+
+ i.key)} strategy={horizontalListSortingStrategy}>
+
+ {(node) => (
+
+ {node}
+
+ )}
+
+
+
+ )}
+ {...props}
+ items={items}
+ className='tab-layout'
+ />
+ );
+};
+
+export default DraggableTab;
diff --git a/src/components/global-loading/index.css b/src/components/global-loading/index.css
new file mode 100644
index 0000000..8644d80
--- /dev/null
+++ b/src/components/global-loading/index.css
@@ -0,0 +1,47 @@
+.loading {
+ display: block;
+ position: relative;
+ width: 6px;
+ height: 10px;
+ border-radius: 2px;
+ animation: rectangle infinite 1s ease-in-out -0.2s;
+
+ background-color: #673AB7;
+}
+
+.loading:before,
+.loading:after {
+ position: absolute;
+ width: 6px;
+ height: 10px;
+ content: "";
+ background-color: #673AB7;
+ border-radius: 2px;
+}
+
+.loading:before {
+ left: -14px;
+
+ animation: rectangle infinite 1s ease-in-out -0.4s;
+}
+
+.loading:after {
+ right: -14px;
+
+ animation: rectangle infinite 1s ease-in-out;
+}
+
+@keyframes rectangle {
+
+ 0%,
+ 80%,
+ 100% {
+ height: 20px;
+ box-shadow: 0 0 #673AB7;
+ }
+
+ 40% {
+ height: 30px;
+ box-shadow: 0 -20px #673AB7;
+ }
+}
\ No newline at end of file
diff --git a/src/components/global-loading/index.tsx b/src/components/global-loading/index.tsx
new file mode 100644
index 0000000..2b12389
--- /dev/null
+++ b/src/components/global-loading/index.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+
+import './index.css'
+
+const GloablLoading: React.FC = () => (
+ <>
+
+ >
+)
+
+export default GloablLoading;
diff --git a/src/components/loading/index.tsx b/src/components/loading/index.tsx
new file mode 100644
index 0000000..2eeafce
--- /dev/null
+++ b/src/components/loading/index.tsx
@@ -0,0 +1,20 @@
+import { Spin } from 'antd';
+import NProgress from 'nprogress';
+import { useEffect } from 'react';
+
+export const Loading = () => {
+ useEffect(() => {
+
+ NProgress.start();
+
+ return () => {
+ NProgress.done();
+ }
+ }, [])
+
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/default-setting.ts b/src/default-setting.ts
new file mode 100644
index 0000000..b3ceb8f
--- /dev/null
+++ b/src/default-setting.ts
@@ -0,0 +1,14 @@
+export const defaultSetting = {
+ slideWidth: 280,
+ languages: [
+ {
+ key: 'zh',
+ name: 'wrQwwbSV',
+ },
+ {
+ key: 'en',
+ name: 'hGtEfNnp',
+ },
+ ],
+ defaultLang: 'zh',
+}
diff --git a/src/hooks/use-match-router/index.tsx b/src/hooks/use-match-router/index.tsx
new file mode 100644
index 0000000..37d315e
--- /dev/null
+++ b/src/hooks/use-match-router/index.tsx
@@ -0,0 +1,47 @@
+import { useEffect, useState } from 'react';
+import { useLocation, useMatches, useOutlet } from 'react-router-dom';
+
+interface MatchRouteType {
+ // 菜单名称
+ title: string;
+ // tab对应的url
+ pathname: string;
+ // 要渲染的组件
+ children: any;
+ // 路由,和pathname区别是,详情页 pathname是 /:id,routePath是 /1
+ routePath: string;
+ // 图标
+ icon?: string;
+}
+
+export function useMatchRoute(): MatchRouteType | undefined {
+ // 获取路由组件实例
+ const children = useOutlet();
+ // 获取所有路由
+ const matches = useMatches();
+ // 获取当前url
+ const { pathname } = useLocation();
+
+ const [matchRoute, setMatchRoute] = useState();
+
+ // 监听pathname变了,说明路由有变化,重新匹配,返回新路由信息
+ useEffect(() => {
+
+ // 获取当前匹配的路由
+ const lastRoute = matches.at(-1);
+
+ if (!lastRoute?.handle) return;
+
+ setMatchRoute({
+ title: (lastRoute?.handle as any)?.name,
+ pathname,
+ children,
+ routePath: lastRoute?.pathname || '',
+ icon: (lastRoute?.handle as any)?.icon,
+ });
+
+ }, [pathname])
+
+
+ return matchRoute;
+}
diff --git a/src/hooks/use-pc-screen/index.tsx b/src/hooks/use-pc-screen/index.tsx
new file mode 100644
index 0000000..3425d6d
--- /dev/null
+++ b/src/hooks/use-pc-screen/index.tsx
@@ -0,0 +1,6 @@
+import { useMedia } from 'react-use';
+
+export const usePCScreen = () => {
+ const isPC = useMedia('(min-width: 1024px)');
+ return isPC;
+}
\ No newline at end of file
diff --git a/src/hooks/use-request/index.ts b/src/hooks/use-request/index.ts
new file mode 100644
index 0000000..3126441
--- /dev/null
+++ b/src/hooks/use-request/index.ts
@@ -0,0 +1,77 @@
+import { useCallback, useEffect, useRef, useState } from 'react';
+import { Response } from '@/request';
+
+interface RequestOptions {
+ manual?: boolean;
+ defaultParams?: any[];
+}
+
+interface RequestResponse {
+ error: boolean | undefined;
+ data: T | undefined;
+ loading: boolean;
+ run(...params: any): void;
+ runAsync(...params: any): Response;
+ refresh(): void;
+}
+
+export function useRequest(
+ serviceMethod: (...args: any) => Response,
+ options?: RequestOptions
+): RequestResponse {
+ const [loading, setLoading] = useState(false);
+ const [data, setData] = useState();
+ const [error, setError] = useState();
+
+ const paramsRef = useRef([]);
+
+ const resolveData = useCallback(async () => {
+ setLoading(true);
+ const [error, requestData] = await serviceMethod(
+ ...(options?.defaultParams || [])
+ );
+ setLoading(false);
+ setData(requestData);
+ setError(error);
+ }, [serviceMethod, options]);
+
+ const runAsync = useCallback(
+ async (...params: any) => {
+ paramsRef.current = params;
+ setLoading(true);
+ const res = await serviceMethod(...params);
+ const [err, curData] = res;
+ setError(err);
+ setLoading(false);
+ setData(curData);
+ return res;
+ },
+ [serviceMethod]
+ );
+
+ const run = useCallback(
+ async (...params: any) => {
+ await runAsync(...params);
+ },
+ [runAsync]
+ );
+
+ const refresh = useCallback(() => {
+ runAsync(...paramsRef.current);
+ }, [runAsync]);
+
+ useEffect(() => {
+ if (!options?.manual) {
+ resolveData();
+ }
+ }, [options, resolveData]);
+
+ return {
+ loading,
+ error,
+ data,
+ run,
+ runAsync,
+ refresh,
+ };
+}
diff --git a/src/hooks/use-tabs/index.tsx b/src/hooks/use-tabs/index.tsx
new file mode 100644
index 0000000..423df34
--- /dev/null
+++ b/src/hooks/use-tabs/index.tsx
@@ -0,0 +1,106 @@
+// /src/layouts/useTabs.tsx
+import { useMatchRoute } from '@/hooks/use-match-router';
+import { router } from '@/router/router';
+import { useCallback, useEffect, useState } from 'react';
+
+export interface KeepAliveTab {
+ title: string;
+ routePath: string;
+ key: string;
+ pathname: string;
+ icon?: any;
+ children: any;
+}
+
+function getKey() {
+ return new Date().getTime().toString();
+}
+
+export function useTabs() {
+ // 存放页面记录
+ const [keepAliveTabs, setKeepAliveTabs] = useState([]);
+ // 当前激活的tab
+ const [activeTabRoutePath, setActiveTabRoutePath] = useState('');
+
+ const matchRoute = useMatchRoute();
+
+ // 关闭tab
+ const closeTab = useCallback(
+ (routePath: string = activeTabRoutePath) => {
+
+ const index = keepAliveTabs.findIndex(o => o.routePath === routePath);
+ if (keepAliveTabs[index].routePath === activeTabRoutePath) {
+ if (index > 0) {
+ router.navigate(keepAliveTabs[index - 1].routePath);
+ } else {
+ router.navigate(keepAliveTabs[index + 1].routePath);
+ }
+ }
+ keepAliveTabs.splice(index, 1);
+
+ setKeepAliveTabs([...keepAliveTabs]);
+ },
+ [activeTabRoutePath],
+ );
+
+ // 关闭除了自己其它tab
+ const closeOtherTab = useCallback((routePath: string = activeTabRoutePath) => {
+ setKeepAliveTabs(prev => prev.filter(o => o.routePath === routePath));
+ }, [activeTabRoutePath]);
+
+ // 刷新tab
+ const refreshTab = useCallback((routePath: string = activeTabRoutePath) => {
+ setKeepAliveTabs(prev => {
+ const index = prev.findIndex(tab => tab.routePath === routePath);
+
+ if (index >= 0) {
+ // 这个是react的特性,key变了,组件会卸载重新渲染
+ prev[index].key = getKey();
+ }
+
+ return [...prev];
+ });
+ }, [activeTabRoutePath]);
+
+ useEffect(() => {
+
+ if (!matchRoute) return;
+
+ const existKeepAliveTab = keepAliveTabs.find(o => o.routePath === matchRoute?.routePath);
+
+ // 如果不存在则需要插入
+ if (!existKeepAliveTab) {
+ setKeepAliveTabs(prev => [...prev, {
+ title: matchRoute.title,
+ key: getKey(),
+ routePath: matchRoute.routePath,
+ pathname: matchRoute.pathname,
+ children: matchRoute.children,
+ icon: matchRoute.icon,
+ }]);
+ } else if (existKeepAliveTab.pathname !== matchRoute.pathname) {
+ // 如果是同一个路由,但是参数不同,我们只需要刷新当前页签并且把pathname设置为新的pathname, children设置为新的children
+ setKeepAliveTabs(prev => {
+ const index = prev.findIndex(tab => tab.routePath === matchRoute.routePath);
+ if (index >= 0) {
+ prev[index].key = getKey();
+ prev[index].pathname = matchRoute.pathname;
+ prev[index].children = matchRoute.children;
+ }
+ return [...prev];
+ });
+ }
+
+ setActiveTabRoutePath(matchRoute.routePath);
+ }, [matchRoute])
+
+
+ return {
+ tabs: keepAliveTabs,
+ activeTabRoutePath,
+ closeTab,
+ closeOtherTab,
+ refreshTab,
+ setTabs: setKeepAliveTabs,
+ }
+}
diff --git a/src/layout/404.tsx b/src/layout/404.tsx
new file mode 100644
index 0000000..a160cac
--- /dev/null
+++ b/src/layout/404.tsx
@@ -0,0 +1,17 @@
+import { Button, Result } from 'antd';
+import { Link } from 'react-router-dom';
+
+const Result404 = () => (
+
+ 首页
+
+ )}
+ />
+);
+
+export default Result404;
diff --git a/src/layout/content/index.tsx b/src/layout/content/index.tsx
new file mode 100644
index 0000000..611cab5
--- /dev/null
+++ b/src/layout/content/index.tsx
@@ -0,0 +1,41 @@
+import { Loading } from '@/components/loading';
+import { defaultSetting } from '@/default-setting';
+import { usePCScreen } from '@/hooks/use-pc-screen';
+import { useGlobalStore } from '@/store/global';
+import { FC, Suspense } from 'react';
+
+const Content: FC = ({ children }) => {
+
+ const isPC = usePCScreen();
+
+ const {
+ collapsed,
+ } = useGlobalStore();
+
+ return (
+
+
+
+ )}
+ >
+ {children}
+
+
+
+ )
+}
+
+export default Content;
diff --git a/src/layout/content/tab.tsx b/src/layout/content/tab.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/layout/header/index.tsx b/src/layout/header/index.tsx
new file mode 100644
index 0000000..0f6f498
--- /dev/null
+++ b/src/layout/header/index.tsx
@@ -0,0 +1,175 @@
+import { memo } from 'react';
+import { Avatar, Button, Dropdown, Input } from 'antd';
+
+import { Icon3 } from '@/assets/icons/3';
+import { IconBuguang } from '@/assets/icons/buguang';
+import { IconFangdajing } from '@/assets/icons/fangdajing';
+import { IconJiaretaiyang } from '@/assets/icons/jiaretaiyang';
+import { IconShuyi_fanyi36 } from '@/assets/icons/shuyi_fanyi-36';
+import { defaultSetting } from '@/default-setting';
+import { useGlobalStore } from '@/store/global';
+import { i18n, t } from '@/utils/i18n';
+import { BellOutlined, MenuOutlined, SettingOutlined } from '@ant-design/icons';
+import { useUserStore } from '@/store/global/user';
+import { useRequest } from '@/hooks/use-request';
+import loginService from '@/request/service/login';
+
+const Header = () => {
+
+ const {
+ darkMode,
+ collapsed,
+ setCollapsed,
+ setDarkMode,
+ setLang,
+ lang,
+ } = useGlobalStore();
+
+ const { currentUser } = useUserStore();
+
+ const { runAsync } = useRequest(loginService.logout, { manual: true });
+
+ const logout = async () => {
+ const [error] = await runAsync();
+ if (error) return;
+
+ useGlobalStore.setState({
+ token: '',
+ refreshToken: ''
+ });
+ }
+
+ return (
+
+
+
+
+
VOGOCM-ERP
+
+
{
+ setCollapsed(!collapsed);
+ }}
+ >
+
+
+
+
+
+ }
+ placeholder={t("jhqxJPbn" /* 搜索菜单 */)}
+ allowClear
+ />
+
+
{
+ setCollapsed(!collapsed);
+ }}
+ >
+
+
+
+
+
{ setDarkMode(!darkMode) }} className='btn-icon text-[20px]'>
+ {darkMode ? (
+
+ ) : (
+
+ )}
+
+
({
+ label: `${t(language.name)} (${language.key.toUpperCase()})`,
+ key: language.key,
+ })),
+ onClick: async ({ key }) => {
+ await i18n.changeLanguage(key);
+ setLang(key);
+ }
+ }}
+ trigger={['click']}
+ placement="bottom"
+ overlayClassName='w-[160px]'
+ >
+
+ {lang === 'zh' ? (
+
+ ) : (
+
+ {lang.toUpperCase()}
+
+ )}
+
+
+
+
+
+
node.parentElement!}
+ dropdownRender={() => {
+ return (
+
+
+
+ {currentUser?.nickName}
+
+
+ {currentUser?.phoneNumber}
+
+
+ {currentUser?.email}
+
+
+
+
+
+
+
+ )
+ }}
+ >
+
+ {currentUser?.avatarPath ? (
+
+ ) : (
+
} />
+ )}
+
+
+
+
+
+
+ )
+}
+
+export default memo(Header);
diff --git a/src/layout/index.css b/src/layout/index.css
new file mode 100644
index 0000000..5c2cd79
--- /dev/null
+++ b/src/layout/index.css
@@ -0,0 +1,24 @@
+::-webkit-scrollbar-thumb {
+ background: hsla(0, 0%, 52.9%, .4);
+
+ border-radius: 4px;
+ border: none;
+}
+
+.menu-slide::-webkit-scrollbar-thumb {
+ background: transparent
+}
+
+.menu-slide:hover::-webkit-scrollbar-thumb {
+ background: hsla(0, 0%, 52.9%, .4);
+}
+
+::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+ background-color: transparent;
+}
+
+::-webkit-scrollbar-track {
+ background-color: transparent;
+}
\ No newline at end of file
diff --git a/src/layout/index.tsx b/src/layout/index.tsx
new file mode 100644
index 0000000..30c6225
--- /dev/null
+++ b/src/layout/index.tsx
@@ -0,0 +1,168 @@
+import { useLocation, useNavigate } from "react-router-dom"
+import { useGlobalStore } from '@/store/global';
+import { lazy, useEffect, useState } from 'react';
+import GloablLoading from '@/components/global-loading';
+import Slide from './slide';
+import Header from './header';
+// import userService from '@/service';
+// import { useRequest } from '@/hooks/use-request';
+import { useUserStore } from '@/store/global/user';
+import { Menu } from '@/models';
+// import { components } from '@/config/routes';
+
+import { replaceRoutes, router } from '@/router/router';
+import Result404 from './404';
+
+import './index.css'
+// import { MenuType } from '@/pages/menu/interface';
+import TabsLayout from './tabs-layout';
+import Content from './content';
+
+const BasicLayout: React.FC = () => {
+
+ const [loading, setLoading] = useState(true);
+
+ const { refreshToken, lang, token } = useGlobalStore();
+ const { setCurrentUser, currentUser } = useUserStore();
+ const navigate = useNavigate();
+ const location = useLocation();
+ // const { setLatestMessage } = useMessageStore();
+
+ // const {
+ // data: currentUserDetail,
+ // run: getCurrentUserDetail,
+ // } = useRequest(
+ // userService.getCurrentUserDetail,
+ // { manual: true }
+ // );
+
+ const formatMenus = (
+ menus: Menu[],
+ menuGroup: Record,
+ routes: Menu[],
+ parentMenu?: Menu
+ ): Menu[] => {
+ return menus.map(menu => {
+ const children = menuGroup[menu.id];
+
+ const parentPaths = parentMenu?.parentPaths || [];
+ const path = (parentMenu ? `${parentPaths.at(-1)}${menu.route}` : menu.route) || '';
+
+ routes.push({ ...menu, path, parentPaths });
+
+ return {
+ ...menu,
+ path,
+ parentPaths,
+ children: children?.length ? formatMenus(children, menuGroup, routes, {
+ ...menu,
+ parentPaths: [...parentPaths, path || ''].filter(o => o),
+ }) : undefined,
+ };
+ });
+ }
+
+ // useEffect(() => {
+ // if (!refreshToken) {
+ // navigate('/user/login');
+ // return;
+ // }
+ // getCurrentUserDetail();
+ // }, [refreshToken, getCurrentUserDetail, navigate]);
+
+ // useEffect(() => {
+ // if (!currentUserDetail) return;
+
+ // const { menus = [] } = currentUserDetail;
+
+ // const menuGroup = menus.reduce>((prev, menu) => {
+ // if (!menu.parentId) {
+ // return prev;
+ // }
+
+ // if (!prev[menu.parentId]) {
+ // prev[menu.parentId] = [];
+ // }
+
+ // prev[menu.parentId].push(menu);
+ // return prev;
+ // }, {});
+
+ // const routes: Menu[] = [];
+
+ // currentUserDetail.menus = formatMenus(menus.filter(o => !o.parentId), menuGroup, routes);
+
+ // currentUserDetail.authList = menus
+ // .filter(menu => menu.type === MenuType.BUTTON && menu.authCode)
+ // .map(menu => menu.authCode!);
+
+
+ // console.log(components, 'components');
+
+ // replaceRoutes('*', [
+ // ...routes.map(menu => ({
+ // path: `/*${menu.path}`,
+ // Component: menu.filePath ? lazy(components[menu.filePath]) : null,
+ // id: `/*${menu.path}`,
+ // handle: {
+ // parentPaths: menu.parentPaths,
+ // path: menu.path,
+ // name: menu.name,
+ // icon: menu.icon,
+ // },
+ // })), {
+ // id: '*',
+ // path: '*',
+ // Component: Result404,
+ // handle: {
+ // path: '404',
+ // name: '404',
+ // },
+ // }
+ // ]);
+
+ // setCurrentUser(currentUserDetail);
+ // setLoading(false);
+
+ // // replace一下当前路由,为了触发路由匹配
+ // router.navigate(`${location.pathname}${location.search}`, { replace: true });
+ // }, [currentUserDetail, setCurrentUser]);
+
+ useEffect(() => {
+ function storageChange(e: StorageEvent) {
+ if (e.key === useGlobalStore.persist.getOptions().name) {
+ useGlobalStore.persist.rehydrate();
+ }
+ }
+
+ window.addEventListener<'storage'>('storage', storageChange);
+
+ return () => {
+ window.removeEventListener<'storage'>('storage', storageChange);
+ }
+ }, []);
+
+ setTimeout(()=>{
+ setLoading(false);
+ }, 3000)
+
+ if (loading) {
+ return (
+
+ )
+ }
+
+ return (
+
+ );
+};
+
+export default BasicLayout;
diff --git a/src/layout/slide/index.tsx b/src/layout/slide/index.tsx
new file mode 100644
index 0000000..d2d3587
--- /dev/null
+++ b/src/layout/slide/index.tsx
@@ -0,0 +1,77 @@
+import { memo } from 'react';
+import { Drawer } from 'antd';
+import { useUpdateEffect } from 'react-use';
+
+import { IconBuguang } from '@/assets/icons/buguang';
+import { useGlobalStore } from '@/store/global';
+import { usePCScreen } from '@/hooks/use-pc-screen';
+import { defaultSetting } from '@/default-setting';
+
+import SlideMenu from './menus';
+
+const SlideIndex = () => {
+
+ const isPC = usePCScreen();
+
+ const {
+ collapsed,
+ setCollapsed,
+ } = useGlobalStore();
+
+
+ useUpdateEffect(() => {
+ if (!isPC) {
+ setCollapsed(true);
+ } else {
+ setCollapsed(false);
+ }
+ }, [isPC]);
+
+
+ function renderMenu() {
+ return (
+
+ )
+ }
+
+ if (!isPC) {
+ return (
+
+
+ fluxy-admin
+
+ )}
+ headerStyle={{ padding: '24px 0', border: 'none' }}
+ bodyStyle={{ padding: '0 16px' }}
+ onClose={() => {
+ setCollapsed(true);
+ }}
+ >
+ {renderMenu()}
+
+ )
+ }
+
+ return (
+
+ {renderMenu()}
+
+ )
+}
+
+export default memo(SlideIndex);
diff --git a/src/layout/slide/menus.tsx b/src/layout/slide/menus.tsx
new file mode 100644
index 0000000..056dacb
--- /dev/null
+++ b/src/layout/slide/menus.tsx
@@ -0,0 +1,311 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { Menu } from 'antd';
+import type { ItemType } from 'antd/es/menu/hooks/useItems';
+import { Link, useMatches } from 'react-router-dom';
+
+import { useGlobalStore } from '@/store/global';
+import { antdIcons } from '@/assets/antd-icons';
+import { Menu as MenuType } from '@/models';
+
+import {
+ AccountBookOutlined,
+ AppstoreOutlined,
+ BarChartOutlined,
+ BgColorsOutlined,
+ CustomerServiceOutlined,
+ DeploymentUnitOutlined,
+ GlobalOutlined,
+ HighlightOutlined,
+ HomeOutlined,
+ InboxOutlined,
+ SettingOutlined,
+ ShopOutlined,
+} from '@ant-design/icons';
+import type { MenuProps, MenuTheme } from 'antd/es/menu';
+
+type MenuItem = Required['items'][number];
+
+function getItem(
+ label: React.ReactNode,
+ key: React.Key,
+ icon?: React.ReactNode,
+ children?: MenuItem[],
+ type?: 'group',
+): MenuItem {
+ return {
+ key,
+ icon,
+ children,
+ label,
+ type
+ } as MenuItem;
+}
+
+const items: MenuItem[] = [
+ getItem('首页', 'home', ),
+
+ getItem('定制选品', 'custom-made', , [
+ getItem('定制商品', 'custom-product', null, [
+ getItem('样机', 'sample'),
+ getItem('素材', 'material'),
+ getItem('款式', 'shape'),
+ getItem('成品', 'finished-product'),
+ ], 'group'),
+ getItem('模板配置', 'template', null, [
+ getItem('数据字典', 'dict'),
+ getItem('虾皮模板', 'xp-template'),
+ getItem('规则引擎', 'rules')
+ ], 'group'),
+ getItem('平台商品', 'platform-product', null, [
+ getItem('虾皮', 'xp'),
+ getItem('亚马逊', 'amazone'),
+ ], 'group'),
+ getItem('SDS商品', 'sds', null, [
+ getItem('成品库', 'finished-product-warehouse'),
+ getItem('款式', 'sds-shape'),
+ getItem('图案素材', 'pattern-material'),
+ ], 'group'),
+ ]),
+
+ getItem('AI应用', 'ai', , [
+ getItem('AI 作图', 'ai-picture'),
+ getItem('AI 画背景', 'ai-background'),
+ getItem('图片裂变', 'picture-split'),
+ getItem('轮廓出图', 'outline-drawing'),
+ getItem('一键白底', 'white-background'),
+ getItem('AI P图', 'ai-ps'),
+ getItem('AI 试装', 'ai-try'),
+ getItem('AI 作图2', 'ai-makeup'),
+ getItem('旺嘉智库', 'vogcom-libs'),
+ ]),
+ getItem('商品', 'product', , [
+ getItem('主库', 'main-db', null, [
+ getItem('商品列表', 'product-list'),
+ getItem('类目', 'category'),
+ getItem('商品属性设置', 'product-attr-settings'),
+ getItem('关键词组', 'keywords'),
+ getItem('子属性sku绑定', 'sub-sku-binding'),
+ getItem('spu商品合成', 'spu-product-compound'),
+ getItem('海外商品管理', 'overseas-product'),
+ ], 'group'),
+ getItem('平台库', 'platform-db', null, [
+ getItem('敦煌商品列表', 'dh-product-list'),
+ getItem('速卖通商品列表', 'smt-product-list'),
+ ], 'group'),
+ getItem('子库', 'sub-db', null, [
+ getItem('敦煌商品列表', 'sub-dh-product-list'),
+ getItem('速卖通商品列表', 'sub-smt-product-list'),
+ ], 'group'),
+ ]),
+
+ getItem('订单', 'order', , [
+ getItem('订单管理', 'order-manager', null, [
+ getItem('订单列表', 'order-list'),
+ getItem('合并包裹', 'merge-package'),
+ ], 'group'),
+ getItem('订单异常', 'order-exception', null, [
+ getItem('物流异常', 'logistics-anomalies'),
+ getItem('同步异常', 'sync-exception'),
+ getItem('订单同步日志', 'order-sync-logs'),
+ ], 'group'),
+ getItem('RAM管理', 'ram-manager', null, [
+ getItem('纠纷订单', 'dispute-order'),
+ getItem('退款&退货', 'refund-return'),
+ ], 'group'),
+ ]),
+
+ getItem('仓库', 'warehouse', , [
+ getItem('仓库管理', 'warehouse-manage', null, [
+ getItem('库存列表', 'stock-list'),
+ getItem('仓库列表', 'warehouse-list'),
+ getItem('篮子管理', 'box-manager'),
+ getItem('盘点计划管理', 'inventory-plan')
+ ], 'group'),
+ getItem('包裹', 'packages', null, [
+ getItem('包裹列表', 'package-list'),
+ getItem('重打面单', 're-print'),
+ getItem('包裹退回清单', 'returned-package-list'),
+ getItem('包裹扫描换单', 'scan-parcels-exchange-orders'),
+ getItem('物流汇总报表', 'logistics-summary-report'),
+ getItem('转单号批量更换', 'batch-replacement-transfer-order-no'),
+ getItem('包裹换单', 'exchange-pkg'),
+ getItem('包裹退单', 'return-pkg'),
+ ], 'group'),
+ getItem('快速出库', 'rapid-exit', null, [
+ getItem('配货', 'distribution'),
+ getItem('单sku复核打包', 'sku-review-packaging'),
+ getItem('多sku复核', 'multi-sku-review'),
+ getItem('多sku打包', 'multi-sku-packaging'),
+ getItem('出库', 'export')
+ ], 'group'),
+ getItem('柔性定制', 'flexible-customization', null, [
+ getItem('柔性定制', 'customization'),
+ getItem('一次复核', 'first-review'),
+ getItem('二次复核', 'second-review'),
+ getItem('印花', 'printing'),
+ getItem('扫描入库', 'scan-into-warehouse'),
+ getItem('异常素材补打', 'abnormal-material-reprinting')
+ ], 'group'),
+ getItem('入库', 'sub-db', null, [
+ getItem('签收', 'sign'),
+ getItem('入库日志管理', 'inbound-log-management')
+ ], 'group'),
+ ]),
+ getItem('供应链', 'supply-chain', , [
+ getItem('采购流程', 'main-db', null, [
+ getItem('缺货采购列表', 'out-of-stock-purchase-list'),
+ getItem('采购管理', 'purchase-management'),
+ getItem('备货管理', 'stocking-management'),
+ getItem('待申请付款', 'payment-pending'),
+ getItem('非常规采购入库', 'unconventional-procurement')
+ ], 'group'),
+ getItem('退货流程', 'return-process', null, [
+ getItem('退货单', 'return-form'),
+ getItem('滞留品列表', 'retained-goods-list'),
+ ], 'group'),
+ getItem('供应商', 'supplier', null, [
+ getItem('供应商品列表', 'supply-product-list'),
+ getItem('供应商商品信息', 'supply-product-information')
+ ], 'group'),
+ ]),
+
+ getItem('物流', 'logistics', , [
+ getItem('物流管理', 'logistics-management', null, [
+ getItem('物流管理', 'logistics-management1'),
+ getItem('物流服务商', 'logistics-server'),
+ getItem('回邮地址', 'email-address'),
+ getItem('寄件地址', 'post-address'),
+ getItem('包材管理', 'package-material-management')
+ ], 'group'),
+ getItem('物流费用', 'platform-db', null, [
+ getItem('包裹物流费用', 'package-logistics-fee'),
+ getItem('物流资费计算', 'logistics-fee-calc'),
+ getItem('物流运费模板管理', 'logistics-fee-template-management'),
+ ], 'group'),
+ ]),
+ getItem('客服', 'service', , [
+ getItem('客户管理', 'customer-management', null, [
+ getItem('客户列表', 'customer-list'),
+ getItem('客户类型列表', 'customer-type-list'),
+ getItem('推广客户列表', 'promotion-customer-list')
+ ], 'group'),
+ getItem('客诉任务管理', 'customer-complaint-task-management', null, [
+ getItem('敦煌客诉管理', 'dh-customer-complaint')
+ ], 'group'),
+ ]),
+
+ getItem('财务', 'financial', , [
+ getItem('账户', 'account', null, [
+ getItem('账户管理', 'account-management'),
+ getItem('商户充值', 'merchant-recharge'),
+ getItem('商户流水管理', 'merchant-flow-management'),
+ ], 'group'),
+ getItem('订单', 'platform-db', null, [
+ getItem('订单流水', 'order-flow'),
+ ], 'group'),
+ getItem('供应处理', 'supply-management', null, [
+ getItem('采购付款', 'purchase-payment'),
+ getItem('供应商商品信息', 'supplier-product-info'),
+ getItem('退货单审核', 'return-list-review'),
+ getItem('备货单审核', 'stocking-list-review')
+ ], 'group'),
+ ]),
+
+
+ getItem('报表', 'report', , [
+ getItem('销售报表', 'sale-report', null, [
+ getItem('收支报表', 'income-report'),
+ getItem('订单状态报表', 'order-status-report'),
+ getItem('订单状态报表2', 'order-status-report-2'),
+ ], 'group'),
+ getItem('物流报表', 'logistics-report', null, [
+ getItem('质检报表', 'quality-inspection-report'),
+ getItem('发货报表', 'shipping-report')
+ ], 'group'),
+ ]),
+
+ getItem('基础配置', 'settings', , [
+ getItem('基础配置', 'basic-settings', null, [
+ getItem('组织权限', 'organization-permissions'),
+ getItem('图片空间', 'pic-space'),
+ getItem('平台内部消息', 'platform-message'),
+ getItem('汇率管理', 'exchange-rate-manager'),
+ getItem('国家管理', 'country-manager'),
+ ], 'group'),
+ getItem('商户&店铺', 'retailer', null, [
+ getItem('商铺管理', 'retailer-manager'),
+ getItem('商户信息', 'merchant-information'),
+ getItem('商户等级', 'merchant-level')
+ ], 'group'),
+ ]),
+];
+
+const SlideMenu = () => {
+
+ const matches = useMatches();
+
+ const [openKeys, setOpenKeys] = useState([]);
+ const [selectKeys, setSelectKeys] = useState([]);
+
+ const {
+ collapsed,
+ } = useGlobalStore();
+
+ useEffect(() => {
+ if (collapsed) {
+ setOpenKeys([]);
+ } else {
+ const [match] = matches || [];
+ if (match) {
+ // 获取当前匹配的路由,默认为最后一个
+ const route = matches.at(-1);
+ // 从匹配的路由中取出自定义参数
+ const handle = route?.handle as any;
+ // 从自定义参数中取出上级path,让菜单自动展开
+ setOpenKeys(handle?.parentPaths || []);
+ // 让当前菜单和所有上级菜单高亮显示
+ setSelectKeys([...(handle?.parentPaths || []), handle?.path] || []);
+ }
+ }
+ }, [
+ matches,
+ collapsed,
+ ]);
+
+ const getMenuTitle = (menu: MenuType) => {
+ if (menu?.children?.filter(menu => menu.show)?.length) {
+ return menu.name;
+ }
+ return (
+ {menu.name}
+ );
+ }
+
+ const treeMenuData = useCallback((menus: MenuType[]): ItemType[] => {
+ return (menus)
+ .map((menu: MenuType) => {
+ const children = menu?.children?.filter(menu => menu.show) || [];
+ return {
+ key: menu.path,
+ label: getMenuTitle(menu),
+ icon: menu.icon && antdIcons[menu.icon] && React.createElement(antdIcons[menu.icon]),
+ children: children.length ? treeMenuData(children || []) : null,
+ };
+ })
+ }, []);
+
+ return (
+
+ )
+}
+
+export default SlideMenu;
diff --git a/src/layout/tabs-context.tsx b/src/layout/tabs-context.tsx
new file mode 100644
index 0000000..c26d4a7
--- /dev/null
+++ b/src/layout/tabs-context.tsx
@@ -0,0 +1,18 @@
+/* eslint-disable @typescript-eslint/no-empty-function */
+
+import { createContext } from 'react'
+
+interface KeepAliveTabContextType {
+ refreshTab: (path?: string) => void;
+ closeTab: (path?: string) => void;
+ closeOtherTab: (path?: string) => void;
+}
+
+const defaultValue = {
+ refreshTab: () => { },
+ closeTab: () => { },
+ closeOtherTab: () => { },
+}
+
+
+export const KeepAliveTabContext = createContext(defaultValue);
diff --git a/src/layout/tabs-layout.tsx b/src/layout/tabs-layout.tsx
new file mode 100644
index 0000000..889d813
--- /dev/null
+++ b/src/layout/tabs-layout.tsx
@@ -0,0 +1,124 @@
+import React, { useCallback, useMemo } from "react";
+import { Dropdown } from 'antd';
+import { antdIcons } from '@/assets/antd-icons';
+import { KeepAliveTab, useTabs } from '@/hooks/use-tabs';
+import { router } from '@/router/router';
+import type { MenuItemType } from 'antd/es/menu/hooks/useItems';
+import { KeepAliveTabContext } from './tabs-context';
+import DraggableTab from '@/components/draggable-tab';
+
+enum OperationType {
+ REFRESH = 'refresh',
+ CLOSE = 'close',
+ CLOSEOTHER = 'close-other',
+}
+
+const TabsLayout: React.FC = () => {
+
+ const { activeTabRoutePath, tabs, closeTab, refreshTab, closeOtherTab } = useTabs();
+
+ const getIcon = (icon?: string): React.ReactElement | undefined => {
+ return icon && antdIcons[icon] && React.createElement(antdIcons[icon]);
+ }
+
+ const menuItems: MenuItemType[] = useMemo(
+ () => [
+ {
+ label: '刷新',
+ key: OperationType.REFRESH,
+ },
+ tabs.length <= 1 ? null : {
+ label: '关闭',
+ key: OperationType.CLOSE,
+ },
+ tabs.length <= 1 ? null : {
+ label: '关闭其他',
+ key: OperationType.CLOSEOTHER,
+ },
+ ].filter(o => o !== null) as MenuItemType[],
+ [tabs]
+ );
+
+ const menuClick = useCallback(({ key, domEvent }: any, tab: KeepAliveTab) => {
+ domEvent.stopPropagation();
+
+ if (key === OperationType.REFRESH) {
+ refreshTab(tab.routePath);
+ } else if (key === OperationType.CLOSE) {
+ closeTab(tab.routePath);
+ } else if (key === OperationType.CLOSEOTHER) {
+ closeOtherTab(tab.routePath);
+ }
+ }, [closeOtherTab, closeTab, refreshTab]);
+
+ const renderTabTitle = useCallback((tab: KeepAliveTab) => {
+ return (
+ menuClick(e, tab) }}
+ trigger={['contextMenu']}
+ >
+
+ {getIcon(tab.icon)}
+ {tab.title}
+
+
+ )
+ }, [menuItems]);
+
+ const tabItems = useMemo(() => {
+ return tabs.map(tab => {
+ return {
+ key: tab.routePath,
+ label: renderTabTitle(tab),
+ children: (
+
+ {tab.children}
+
+ ),
+ closable: tabs.length > 1, // 剩最后一个就不能删除了
+ }
+ })
+ }, [tabs]);
+
+
+ const onTabsChange = useCallback((tabRoutePath: string) => {
+ router.navigate(tabRoutePath);
+ }, []);
+
+ const onTabEdit = (
+ targetKey: React.MouseEvent | React.KeyboardEvent | string,
+ action: 'add' | 'remove',
+ ) => {
+ if (action === 'remove') {
+ closeTab(targetKey as string);
+ }
+ };
+
+ const keepAliveContextValue = useMemo(
+ () => ({
+ closeTab,
+ closeOtherTab,
+ refreshTab,
+ }),
+ [closeTab, closeOtherTab, refreshTab]
+ );
+
+ return (
+
+
+
+ )
+}
+
+export default TabsLayout;
diff --git a/src/main.tsx b/src/main.tsx
index 3d7150d..871074f 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,10 +1,18 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import App from './App.tsx'
-import './index.css'
+import ReactDOM from 'react-dom/client';
+import NProgress from 'nprogress';
+import App from './App';
+import 'virtual:windi.css';
+import 'nprogress/nprogress.css';
+import '@/assets/css/overwrite.css';
-ReactDOM.createRoot(document.getElementById('root')!).render(
-
-
- ,
+NProgress.configure({
+ minimum: 0.3,
+ easing: 'ease',
+ speed: 800,
+ showSpinner: false,
+ parent: '#root'
+});
+
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
)
diff --git a/src/models/index.ts b/src/models/index.ts
new file mode 100644
index 0000000..a864daa
--- /dev/null
+++ b/src/models/index.ts
@@ -0,0 +1,6 @@
+export * from './user.ts'
+
+export interface PageData {
+ data: T[];
+ total: number;
+}
diff --git a/src/models/user.ts b/src/models/user.ts
new file mode 100644
index 0000000..d171ca5
--- /dev/null
+++ b/src/models/user.ts
@@ -0,0 +1,108 @@
+export interface LoginDTO {
+ userName: string;
+ password: string;
+}
+
+export interface TokenDTO {
+ expire: number;
+ token: string;
+ refreshExpire: number;
+ refreshToken: string;
+}
+
+export interface RoleDTO {
+ id: number;
+ roleName: string;
+ status: number;
+ createTime: string;
+ createOper: string;
+ operTime: string;
+ oper: string;
+}
+
+export interface MerchantDTO {
+ id: number;
+ merchantName: string;
+ levelId: number;
+ merchantStatus: number;
+ balance: number;
+ frozenBalance: number;
+ integral: number;
+ relationName: string;
+ phone: string;
+ address: string;
+ remark: string;
+ creatorId: number;
+ createTime: string;
+ isDelete: number;
+}
+
+export interface UserDTO {
+ id: number;
+ userName: string;
+ password: string;
+ phoneNumber: string;
+ emailAddress: string;
+ name: string;
+ avatarId: number;
+ status: number;
+ isDelete: number;
+ merchantId: number;
+ roles: Array;
+ avatarUrl: string;
+ merchant: MerchantDTO;
+ idToString: string;
+}
+
+export interface PopMenu {
+ id: number;
+ parentid: number;
+ homeid: number;
+ menuName: string;
+ parentMenuName: string;
+ pageUrl: string;
+ sort: number;
+ level: number;
+}
+
+export interface LoginRespDTO {
+ ack: number;
+ data: UserDTO;
+ msg: string;
+ pop: Array;
+}
+
+
+export interface Menu {
+ id: string;
+ parentId?: string;
+ name?: string;
+ icon?: string;
+ type?: number;
+ route?: string;
+ filePath?: string;
+ orderNumber?: number;
+ url?: string;
+ show?: boolean;
+ children?: Menu[];
+ path: string;
+ Component?: any;
+ parentPaths?: string[];
+ authCode?: string;
+}
+
+export interface User {
+ id: number;
+ userName: string;
+ nickName: string;
+ phoneNumber: string;
+ email: string;
+ createDate: string;
+ updateDate: string;
+ avatar?: any;
+ menus: Menu[];
+ routes: any[];
+ flatMenus: Menu[];
+ avatarPath: string;
+ authList: string[];
+}
diff --git a/src/pages/login/index.css b/src/pages/login/index.css
new file mode 100644
index 0000000..6ac1533
--- /dev/null
+++ b/src/pages/login/index.css
@@ -0,0 +1,43 @@
+.img1 {
+ animation: img1-anim 10s linear 0ms infinite normal backwards;
+}
+
+.img2 {
+ animation: img2-anim 8s linear 0ms infinite normal backwards;
+}
+
+@keyframes img1-anim {
+ 0% {
+ transform: translate3d(0, 0, 0);
+ }
+
+ 50% {
+ transform: translate3d(0px, 30px, 0)
+ }
+
+ 100% {
+ transform: translate3d(0px, 0px, 0)
+ }
+}
+
+@keyframes img2-anim {
+ 0% {
+ transform: translate3d(0px, 0px, 0)
+ }
+
+ 50% {
+ transform: translate3d(0px, 20px, 0)
+ }
+
+ 100% {
+ transform: translate3d(0px, 0px, 0)
+ }
+}
+
+.custom.slick-dots .slick-active button {
+ background: #000 !important;
+}
+
+.custom.slick-dots button {
+ background: rgba(0, 0, 0, .7) !important;
+}
\ No newline at end of file
diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx
new file mode 100644
index 0000000..fd2c6d2
--- /dev/null
+++ b/src/pages/login/index.tsx
@@ -0,0 +1,148 @@
+import { t } from '@/utils/i18n';
+import { IconBuguang } from '@/assets/icons/buguang'
+import { LockOutlined, UserOutlined } from '@ant-design/icons';
+import { Button, Form, Input, Carousel } from 'antd';
+import { useNavigate } from 'react-router-dom';
+import './index.css'
+
+const Login = () => {
+ const navigate = useNavigate();
+
+ const onFinish = async () => {
+ navigate('/');
+ };
+
+ return (
+
+
+
+
+
+
+
旺嘉-ERP Admin
+
+
+ {t("wbTMzvDM" /* 一个高颜值后台管理系统 */)}
+
+
+
+ }
+ placeholder={t("RNISycbR" /* 账号 */)}
+ size="large"
+ />
+
+
+ }
+ type="password"
+ placeholder={t("HplkKxdY" /* 密码 */)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ fluxy-admin
+
+
+ 一个高颜值后台管理系统
+
+
+
+
+
+
+
+
+ fluxy-admin
+
+
+ 一个高颜值后台管理系统
+
+
+
+
+
+
+
+
+ fluxy-admin
+
+
+ 一个高颜值后台管理系统
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Login;
diff --git a/src/request/index.ts b/src/request/index.ts
new file mode 100644
index 0000000..b23842c
--- /dev/null
+++ b/src/request/index.ts
@@ -0,0 +1,176 @@
+import axios, {
+ AxiosInstance,
+ AxiosRequestConfig,
+ AxiosResponse,
+ CreateAxiosDefaults,
+ InternalAxiosRequestConfig,
+} from 'axios';
+import {useGlobalStore} from '@/store/global';
+import {antdUtils} from '@/utils/antd';
+
+const refreshTokenUrl = '/api/auth/refresh/token';
+
+export type Response = Promise<[boolean, T, AxiosResponse]>;
+
+class Request {
+ constructor(config?: CreateAxiosDefaults) {
+ this.axiosInstance = axios.create(config);
+
+ this.axiosInstance.interceptors.request.use(
+ (axiosConfig: InternalAxiosRequestConfig) =>
+ this.requestInterceptor(axiosConfig)
+ );
+ this.axiosInstance.interceptors.response.use(
+ (response: AxiosResponse) =>
+ this.responseSuccessInterceptor(response),
+ (error: any) => this.responseErrorInterceptor(error)
+ );
+ }
+
+ private axiosInstance: AxiosInstance;
+
+ private refreshTokenFlag = false;
+ private requestQueue: {
+ resolve: any;
+ config: any;
+ type: 'reuqest' | 'response';
+ }[] = [];
+ private limit = 3;
+
+ private requestingCount = 0;
+
+ setLimit(limit: number) {
+ this.limit = limit;
+ }
+
+ private async requestInterceptor(
+ axiosConfig: InternalAxiosRequestConfig
+ ): Promise {
+ if ([refreshTokenUrl].includes(axiosConfig.url || '')) {
+ return Promise.resolve(axiosConfig);
+ }
+
+ if (this.refreshTokenFlag || this.requestingCount >= this.limit) {
+ return new Promise((resolve) => {
+ this.requestQueue.push({
+ resolve,
+ config: axiosConfig,
+ type: 'reuqest',
+ });
+ });
+ }
+
+ this.requestingCount += 1;
+
+ const {token} = useGlobalStore.getState();
+
+ if (token) {
+ axiosConfig.headers.Authorization = `Bearer ${token}`;
+ }
+ return Promise.resolve(axiosConfig);
+ }
+
+ private requestByQueue() {
+ if (!this.requestQueue.length) return;
+
+ console.log(
+ this.requestingCount,
+ this.limit - this.requestingCount,
+ 'count'
+ );
+
+ Array.from({length: this.limit - this.requestingCount}).forEach(
+ async () => {
+ const record = this.requestQueue.shift();
+ if (!record) {
+ return;
+ }
+
+ const {config, resolve, type} = record;
+ if (type === 'response') {
+ resolve(await this.request(config));
+ } else if (type === 'reuqest') {
+ this.requestingCount += 1;
+ const {token} = useGlobalStore.getState();
+ config.headers.Authorization = `Bearer ${token}`;
+ resolve(config);
+ }
+ }
+ );
+ }
+
+ private async responseSuccessInterceptor(
+ response: AxiosResponse
+ ): Promise {
+ if (response.config.url !== refreshTokenUrl) {
+ this.requestingCount -= 1;
+ if (this.requestQueue.length) {
+ this.requestByQueue();
+ }
+ }
+
+ return Promise.resolve([false, response.data, response]);
+ }
+
+ private async responseErrorInterceptor(error: any): Promise {
+ this.requestingCount -= 1;
+ const {config, status} = error?.response || {};
+
+ if (status === 401) {
+ return new Promise((resolve) => {
+ this.requestQueue.unshift({resolve, config, type: 'response'});
+ if (this.refreshTokenFlag) return;
+
+ this.refreshTokenFlag = true;
+ });
+ } else {
+ antdUtils.notification?.error({
+ message: '出错了',
+ description: error?.response?.data?.message,
+ });
+ return Promise.resolve([true, error?.response?.data]);
+ }
+ }
+
+ private reset() {
+ this.requestQueue = [];
+ this.refreshTokenFlag = false;
+ this.requestingCount = 0;
+ }
+
+ private toLoginPage() {
+ this.reset();
+ }
+
+ request(config: AxiosRequestConfig): Response {
+ return this.axiosInstance(config);
+ }
+
+ get(url: string, config?: AxiosRequestConfig): Response {
+ return this.axiosInstance.get(url, config);
+ }
+
+ post(
+ url: string,
+ data?: D,
+ config?: AxiosRequestConfig
+ ): Response {
+ return this.axiosInstance.post(url, data, config);
+ }
+
+ put(
+ url: string,
+ data?: D,
+ config?: AxiosRequestConfig
+ ): Response {
+ return this.axiosInstance.put(url, data, config);
+ }
+
+ delete(url: string, config?: AxiosRequestConfig): Response {
+ return this.axiosInstance.delete(url, config);
+ }
+}
+
+const request = new Request({timeout: 60 * 1000 * 5, baseURL: 'https://test.vogocm.com:9697'});
+
+export default request;
diff --git a/src/request/service/login.ts b/src/request/service/login.ts
new file mode 100644
index 0000000..a9e4109
--- /dev/null
+++ b/src/request/service/login.ts
@@ -0,0 +1,15 @@
+import request from '@/request';
+import { LoginDTO, LoginRespDTO } from '@/models'
+
+const loginService = {
+ // 登录
+ login: (loginDTO: LoginDTO) => {
+ return request.post('/api/login', loginDTO);
+ },
+
+ logout: () => {
+ return request.get('/api/logout');
+ }
+};
+
+export default loginService;
diff --git a/src/request/service/user.ts b/src/request/service/user.ts
new file mode 100644
index 0000000..5d2923e
--- /dev/null
+++ b/src/request/service/user.ts
@@ -0,0 +1,5 @@
+const userService = {
+
+};
+
+export default userService;
diff --git a/src/router/router-error-element.tsx b/src/router/router-error-element.tsx
new file mode 100644
index 0000000..117260e
--- /dev/null
+++ b/src/router/router-error-element.tsx
@@ -0,0 +1,8 @@
+import { useRouteError } from 'react-router-dom';
+
+const RouterErrorElement = () => {
+ const error = useRouteError();
+ throw error;
+}
+
+export default RouterErrorElement;
\ No newline at end of file
diff --git a/src/router/router.tsx b/src/router/router.tsx
new file mode 100644
index 0000000..f5b4c81
--- /dev/null
+++ b/src/router/router.tsx
@@ -0,0 +1,89 @@
+import { RouteObject, RouterProvider, createBrowserRouter, Navigate } from 'react-router-dom';
+
+import Login from '@/pages/login';
+import BasicLayout from '@/layout';
+import { App } from 'antd';
+import { useEffect } from 'react';
+import { antdUtils } from '@/utils/antd';
+import RouterErrorElement from './router-error-element';
+
+export const router = createBrowserRouter(
+ [
+ {
+ path: '/login',
+ Component: Login,
+ },
+ {
+ path: '/',
+ element: (
+
+ ),
+ },
+ {
+ path: '*',
+ Component: BasicLayout,
+ children: [],
+ errorElement:
+ },
+ ]
+);
+
+export const toLoginPage = () => {
+ router.navigate('/login');
+}
+
+function findNodeByPath(routes: RouteObject[], path: string) {
+ for (let i = 0; i < routes.length; i += 1) {
+ const element = routes[i];
+
+ if (element.path === path) return element;
+
+ findNodeByPath(element.children || [], path);
+ }
+}
+
+export const addRoutes = (parentPath: string, routes: RouteObject[]) => {
+ if (!parentPath) {
+ router.routes.push(...routes as any);
+ return;
+ }
+
+ const curNode = findNodeByPath(router.routes, parentPath);
+
+ if (curNode?.children) {
+ curNode?.children.push(...routes);
+ } else if (curNode) {
+ curNode.children = routes;
+ }
+}
+
+export const replaceRoutes = (parentPath: string, routes: RouteObject[]) => {
+ if (!parentPath) {
+ router.routes.push(...routes as any);
+ return;
+ }
+
+ const curNode = findNodeByPath(router.routes, parentPath);
+
+ if (curNode) {
+ curNode.children = routes;
+ }
+}
+
+
+const Router = () => {
+ const { notification, message, modal } = App.useApp();
+
+ useEffect(() => {
+ antdUtils.setMessageInstance(message);
+ antdUtils.setNotificationInstance(notification);
+ antdUtils.setModalInstance(modal);
+ }, [notification, message, modal]);
+
+ return (
+
+ )
+};
+
+export default Router;
+
diff --git a/src/store/global/index.ts b/src/store/global/index.ts
new file mode 100644
index 0000000..3c7636d
--- /dev/null
+++ b/src/store/global/index.ts
@@ -0,0 +1,53 @@
+import { create } from 'zustand'
+import { devtools, persist, createJSONStorage } from 'zustand/middleware';
+
+interface State {
+ darkMode: boolean;
+ collapsed: boolean;
+ lang: string;
+ token: string;
+ refreshToken: string;
+}
+
+interface Action {
+ setDarkMode: (darkMode: State['darkMode']) => void;
+ setCollapsed: (collapsed: State['collapsed']) => void;
+ setLang: (lang: State['lang']) => void;
+ setToken: (lang: State['token']) => void;
+ setRefreshToken: (lang: State['refreshToken']) => void;
+}
+
+export const useGlobalStore = create()(
+ devtools(persist(
+ (set) => {
+ return {
+ darkMode: false,
+ collapsed: false,
+ lang: 'zh',
+ token: '',
+ refreshToken: '',
+ setDarkMode: (darkMode: State['darkMode']) => set({
+ darkMode,
+ }),
+ setCollapsed: (collapsed: State['collapsed']) => set({
+ collapsed,
+ }),
+ setLang: (lang: State['lang']) => set({
+ lang,
+ }),
+ setToken: (token: State['token']) => set({
+ token,
+ }),
+ setRefreshToken: (refreshToken: State['refreshToken']) => set({
+ refreshToken,
+ }),
+ };
+ },
+ {
+ name: 'globalStore',
+ storage: createJSONStorage(() => localStorage),
+ }
+ ),
+ { name: 'globalStore' }
+ )
+)
diff --git a/src/store/global/user.ts b/src/store/global/user.ts
new file mode 100644
index 0000000..15cd99f
--- /dev/null
+++ b/src/store/global/user.ts
@@ -0,0 +1,24 @@
+import { UserDTO } from '@/models/user';
+import { create } from 'zustand';
+import { devtools } from 'zustand/middleware';
+
+interface State {
+ currentUser: UserDTO | null;
+}
+
+interface Action {
+ setCurrentUser: (currentUser: State['currentUser']) => void;
+}
+
+export const useUserStore = create()(
+ devtools(
+ (set) => {
+ return {
+ currentUser: null,
+ setCurrentUser: (currentUser: State['currentUser']) =>
+ set({currentUser}),
+ };
+ },
+ {name: 'globalUserStore'}
+ )
+);
diff --git a/src/store/index.ts b/src/store/index.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/utils/antd.ts b/src/utils/antd.ts
new file mode 100644
index 0000000..ba28089
--- /dev/null
+++ b/src/utils/antd.ts
@@ -0,0 +1,26 @@
+import { MessageInstance } from 'antd/es/message/interface';
+import { ModalStaticFunctions } from 'antd/es/modal/confirm';
+import { NotificationInstance } from 'antd/es/notification/interface';
+
+type ModalInstance = Omit;
+
+class AntdUtils {
+ message: MessageInstance | null = null;
+ notification: NotificationInstance | null = null;
+ modal: ModalInstance | null = null;
+
+ setMessageInstance(message: MessageInstance) {
+ this.message = message;
+ this.message.success
+ }
+
+ setNotificationInstance(notification: NotificationInstance) {
+ this.notification = notification;
+ }
+
+ setModalInstance(modal: ModalInstance) {
+ this.modal = modal;
+ }
+}
+
+export const antdUtils = new AntdUtils();
\ No newline at end of file
diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts
new file mode 100644
index 0000000..5ceeea2
--- /dev/null
+++ b/src/utils/i18n.ts
@@ -0,0 +1,27 @@
+import i18n from "i18next";
+import enUS from '@/assets/locales/en-US'
+import zhCN from '@/assets/locales/zh-CN'
+import { defaultSetting } from '@/default-setting';
+
+i18n
+ .init({
+ resources: {
+ 'en': {
+ translation: enUS,
+ },
+ 'zh': {
+ translation: zhCN,
+ },
+ },
+ lng: defaultSetting.defaultLang || 'zh',
+ fallbackLng: defaultSetting.defaultLang || 'zh',
+ interpolation: {
+ escapeValue: false
+ },
+ });
+
+export const t = (key: string) => {
+ return i18n.t(key) || key;
+};
+
+export { i18n };
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
new file mode 100644
index 0000000..0e1dc22
--- /dev/null
+++ b/src/utils/utils.ts
@@ -0,0 +1,12 @@
+export function getParamsBySearchParams(query: URLSearchParams) {
+ const params = [...query.keys()].reduce>(
+ (prev, cur: string) => {
+ if (cur) {
+ prev[cur] = query.get(cur);
+ }
+ return prev;
+ },
+ {}
+ );
+ return params as T;
+}
diff --git a/tsconfig.json b/tsconfig.json
index a7fc6fb..e7b52dd 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -18,7 +18,11 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
+ "noFallthroughCasesInSwitch": true,
+ "baseUrl": "./",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]