@@ -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 |
@@ -4,7 +4,7 @@ | |||||
<meta charset="UTF-8" /> | <meta charset="UTF-8" /> | ||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | <link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
<title>Vite + React + TS</title> | |||||
<title>vogocm erp admin</title> | |||||
</head> | </head> | ||||
<body> | <body> | ||||
<div id="root"></div> | <div id="root"></div> | ||||
@@ -4,6 +4,7 @@ | |||||
"version": "0.0.0", | "version": "0.0.0", | ||||
"type": "module", | "type": "module", | ||||
"scripts": { | "scripts": { | ||||
"start": "vite", | |||||
"dev": "vite", | "dev": "vite", | ||||
"build": "tsc && vite build", | "build": "tsc && vite build", | ||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | ||||
@@ -0,0 +1,39 @@ | |||||
<svg width="670" height="903" viewBox="0 0 670 903" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="670" height="903"> | |||||
<g opacity="0.2"> | |||||
<path d="M0 0H670V903H0V0Z" fill="white"/> | |||||
</g> | |||||
</mask> | |||||
<g mask="url(#mask0)"> | |||||
<path d="M2030.91 374.849L426.331 1300.78" stroke="#8492C4"/> | |||||
<path d="M426.409 -527.071L2030.72 399.311" stroke="#8492C4"/> | |||||
<path d="M1919.22 310.39L314.731 1236.47" stroke="#8492C4"/> | |||||
<path d="M314.731 -462.612L1919.22 463.467" stroke="#8492C4"/> | |||||
<path d="M1807.54 245.932L203.055 1172.01" stroke="#8492C4"/> | |||||
<path d="M203.052 -398.154L1807.54 527.925" stroke="#8492C4"/> | |||||
<path d="M1695.87 181.473L91.3788 1107.55" stroke="#8492C4"/> | |||||
<path d="M91.3744 -333.695L1695.86 592.384" stroke="#8492C4"/> | |||||
<path d="M1584.19 117.014L-20.3012 1043.09" stroke="#8492C4"/> | |||||
<path d="M-20.3044 -269.237L1584.19 656.843" stroke="#8492C4"/> | |||||
<path d="M1472.51 52.5562L-131.98 978.636" stroke="#8492C4"/> | |||||
<path d="M-131.983 -204.778L1472.51 721.301" stroke="#8492C4"/> | |||||
<path d="M1360.83 -11.9023L-243.658 914.177" stroke="#8492C4"/> | |||||
<path d="M-243.662 -140.319L1360.83 785.76" stroke="#8492C4"/> | |||||
<path d="M1249.15 -76.3613L-355.336 849.718" stroke="#8492C4"/> | |||||
<path d="M-355.341 -75.8608L1249.15 850.219" stroke="#8492C4"/> | |||||
<path d="M1137.48 -140.819L-467.014 785.26" stroke="#8492C4"/> | |||||
<path d="M-467.017 -11.4023L1137.47 914.677" stroke="#8492C4"/> | |||||
<path d="M1025.8 -205.278L-578.692 720.801" stroke="#8492C4"/> | |||||
<path d="M-578.693 53.0562L1025.8 979.136" stroke="#8492C4"/> | |||||
<path d="M914.119 -269.736L-690.371 656.343" stroke="#8492C4"/> | |||||
<path d="M-690.379 117.515L914.111 1043.59" stroke="#8492C4"/> | |||||
<path d="M802.441 -334.195L-802.052 591.887" stroke="#8492C4"/> | |||||
<path d="M-802.055 181.974L802.435 1108.05" stroke="#8492C4"/> | |||||
<path d="M690.762 -398.654L-913.728 527.426" stroke="#8492C4"/> | |||||
<path d="M-913.731 246.432L690.759 1172.51" stroke="#8492C4"/> | |||||
<path d="M579.084 -463.112L-1025.41 462.967" stroke="#8492C4"/> | |||||
<path d="M-1025.41 310.891L579.083 1236.97" stroke="#8492C4"/> | |||||
<path d="M467.406 -527.571L-1136.91 398.811" stroke="#8492C4"/> | |||||
<path d="M-1137.09 375.35L467.397 1301.43" stroke="#8492C4"/> | |||||
</g> | |||||
</svg> |
@@ -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() { | 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 ( | return ( | ||||
<> | |||||
<div> | |||||
<a href="https://vitejs.dev" target="_blank"> | |||||
<img src={viteLogo} className="logo" alt="Vite logo" /> | |||||
</a> | |||||
<a href="https://react.dev" target="_blank"> | |||||
<img src={reactLogo} className="logo react" alt="React logo" /> | |||||
</a> | |||||
</div> | |||||
<h1>Vite + React</h1> | |||||
<div className="card"> | |||||
<button onClick={() => setCount((count) => count + 1)}> | |||||
count is {count} | |||||
</button> | |||||
<p> | |||||
Edit <code>src/App.tsx</code> and save to test HMR | |||||
</p> | |||||
</div> | |||||
<p className="read-the-docs"> | |||||
Click on the Vite and React logos to learn more | |||||
</p> | |||||
</> | |||||
<ConfigProvider | |||||
theme={curTheme} | |||||
locale={lang === 'zh' ? zhCN : enUS} | |||||
componentSize='large' | |||||
> | |||||
<AntdApp> | |||||
<Router /> | |||||
</AntdApp> | |||||
</ConfigProvider> | |||||
) | ) | ||||
} | } | ||||
@@ -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, | |||||
}; |
@@ -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); | |||||
} |
@@ -0,0 +1,22 @@ | |||||
import Icon from "@ant-design/icons"; | |||||
import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon"; | |||||
const SVG3 = () => ( | |||||
<svg | |||||
style={{ | |||||
width: "1em", | |||||
height: "1em", | |||||
fill: "currentcolor", | |||||
overflow: "hidden", | |||||
}} | |||||
viewBox="0 0 1024 1024" | |||||
version="1.1" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
> | |||||
<path d="M477.04728007 930.47791389c-68.87620267 0-137.16909283-17.11280925-197.49704135-49.49069028-58.36611129-31.32303019-109.17128989-76.74334435-146.92122851-131.34878151-5.38952135-7.79611933-5.51643022-18.08150187-0.32018091-26.00802304 5.19624931-7.92884907 14.68059648-11.9142309 23.97865643-10.07930255 21.53130894 4.24501931 43.46895587 6.39663673 65.20401578 6.39663674 88.70996878 0 172.10725945-34.54463431 234.83016192-97.27102976 62.72290361-62.72756053 97.26637283-146.13067207 97.26637284-234.84762682 0-97.14994403-42.43739079-189.1630171-116.43066937-252.4459463-7.21164402-6.16727097-9.95937963-16.09405099-6.94501945-25.09288676s11.18654691-15.26740082 20.6592512-15.84605526c9.61940594-0.58796942 18.181632-0.87438563 26.17801045-0.87438563 56.47762432 0 111.27866368 11.066624 162.88138468 32.89249906 49.83182791 21.07723321 94.58034461 51.24644637 133.00324579 89.66818361 38.42406514 38.42290119 68.59444338 83.17258183 89.67400448 133.00557368 21.82820409 51.60621397 32.89599203 106.41191026 32.89599204 162.89652054 0 56.47413134-11.06778909 111.27284281-32.89715712 162.87672775-21.07839715 49.82950002-51.24877539 94.57685163-89.67284054 132.99975282s-83.17258183 68.59211435-133.00324579 89.67167659C588.32594375 919.4101248 533.52374045 930.47791389 477.04728007 930.47791389zM205.69434681 766.18773163c27.73001557 29.56377998 60.21966848 54.62639502 95.87853312 73.76274887 53.57969408 28.7545947 114.25809522 43.95446727 175.47440014 43.95446727 99.32601117 0 192.71178809-38.6848677 262.95487374-108.92795222 70.2442496-70.24075662 108.92911616-163.62304057 108.92911616-262.9443948 0-99.33765405-38.6848677-192.72925298-108.92911616-262.97117355-62.65537422-62.65537422-143.73338795-100.2003968-231.01360242-107.57737017 58.48254009 68.18926819 91.17128818 155.28552448 91.17128818 246.3438757 0 101.15511865-39.38693689 196.25357426-110.90608924 267.77738354-71.51915235 71.52380928-166.61295104 110.9130752-267.76224768 110.9130752C216.23354595 766.51955541 210.96511033 766.40894749 205.69434681 766.18773163z"></path> | |||||
</svg> | |||||
); | |||||
export const Icon3 = (props: Partial<CustomIconComponentProps>) => ( | |||||
<Icon component={SVG3} {...props} /> | |||||
); |
@@ -0,0 +1,22 @@ | |||||
import Icon from "@ant-design/icons"; | |||||
import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon"; | |||||
const SVGBuguang = () => ( | |||||
<svg | |||||
style={{ | |||||
width: "1em", | |||||
height: "1em", | |||||
fill: "currentcolor", | |||||
overflow: "hidden", | |||||
}} | |||||
viewBox="0 0 1024 1024" | |||||
version="1.1" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
> | |||||
<path d="M547.84 0 128 468.48l194.56 112.64L140.8 1024l673.28-547.84-204.8-117.76L896 102.4 547.84 0z"></path> | |||||
</svg> | |||||
); | |||||
export const IconBuguang = (props: Partial<CustomIconComponentProps>) => ( | |||||
<Icon component={SVGBuguang} {...props} /> | |||||
); |
@@ -0,0 +1,22 @@ | |||||
import Icon from "@ant-design/icons"; | |||||
import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon"; | |||||
const SVGFangdajing = () => ( | |||||
<svg | |||||
style={{ | |||||
width: "1em", | |||||
height: "1em", | |||||
fill: "currentcolor", | |||||
overflow: "hidden", | |||||
}} | |||||
viewBox="0 0 1024 1024" | |||||
version="1.1" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
> | |||||
<path d="M781.7 728l-13.6 13.6-102-102c54.7-61.1 88.3-141.6 88.3-230.1C754.4 218.7 599.7 64 408.9 64S63.4 218.7 63.4 409.5 218.1 755 408.9 755c88.5 0 168.9-33.6 230.1-88.3l102 102-13.6 13.6 177.1 177.1 54.3-54.3L781.7 728z m-680-318.6c0-169.3 137.8-307.1 307.1-307.1s307.1 137.8 307.1 307.1-137.8 307.1-307.1 307.1c-169.3 0.1-307.1-137.7-307.1-307.1z"></path> | |||||
</svg> | |||||
); | |||||
export const IconFangdajing = (props: Partial<CustomIconComponentProps>) => ( | |||||
<Icon component={SVGFangdajing} {...props} /> | |||||
); |
@@ -0,0 +1,22 @@ | |||||
import Icon from "@ant-design/icons"; | |||||
import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon"; | |||||
const SVGJiaretaiyang = () => ( | |||||
<svg | |||||
style={{ | |||||
width: "1em", | |||||
height: "1em", | |||||
fill: "currentcolor", | |||||
overflow: "hidden", | |||||
}} | |||||
viewBox="0 0 1024 1024" | |||||
version="1.1" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
> | |||||
<path d="M265.7 512c0-19.1-16.5-31.9-31.9-31.9H95.7c-19.1 0-31.9 16.5-31.9 31.9 0 15.4 12.8 31.9 31.9 31.9h138.1c16.4 0 31.9-15.5 31.9-31.9zM744.2 311.7c9.4 0 17.1-3.7 22.1-10.8l91.3-91.3c6.1-5 9.6-12 10.1-19.6 0.5-8.5-2.9-16.8-9.5-23.6-5.4-6.5-12.9-10.1-21.1-10.1-8.1 0-16 3.5-22.4 9.9L722.8 260c-6 5-9.6 11.9-10 19.6-0.5 8.6 3 17.1 9.8 23.9 5.3 5.2 13.1 8.2 21.6 8.2zM512 265.7c16.4 0 31.9-15.5 31.9-31.9V95.7c0-19.1-16.5-31.9-31.9-31.9-15.4 0-31.9 12.8-31.9 31.9v138.1c0 19.1 16.5 31.9 31.9 31.9zM166.4 209.6l91.8 93.8c5.3 5.3 13.2 8.3 21.6 8.3 9.3 0 16.8-3.6 21.9-10.5 5.7-5 9.1-11.7 9.5-19.1 0.5-8.6-3-17.1-9.8-23.9l-91.8-91.8c-5.4-6.5-12.9-10.1-21.1-10.1-7.9 0-15.8 3.4-22 9.5-6.4 5.3-10.1 13.3-10.1 21.9 0 8.6 3.6 16.6 10 21.9zM280.6 714.8c-8.1 0-16 3.5-22.3 9.8l-91.8 91.8c-6.1 5-9.6 12-10.1 19.6-0.5 8.6 3 17.1 9.8 23.9 5.3 5.3 13.2 8.3 21.6 8.3 9.4 0 17-3.7 22.1-10.8l91.3-89.3c6.1-5 9.7-12 10.1-19.6 0.5-8.5-2.9-16.8-9.5-23.6-5.5-6.5-13-10.1-21.2-10.1zM928.3 480.1H790.2c-19.1 0-31.9 16.5-31.9 31.9 0 19.1 16.5 31.9 31.9 31.9h138.1c19.1 0 31.9-16.5 31.9-31.9 0-19.1-16.5-31.9-31.9-31.9zM766 724.9c-5.4-6.5-12.9-10.1-21.1-10.1-7.9 0-15.8 3.4-22 9.5-6.1 5-9.7 12-10.1 19.6-0.5 8.6 3 17.1 9.8 23.9l92.1 92.1c5.3 5.3 13.1 8.3 21.6 8.3 8.2 0 16-2.9 21.3-8 6.1-5 9.7-12 10.1-19.6 0.5-8.6-3-17.1-9.8-23.9L766 724.9zM664.4 359.6c-41-41-95.1-63.6-152.4-63.6-57.3 0-111.4 22.6-152.4 63.6S296 454.8 296 512c0 57.3 22.6 111.4 63.6 152.4S454.7 728 512 728c57.3 0 111.4-22.6 152.4-63.6S728 569.3 728 512c0-57.3-22.6-111.4-63.6-152.4zM512 357.8c85 0 154.2 69.2 154.2 154.2 0 85-69.2 154.2-154.2 154.2S357.8 597 357.8 512c0-85 69.2-154.2 154.2-154.2zM512 758.3c-19.1 0-31.9 16.5-31.9 31.9v138.1c0 19.1 16.5 31.9 31.9 31.9 15.4 0 31.9-12.8 31.9-31.9V790.2c0-19.1-16.5-31.9-31.9-31.9z"></path> | |||||
</svg> | |||||
); | |||||
export const IconJiaretaiyang = (props: Partial<CustomIconComponentProps>) => ( | |||||
<Icon component={SVGJiaretaiyang} {...props} /> | |||||
); |
@@ -0,0 +1,22 @@ | |||||
import Icon from "@ant-design/icons"; | |||||
import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon"; | |||||
const SVGMoon = () => ( | |||||
<svg | |||||
style={{ | |||||
width: "1em", | |||||
height: "1em", | |||||
fill: "currentcolor", | |||||
overflow: "hidden", | |||||
}} | |||||
viewBox="0 0 1024 1024" | |||||
version="1.1" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
> | |||||
<path d="M572.4 229.9c8.8-8.8 21.9-11.7 33.6-7.5 115.1 41.7 197.5 152 197.5 281.6 0 165.4-134.1 299.5-299.5 299.5-129.6 0-239.9-82.3-281.6-197.5-4.2-11.7-1.3-24.8 7.5-33.6s21.9-11.7 33.6-7.5c25 9 51.9 14 80.1 14 129.9 0 235.3-105.3 235.3-235.3 0-28.2-4.9-55.2-14-80.1-4.2-11.7-1.3-24.8 7.5-33.6z m69.1 83.2c1 10 1.5 20.2 1.5 30.5C643 509 508.9 643 343.6 643c-10.3 0-20.5-0.5-30.5-1.5 42.7 59.3 112.4 97.8 191 97.8C634 739.3 739.3 634 739.3 504c0-78.5-38.5-148.2-97.8-190.9z"></path> | |||||
</svg> | |||||
); | |||||
export const IconMoon = (props: Partial<CustomIconComponentProps>) => ( | |||||
<Icon component={SVGMoon} {...props} /> | |||||
); |
@@ -0,0 +1,22 @@ | |||||
import Icon from "@ant-design/icons"; | |||||
import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon"; | |||||
const SVGShuyi_fanyi36 = () => ( | |||||
<svg | |||||
style={{ | |||||
width: "1em", | |||||
height: "1em", | |||||
fill: "currentcolor", | |||||
overflow: "hidden", | |||||
}} | |||||
viewBox="0 0 1024 1024" | |||||
version="1.1" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
> | |||||
<path d="M938.666667 981.333333c-17.066667 0-29.866667-8.533333-38.4-25.6l-59.733334-119.466666h-277.333333l-59.733333 119.466666c-8.533333 21.333333-34.133333 29.866667-55.466667 17.066667-25.6-8.533333-34.133333-34.133333-21.333333-51.2l72.533333-140.8 145.066667-290.133333c12.8-21.333333 34.133333-38.4 59.733333-38.4s46.933333 12.8 59.733333 38.4l145.066667 290.133333 72.533333 140.8c8.533333 21.333333 0 46.933333-17.066666 55.466667-12.8 4.266667-17.066667 4.266667-25.6 4.266666z m-332.8-226.133333h192l-98.133334-192-93.866666 192zM85.333333 844.8c-17.066667 0-29.866667-8.533333-38.4-25.6-8.533333-21.333333 0-46.933333 21.333334-55.466667 93.866667-46.933333 179.2-110.933333 247.466666-187.733333-46.933333-64-85.333333-128-110.933333-192-8.533333-21.333333 4.266667-46.933333 25.6-55.466667 21.333333-8.533333 46.933333 4.266667 55.466667 25.6 21.333333 51.2 46.933333 102.4 81.066666 149.333334 59.733333-85.333333 102.4-179.2 128-281.6H85.333333c-25.6 0-42.666667-17.066667-42.666666-42.666667s17.066667-42.666667 42.666666-42.666667h243.2V85.333333c0-25.6 17.066667-42.666667 42.666667-42.666666s42.666667 17.066667 42.666667 42.666666v51.2h238.933333c25.6 0 42.666667 17.066667 42.666667 42.666667s-17.066667 42.666667-42.666667 42.666667h-68.266667c-25.6 128-85.333333 247.466667-162.133333 349.866666l25.6 25.6c17.066667 17.066667 17.066667 42.666667 0 59.733334-17.066667 17.066667-42.666667 17.066667-59.733333 0l-17.066667-17.066667c-72.533333 81.066667-162.133333 149.333333-264.533333 200.533333-8.533333 0-17.066667 4.266667-21.333334 4.266667z"></path> | |||||
</svg> | |||||
); | |||||
export const IconShuyi_fanyi36 = (props: Partial<CustomIconComponentProps>) => ( | |||||
<Icon component={SVGShuyi_fanyi36} {...props} /> | |||||
); |
@@ -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", | |||||
}; |
@@ -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: "邮箱格式不正确", | |||||
}; |
@@ -1 +0,0 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg> |
@@ -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; | |||||
} |
@@ -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<HTMLDivElement> { | |||||
'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<TabsProps & { onItemsChange?: (items: any[]) => 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 ( | |||||
<Tabs | |||||
renderTabBar={(tabBarProps, DefaultTabBar) => ( | |||||
<DndContext sensors={[sensor]} onDragEnd={onDragEnd} modifiers={[restrictToHorizontalAxis]}> | |||||
<SortableContext items={items.map((i) => i.key)} strategy={horizontalListSortingStrategy}> | |||||
<DefaultTabBar {...tabBarProps}> | |||||
{(node) => ( | |||||
<DraggableTabNode {...node.props} key={node.key}> | |||||
{node} | |||||
</DraggableTabNode> | |||||
)} | |||||
</DefaultTabBar> | |||||
</SortableContext> | |||||
</DndContext> | |||||
)} | |||||
{...props} | |||||
items={items} | |||||
className='tab-layout' | |||||
/> | |||||
); | |||||
}; | |||||
export default DraggableTab; |
@@ -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; | |||||
} | |||||
} |
@@ -0,0 +1,13 @@ | |||||
import React from "react"; | |||||
import './index.css' | |||||
const GloablLoading: React.FC<any> = () => ( | |||||
<> | |||||
<div className='w-[100vw] h-[100vh] flex justify-center items-center'> | |||||
<div className="loading transform translate-y-[-12vh]"></div> | |||||
</div> | |||||
</> | |||||
) | |||||
export default GloablLoading; |
@@ -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 ( | |||||
<div className='flex justify-center'> | |||||
<Spin /> | |||||
</div> | |||||
); | |||||
} |
@@ -0,0 +1,14 @@ | |||||
export const defaultSetting = { | |||||
slideWidth: 280, | |||||
languages: [ | |||||
{ | |||||
key: 'zh', | |||||
name: 'wrQwwbSV', | |||||
}, | |||||
{ | |||||
key: 'en', | |||||
name: 'hGtEfNnp', | |||||
}, | |||||
], | |||||
defaultLang: 'zh', | |||||
} |
@@ -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<MatchRouteType | undefined>(); | |||||
// 监听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; | |||||
} |
@@ -0,0 +1,6 @@ | |||||
import { useMedia } from 'react-use'; | |||||
export const usePCScreen = () => { | |||||
const isPC = useMedia('(min-width: 1024px)'); | |||||
return isPC; | |||||
} |
@@ -0,0 +1,77 @@ | |||||
import { useCallback, useEffect, useRef, useState } from 'react'; | |||||
import { Response } from '@/request'; | |||||
interface RequestOptions { | |||||
manual?: boolean; | |||||
defaultParams?: any[]; | |||||
} | |||||
interface RequestResponse<T> { | |||||
error: boolean | undefined; | |||||
data: T | undefined; | |||||
loading: boolean; | |||||
run(...params: any): void; | |||||
runAsync(...params: any): Response<T>; | |||||
refresh(): void; | |||||
} | |||||
export function useRequest<T>( | |||||
serviceMethod: (...args: any) => Response<T>, | |||||
options?: RequestOptions | |||||
): RequestResponse<T> { | |||||
const [loading, setLoading] = useState<boolean>(false); | |||||
const [data, setData] = useState<T>(); | |||||
const [error, setError] = useState<boolean>(); | |||||
const paramsRef = useRef<any[]>([]); | |||||
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, | |||||
}; | |||||
} |
@@ -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<KeepAliveTab[]>([]); | |||||
// 当前激活的tab | |||||
const [activeTabRoutePath, setActiveTabRoutePath] = useState<string>(''); | |||||
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, | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
import { Button, Result } from 'antd'; | |||||
import { Link } from 'react-router-dom'; | |||||
const Result404 = () => ( | |||||
<Result | |||||
status="404" | |||||
title="404" | |||||
subTitle="对不起,你访问的页面不存在。" | |||||
extra={( | |||||
<Button type="primary"> | |||||
<Link to="/">首页</Link> | |||||
</Button> | |||||
)} | |||||
/> | |||||
); | |||||
export default Result404; |
@@ -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<any> = ({ children }) => { | |||||
const isPC = usePCScreen(); | |||||
const { | |||||
collapsed, | |||||
} = useGlobalStore(); | |||||
return ( | |||||
<div | |||||
className='color-transition mt-[80px] w-[100%] bg-container !<lg:ml-[16px]' | |||||
style={{ | |||||
borderRadius: '8px', | |||||
marginLeft: collapsed ? 112 : defaultSetting.slideWidth, | |||||
minHeight: 'calc(100vh - 80px)', | |||||
transition: "all 200ms cubic-bezier(0.4, 0, 0.6, 1) 0ms", | |||||
width: `calc(100vw - ${isPC ? collapsed ? 112 : defaultSetting.slideWidth : 32}px)` | |||||
}} | |||||
> | |||||
<div | |||||
className='m-0 rounded-md z-1 p-[0px]' | |||||
> | |||||
<Suspense | |||||
fallback={( | |||||
<Loading /> | |||||
)} | |||||
> | |||||
{children} | |||||
</Suspense> | |||||
</div> | |||||
</div> | |||||
) | |||||
} | |||||
export default Content; |
@@ -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 ( | |||||
<div | |||||
style={{ zIndex: 998 }} | |||||
className="color-transition h-[80px] flex basis-[48px] items-center px-0 gap-[16px] fixed top-0 right-0 left-0 bg-primary" | |||||
> | |||||
<div style={{ width: defaultSetting.slideWidth }} className="<lg:hidden flex justify-between items-center"> | |||||
<div className='flex items-center gap-[4px] text-[20px] px-[24px] pr-0'> | |||||
<IconBuguang className="text-blue-500" /> | |||||
<h1 className='text-primary font-bold text-[22px]'>VOGOCM-ERP</h1> | |||||
</div> | |||||
<div | |||||
className='btn-icon' | |||||
onClick={() => { | |||||
setCollapsed(!collapsed); | |||||
}} | |||||
> | |||||
<MenuOutlined /> | |||||
</div> | |||||
</div> | |||||
<div className='flex items-center justify-between flex-1 pr-[24px]'> | |||||
<Input | |||||
style={{ | |||||
borderRadius: 8, | |||||
outline: 'none', | |||||
boxShadow: 'none' | |||||
}} | |||||
className='w-[400px] h-[50px] focus:(border-[rgb(135,94,196)]) <lg:hidden' | |||||
size="large" | |||||
prefix={ | |||||
<IconFangdajing | |||||
style={{ | |||||
color: '#697586', | |||||
paddingRight: 8, | |||||
}} | |||||
/> | |||||
} | |||||
placeholder={t("jhqxJPbn" /* 搜索菜单 */)} | |||||
allowClear | |||||
/> | |||||
<div className='pl-[20px] lg:hidden'> | |||||
<div | |||||
className='btn-icon' | |||||
onClick={() => { | |||||
setCollapsed(!collapsed); | |||||
}} | |||||
> | |||||
<MenuOutlined /> | |||||
</div> | |||||
</div> | |||||
<div className='flex gap-[16px] items-center'> | |||||
<div onClick={() => { setDarkMode(!darkMode) }} className='btn-icon text-[20px]'> | |||||
{darkMode ? ( | |||||
<IconJiaretaiyang /> | |||||
) : ( | |||||
<Icon3 /> | |||||
)} | |||||
</div> | |||||
<Dropdown | |||||
menu={{ | |||||
items: defaultSetting.languages.map(language => ({ | |||||
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]' | |||||
> | |||||
<div className='btn-icon text-[20px] bg-[rgb(227,242,253)] dark:text-[rgb(30,136,229)] text-[rgb(30,136,229)] hover:(bg-[rgb(33,150,243)] dark:text-[rgb(227,242,253)] text-[rgb(227,242,253)])'> | |||||
{lang === 'zh' ? ( | |||||
<IconShuyi_fanyi36 /> | |||||
) : ( | |||||
<span className='text-[14px]'> | |||||
{lang.toUpperCase()} | |||||
</span> | |||||
)} | |||||
</div> | |||||
</Dropdown> | |||||
<div className='btn-icon'> | |||||
<BellOutlined /> | |||||
</div> | |||||
<Dropdown | |||||
trigger={['click']} | |||||
placement="bottomLeft" | |||||
getPopupContainer={node => node.parentElement!} | |||||
dropdownRender={() => { | |||||
return ( | |||||
<div | |||||
style={{ | |||||
boxShadow: darkMode ? | |||||
'rgba(0, 0, 0, 0.2) 0px 8px 10px -5px, rgba(0, 0, 0, 0.14) 0px 16px 24px 2px, rgba(0, 0, 0, 0.12) 0px 6px 30px 5px' | |||||
: 'rgba(0, 0, 0, 0.08) 0px 6px 30px', | |||||
}} | |||||
className='dark:bg-[rgb(33,41,70)] bg-white rounded-lg w-[200px]' | |||||
> | |||||
<div className='p-[16px]'> | |||||
<p className='text-[16px] dark:text-[rgb(237,242,247)] text-[rgb(17,25,39)] '> | |||||
{currentUser?.nickName} | |||||
</p> | |||||
<p className='text-[rgb(108,115,127)] dark:text-[rgb(160,174,192)] mt-[10px]'> | |||||
{currentUser?.phoneNumber} | |||||
</p> | |||||
<p className='text-[rgb(108,115,127)] dark:text-[rgb(160,174,192)] mt-[0px]'> | |||||
{currentUser?.email} | |||||
</p> | |||||
</div> | |||||
<hr style={{ borderWidth: '0 0 thin' }} className='m-[0] border-solid dark:border-[rgb(45,55,72)] border-[rgb(242,244,247)]' /> | |||||
<div className='p-[16px] text-center'> | |||||
<Button onClick={logout} type='text' size='small'>退出登录</Button> | |||||
</div> | |||||
</div> | |||||
) | |||||
}} | |||||
> | |||||
<div className='btn-icon rounded-[27px] pl-[10px] pr-[14px] justify-between h-[48px] w-[92px] text-[20px] bg-[rgb(227,242,253)] text-[rgb(30,136,229)] hover:(bg-[rgb(33,150,243)] text-[rgb(227,242,253)])'> | |||||
{currentUser?.avatarPath ? ( | |||||
<Avatar style={{ verticalAlign: 'middle' }} src={currentUser.avatarPath} /> | |||||
) : ( | |||||
<Avatar style={{ backgroundColor: 'gold', verticalAlign: 'middle' }} icon={<IconBuguang />} /> | |||||
)} | |||||
<SettingOutlined /> | |||||
</div> | |||||
</Dropdown> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
) | |||||
} | |||||
export default memo(Header); |
@@ -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; | |||||
} |
@@ -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<string, Menu[]>, | |||||
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<Record<string, Menu[]>>((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 ( | |||||
<GloablLoading /> | |||||
) | |||||
} | |||||
return ( | |||||
<div> | |||||
<div key={lang} className='bg-primary overflow-hidden'> | |||||
<Header /> | |||||
<Slide /> | |||||
<Content> | |||||
<TabsLayout /> | |||||
</Content> | |||||
</div> | |||||
</div> | |||||
); | |||||
}; | |||||
export default BasicLayout; |
@@ -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 ( | |||||
<SlideMenu /> | |||||
) | |||||
} | |||||
if (!isPC) { | |||||
return ( | |||||
<Drawer | |||||
open={!collapsed} | |||||
footer={null} | |||||
placement="left" | |||||
width={defaultSetting.slideWidth} | |||||
className="bg-primary" | |||||
zIndex={10001} | |||||
closable={false} | |||||
title={( | |||||
<div | |||||
className='flex items-center gap-[4px] text-[20px] justify-center' | |||||
style={{ width: defaultSetting.slideWidth }} | |||||
> | |||||
<IconBuguang className="text-blue-500" /> | |||||
<h1 className='text-primary font-bold text-[22px]'>fluxy-admin</h1> | |||||
</div> | |||||
)} | |||||
headerStyle={{ padding: '24px 0', border: 'none' }} | |||||
bodyStyle={{ padding: '0 16px' }} | |||||
onClose={() => { | |||||
setCollapsed(true); | |||||
}} | |||||
> | |||||
{renderMenu()} | |||||
</Drawer> | |||||
) | |||||
} | |||||
return ( | |||||
<div | |||||
style={{ width: collapsed ? 112 : defaultSetting.slideWidth }} | |||||
className="menu-slide color-transition top-[80px] fixed box-border left-0 bottom-0 overflow-y-auto px-[16px] bg-primary <lg:hidden" | |||||
> | |||||
{renderMenu()} | |||||
</div> | |||||
) | |||||
} | |||||
export default memo(SlideIndex); |
@@ -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<MenuProps>['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', <HomeOutlined />), | |||||
getItem('定制选品', 'custom-made', <BgColorsOutlined />, [ | |||||
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', <HighlightOutlined />, [ | |||||
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', <AppstoreOutlined />, [ | |||||
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', <ShopOutlined />, [ | |||||
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', <InboxOutlined />, [ | |||||
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', <DeploymentUnitOutlined />, [ | |||||
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', <GlobalOutlined />, [ | |||||
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', <CustomerServiceOutlined />, [ | |||||
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', <AccountBookOutlined />, [ | |||||
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', <BarChartOutlined />, [ | |||||
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', <SettingOutlined />, [ | |||||
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<string[]>([]); | |||||
const [selectKeys, setSelectKeys] = useState<string[]>([]); | |||||
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 ( | |||||
<Link to={menu.path}>{menu.name}</Link> | |||||
); | |||||
} | |||||
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 ( | |||||
<Menu | |||||
className='bg-primary color-transition' | |||||
mode="inline" | |||||
selectedKeys={selectKeys} | |||||
style={{ height: '100%', borderRight: 0 }} | |||||
items={items} | |||||
inlineCollapsed={collapsed} | |||||
openKeys={openKeys} | |||||
onOpenChange={setOpenKeys} | |||||
/> | |||||
) | |||||
} | |||||
export default SlideMenu; |
@@ -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<KeepAliveTabContextType>(defaultValue); |
@@ -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 ( | |||||
<Dropdown | |||||
menu={{ items: menuItems, onClick: (e) => menuClick(e, tab) }} | |||||
trigger={['contextMenu']} | |||||
> | |||||
<div style={{ margin: '-12px 0', padding: '12px 0' }}> | |||||
{getIcon(tab.icon)} | |||||
{tab.title} | |||||
</div> | |||||
</Dropdown> | |||||
) | |||||
}, [menuItems]); | |||||
const tabItems = useMemo(() => { | |||||
return tabs.map(tab => { | |||||
return { | |||||
key: tab.routePath, | |||||
label: renderTabTitle(tab), | |||||
children: ( | |||||
<div | |||||
key={tab.key} | |||||
className='px-[16px]' | |||||
> | |||||
{tab.children} | |||||
</div> | |||||
), | |||||
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 ( | |||||
<KeepAliveTabContext.Provider value={keepAliveContextValue}> | |||||
<DraggableTab | |||||
activeKey={activeTabRoutePath} | |||||
items={tabItems} | |||||
type="editable-card" | |||||
onChange={onTabsChange} | |||||
hideAdd | |||||
onEdit={onTabEdit} | |||||
size="small" | |||||
/> | |||||
</KeepAliveTabContext.Provider> | |||||
) | |||||
} | |||||
export default TabsLayout; |
@@ -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( | |||||
<React.StrictMode> | |||||
<App /> | |||||
</React.StrictMode>, | |||||
NProgress.configure({ | |||||
minimum: 0.3, | |||||
easing: 'ease', | |||||
speed: 800, | |||||
showSpinner: false, | |||||
parent: '#root' | |||||
}); | |||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( | |||||
<App /> | |||||
) | ) |
@@ -0,0 +1,6 @@ | |||||
export * from './user.ts' | |||||
export interface PageData<T> { | |||||
data: T[]; | |||||
total: number; | |||||
} |
@@ -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<RoleDTO>; | |||||
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<PopMenu>; | |||||
} | |||||
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[]; | |||||
} |
@@ -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; | |||||
} |
@@ -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 ( | |||||
<div className="bg-primary light:bg-[rgb(238,242,246)] bg-[rgb(238,242,246)] flex justify-center items-center h-[100vh]"> | |||||
<div className='flex-[2.5] flex justify-center'> | |||||
<div className='dark:bg-[rgb(33,41,70)] w-[400px] px-[32px] py-[20px] mt-[-12%] bg-white rounded-lg <lg:(w-[94%] mx-auto)'> | |||||
<div className='text-center'> | |||||
<div className='flex justify-center gap-2'> | |||||
<IconBuguang className='text-[20px] text-blue-500' /> | |||||
<h1 className='dark:(text-white) ' style={{ marginBottom: '0.2em' }}> 旺嘉-ERP Admin</h1> | |||||
</div> | |||||
<h3 className='dark:(text-white) text-[rgba(0,0,0,.45)] mb-[1em] text-[14px] font-normal'> | |||||
{t("wbTMzvDM" /* 一个高颜值后台管理系统 */)} | |||||
</h3> | |||||
</div> | |||||
<Form | |||||
name="super-admin" | |||||
className="login-form" | |||||
initialValues={{ userName: 'admin', password: '1' }} | |||||
onFinish={onFinish} | |||||
size="large" | |||||
> | |||||
<Form.Item | |||||
name="userName" | |||||
rules={[{ required: true, message: t("wVzXBuYs" /* 请输入账号 */) }]} | |||||
> | |||||
<Input | |||||
prefix={<UserOutlined className="site-form-item-icon" />} | |||||
placeholder={t("RNISycbR" /* 账号 */)} | |||||
size="large" | |||||
/> | |||||
</Form.Item> | |||||
<Form.Item | |||||
name="password" | |||||
rules={[{ required: true, message: t("DjMcEMAe" /* 请输入密码 */) }]} | |||||
> | |||||
<Input | |||||
prefix={<LockOutlined className="site-form-item-icon" />} | |||||
type="password" | |||||
placeholder={t("HplkKxdY" /* 密码 */)} | |||||
/> | |||||
</Form.Item> | |||||
<Form.Item noStyle style={{ marginBottom: 0 }} > | |||||
<div | |||||
className='text-right mb-[18px]' | |||||
> | |||||
<a | |||||
onClick={() => { | |||||
}} | |||||
className='text-[16px] !text-[rgb(124,77,255)] select-none' | |||||
type='link' | |||||
> | |||||
忘记密码? | |||||
</a> | |||||
</div> | |||||
</Form.Item> | |||||
<Form.Item style={{ marginBottom: 18 }}> | |||||
<Button | |||||
type="primary" | |||||
loading={false} | |||||
block | |||||
htmlType='submit' | |||||
> | |||||
{t("dDdqAAve" /* 登录 */)} | |||||
</Button> | |||||
</Form.Item> | |||||
</Form> | |||||
</div> | |||||
</div> | |||||
<div | |||||
className='flex-[1.7] dark:bg-[rgb(33,41,70)] bg-white h-[100vh] relative <lg:hidden' | |||||
style={{ | |||||
backgroundImage: 'url(/images/login-right-bg.svg)' | |||||
}} | |||||
> | |||||
<div | |||||
className='img1 w-[243px] h-[210px] bg-center absolute top-[23%] left-[37%]' | |||||
style={{ | |||||
backgroundSize: 380, | |||||
backgroundImage: 'url(/images/login-right-before.svg)' | |||||
}} | |||||
/> | |||||
<div | |||||
className='img2 w-[313px] h-[280px] bg-center absolute top-[32%] left-[40%]' | |||||
style={{ | |||||
backgroundSize: 380, | |||||
backgroundImage: 'url(/images/login-right-after.svg)' | |||||
}} | |||||
/> | |||||
<div className='absolute left-[100px] right-[100px] bottom-[50px] h-[200px]'> | |||||
<Carousel autoplay dots={{ className: 'custom' }}> | |||||
<div> | |||||
<div className='h-[160px] bg-transparent flex items-center justify-center'> | |||||
<div> | |||||
<h3 className='dark:text-[rgb(215,220,236)] text-[rgb(18,25,38)] text-[34px]'> | |||||
fluxy-admin | |||||
</h3> | |||||
<div className='dark:text-[rgb(132,146,196)] text-[rgb(105,117,134)] text-[12px] my-[20px] '> | |||||
一个高颜值后台管理系统 | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div> | |||||
<div className='h-[160px] bg-transparent flex items-center justify-center'> | |||||
<div> | |||||
<h3 className='dark:text-[rgb(215,220,236)] text-[rgb(18,25,38)] text-[34px]'> | |||||
fluxy-admin | |||||
</h3> | |||||
<div className='dark:text-[rgb(132,146,196)] text-[rgb(105,117,134)] text-[12px] my-[20px]'> | |||||
一个高颜值后台管理系统 | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div> | |||||
<div className='h-[160px] bg-transparent flex items-center justify-center'> | |||||
<div> | |||||
<h3 className='dark:text-[rgb(215,220,236)] text-[rgb(18,25,38)] text-[34px]'> | |||||
fluxy-admin | |||||
</h3> | |||||
<div className='dark:text-[rgb(132,146,196)] text-[rgb(105,117,134)] text-[12px] my-[20px] '> | |||||
一个高颜值后台管理系统 | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</Carousel> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
); | |||||
}; | |||||
export default Login; |
@@ -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<T> = Promise<[boolean, T, AxiosResponse<T>]>; | |||||
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<unknown, unknown>) => | |||||
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<any> { | |||||
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<any, any> | |||||
): Promise<any> { | |||||
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<any> { | |||||
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<T, D = any>(config: AxiosRequestConfig<D>): Response<T> { | |||||
return this.axiosInstance(config); | |||||
} | |||||
get<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Response<T> { | |||||
return this.axiosInstance.get(url, config); | |||||
} | |||||
post<T, D = any>( | |||||
url: string, | |||||
data?: D, | |||||
config?: AxiosRequestConfig<D> | |||||
): Response<T> { | |||||
return this.axiosInstance.post(url, data, config); | |||||
} | |||||
put<T, D = any>( | |||||
url: string, | |||||
data?: D, | |||||
config?: AxiosRequestConfig<D> | |||||
): Response<T> { | |||||
return this.axiosInstance.put(url, data, config); | |||||
} | |||||
delete<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Response<T> { | |||||
return this.axiosInstance.delete(url, config); | |||||
} | |||||
} | |||||
const request = new Request({timeout: 60 * 1000 * 5, baseURL: 'https://test.vogocm.com:9697'}); | |||||
export default request; |
@@ -0,0 +1,15 @@ | |||||
import request from '@/request'; | |||||
import { LoginDTO, LoginRespDTO } from '@/models' | |||||
const loginService = { | |||||
// 登录 | |||||
login: (loginDTO: LoginDTO) => { | |||||
return request.post<LoginRespDTO>('/api/login', loginDTO); | |||||
}, | |||||
logout: () => { | |||||
return request.get<LoginRespDTO>('/api/logout'); | |||||
} | |||||
}; | |||||
export default loginService; |
@@ -0,0 +1,5 @@ | |||||
const userService = { | |||||
}; | |||||
export default userService; |
@@ -0,0 +1,8 @@ | |||||
import { useRouteError } from 'react-router-dom'; | |||||
const RouterErrorElement = () => { | |||||
const error = useRouteError(); | |||||
throw error; | |||||
} | |||||
export default RouterErrorElement; |
@@ -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: ( | |||||
<Navigate to="/dashboard" /> | |||||
), | |||||
}, | |||||
{ | |||||
path: '*', | |||||
Component: BasicLayout, | |||||
children: [], | |||||
errorElement: <RouterErrorElement /> | |||||
}, | |||||
] | |||||
); | |||||
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 ( | |||||
<RouterProvider router={router} /> | |||||
) | |||||
}; | |||||
export default Router; | |||||
@@ -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<State & Action>()( | |||||
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' } | |||||
) | |||||
) |
@@ -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<State & Action>()( | |||||
devtools( | |||||
(set) => { | |||||
return { | |||||
currentUser: null, | |||||
setCurrentUser: (currentUser: State['currentUser']) => | |||||
set({currentUser}), | |||||
}; | |||||
}, | |||||
{name: 'globalUserStore'} | |||||
) | |||||
); |
@@ -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<ModalStaticFunctions, 'warn'>; | |||||
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(); |
@@ -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 }; |
@@ -0,0 +1,12 @@ | |||||
export function getParamsBySearchParams<T = any>(query: URLSearchParams) { | |||||
const params = [...query.keys()].reduce<Record<string, any>>( | |||||
(prev, cur: string) => { | |||||
if (cur) { | |||||
prev[cur] = query.get(cur); | |||||
} | |||||
return prev; | |||||
}, | |||||
{} | |||||
); | |||||
return params as T; | |||||
} |
@@ -18,7 +18,11 @@ | |||||
"strict": true, | "strict": true, | ||||
"noUnusedLocals": true, | "noUnusedLocals": true, | ||||
"noUnusedParameters": true, | "noUnusedParameters": true, | ||||
"noFallthroughCasesInSwitch": true | |||||
"noFallthroughCasesInSwitch": true, | |||||
"baseUrl": "./", | |||||
"paths": { | |||||
"@/*": ["./src/*"] | |||||
} | |||||
}, | }, | ||||
"include": ["src"], | "include": ["src"], | ||||
"references": [{ "path": "./tsconfig.node.json" }] | "references": [{ "path": "./tsconfig.node.json" }] | ||||