├── src
├── index.css
├── env.d.ts
├── components
│ ├── stateful
│ │ ├── index.tsx
│ │ └── ErrorBoundary
│ │ │ ├── index.module.less
│ │ │ └── index.tsx
│ └── stateless
│ │ ├── FixLayout
│ │ └── index.tsx
│ │ ├── AlignCenter
│ │ └── index.tsx
│ │ ├── FixTabPanel
│ │ └── index.tsx
│ │ ├── Exception
│ │ ├── exception500.tsx
│ │ ├── exception404.tsx
│ │ ├── exception401.tsx
│ │ └── exception403.tsx
│ │ ├── ContainerLoading
│ │ ├── index.md
│ │ └── index.tsx
│ │ ├── Loading
│ │ └── index.tsx
│ │ ├── ScrollToTop
│ │ └── index.tsx
│ │ ├── MultiColorBorder
│ │ ├── index.tsx
│ │ └── index.module.less
│ │ ├── NoMatch
│ │ └── index.tsx
│ │ ├── TypedText
│ │ └── index.tsx
│ │ ├── LanguageSwitcher
│ │ └── index.tsx
│ │ └── UserIP
│ │ └── index.tsx
├── utils
│ ├── token
│ │ └── index.ts
│ ├── index.ts
│ ├── sleep
│ │ └── index.ts
│ ├── style
│ │ └── index.ts
│ ├── suffix
│ │ └── index.ts
│ ├── tryCatch
│ │ ├── index.js
│ │ └── runPromise.js
│ ├── encrypt
│ │ └── index.ts
│ ├── sentry
│ │ └── index.js
│ ├── menu
│ │ └── index.ts
│ ├── publicFn
│ │ └── index.tsx
│ └── aidFn.js
├── layout
│ ├── proContent
│ │ ├── index.module.less
│ │ └── index.tsx
│ ├── proHeader
│ │ ├── components
│ │ │ ├── Breadcrumb
│ │ │ │ ├── index.module.less
│ │ │ │ ├── util.ts
│ │ │ │ └── index.tsx
│ │ │ ├── UserInfo
│ │ │ │ ├── ChangeAvatar
│ │ │ │ │ ├── index.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── index.less
│ │ │ │ ├── index.tsx
│ │ │ │ └── ChangePassword
│ │ │ │ │ └── index.tsx
│ │ │ ├── SwitchWorkspace
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── HeaderSearch
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── HeaderRight
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ └── TabsHistory
│ │ │ │ ├── index.module.less
│ │ │ │ └── inex.tsx
│ │ ├── index.module.less
│ │ └── index.tsx
│ ├── index.module.less
│ ├── proSecNav
│ │ ├── index.module.less
│ │ └── index.tsx
│ ├── index.tsx
│ ├── proSider
│ │ └── index.tsx
│ ├── fullscreen
│ │ └── index.tsx
│ └── proTabs
│ │ └── index.tsx
├── theme
│ ├── dark.ts
│ ├── light.ts
│ ├── index.ts
│ └── hooks.tsx
├── assets
│ ├── images
│ │ └── signIn
│ │ │ └── bg.jpg
│ └── svg
│ │ ├── v8-outline.svg
│ │ └── antd.svg
├── locales
│ ├── zh
│ │ └── translation.ts
│ └── en
│ │ └── translation.ts
├── pages
│ ├── signIn
│ │ ├── LoginCard
│ │ │ ├── index.tsx
│ │ │ └── index.less
│ │ └── index.tsx
│ ├── coupons
│ │ ├── home
│ │ │ └── index.jsx
│ │ ├── edit
│ │ │ └── index.jsx
│ │ ├── detail
│ │ │ └── index.jsx
│ │ ├── add
│ │ │ └── index.jsx
│ │ └── index.jsx
│ ├── demo
│ │ └── index.tsx
│ ├── error
│ │ └── index.jsx
│ ├── home
│ │ └── index.tsx
│ ├── echarts
│ │ └── index.jsx
│ └── crypto
│ │ └── index.jsx
├── store
│ ├── index.ts
│ ├── userInfoSlice
│ │ └── index.ts
│ └── tabsSlice
│ │ └── index.tsx
├── index.tsx
├── i18n
│ └── i18n.ts
├── App.tsx
├── routers
│ ├── authRouter.tsx
│ └── index.tsx
├── ThemeIndex.tsx
├── styles
│ └── reset.css
└── hooks
│ └── usePagination
│ └── index.ts
├── .env
├── .gitignore
├── prettier.config.mjs
├── uno.config.ts
├── .vscode
├── settings.json
└── launch.json
├── typings
└── i18next.d.ts
├── README.md
├── tsconfig.json
├── rsbuild.config.ts
├── .github
└── workflows
│ └── deploy.yml
├── eslint.config.mjs
└── package.json
/src/index.css:
--------------------------------------------------------------------------------
1 | @unocss preflights;
2 | @unocss default;
3 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/components/stateful/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './ErrorBoundary';
2 |
--------------------------------------------------------------------------------
/src/utils/token/index.ts:
--------------------------------------------------------------------------------
1 | const getToken = () => 'token'
2 |
3 | export default getToken
4 |
--------------------------------------------------------------------------------
/src/layout/proContent/index.module.less:
--------------------------------------------------------------------------------
1 | .layout {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | APP_BASE_URL=
2 | DEPLOYED_ENV=Dev
3 | VITE_GREETINGS=Dev
4 | AUTH_USER=cl1107
5 | AUTH_PASSWORD=cl1107
--------------------------------------------------------------------------------
/src/theme/dark.ts:
--------------------------------------------------------------------------------
1 | const darkTheme = {
2 | colorPrimary: '#1677ff',
3 | };
4 |
5 | export default darkTheme;
6 |
--------------------------------------------------------------------------------
/src/theme/light.ts:
--------------------------------------------------------------------------------
1 | const lightTheme = {
2 | colorPrimary: '#1677ff',
3 | }
4 |
5 | export default lightTheme
6 |
--------------------------------------------------------------------------------
/src/assets/images/signIn/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cl1107/react-antd-admin-pro/HEAD/src/assets/images/signIn/bg.jpg
--------------------------------------------------------------------------------
/src/locales/zh/translation.ts:
--------------------------------------------------------------------------------
1 | const zh = {
2 | demo: '演示',
3 | lang: 'ZH',
4 | home: '首页',
5 | }
6 |
7 | export default zh
8 |
--------------------------------------------------------------------------------
/src/locales/en/translation.ts:
--------------------------------------------------------------------------------
1 | const en = {
2 | demo: 'Demo',
3 | lang: 'En',
4 | home: 'Home',
5 | }
6 |
7 | export default en
8 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import tryCatch from './tryCatch'
2 | import sentryInit from './sentry'
3 |
4 | export { tryCatch, sentryInit }
5 |
--------------------------------------------------------------------------------
/src/layout/proHeader/components/Breadcrumb/index.module.less:
--------------------------------------------------------------------------------
1 | .breadcrumb {
2 | cursor: pointer;
3 | line-height: 56px !important;
4 | }
5 |
--------------------------------------------------------------------------------
/src/layout/index.module.less:
--------------------------------------------------------------------------------
1 | .layout {
2 | height: 100vh;
3 | width: 100vw;
4 | overflow: hidden;
5 | // flex-shrink: 0;
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/stateful/ErrorBoundary/index.module.less:
--------------------------------------------------------------------------------
1 | .pre {
2 | white-space: pre-wrap;
3 | word-wrap: break-word;
4 | color: #f00;
5 | }
6 |
--------------------------------------------------------------------------------
/src/layout/proHeader/components/UserInfo/ChangeAvatar/index.less:
--------------------------------------------------------------------------------
1 | .change-avatar {
2 | .cl-ant-upload-list {
3 | display: none;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/layout/proSecNav/index.module.less:
--------------------------------------------------------------------------------
1 | .menu {
2 | border-right: 0;
3 | height: calc(100% - 40px);
4 | overflow-y: auto;
5 | overflow-x: hidden;
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Local
2 | .DS_Store
3 | *.local
4 | *.log*
5 |
6 | # Dist
7 | node_modules
8 | dist/
9 |
10 | # IDE
11 | !.vscode/extensions.json
12 | .idea
13 |
--------------------------------------------------------------------------------
/src/theme/index.ts:
--------------------------------------------------------------------------------
1 | import darkTheme from './dark'
2 | import lightTheme from './light'
3 |
4 | const myThemes = { darkTheme, lightTheme }
5 | export default myThemes
6 |
--------------------------------------------------------------------------------
/src/layout/proHeader/index.module.less:
--------------------------------------------------------------------------------
1 | .header {
2 | padding: 0 12px !important;
3 | display: flex;
4 | align-items: center;
5 | justify-content: space-between;
6 | }
7 |
--------------------------------------------------------------------------------
/src/utils/sleep/index.ts:
--------------------------------------------------------------------------------
1 | const sleep = (time = 100) =>
2 | new Promise((resolve) => {
3 | setTimeout(() => {
4 | resolve(true);
5 | }, time);
6 | });
7 |
8 | export default sleep;
9 |
--------------------------------------------------------------------------------
/src/utils/style/index.ts:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import { twMerge } from 'tailwind-merge';
3 |
4 | export function cn(...inputs: clsx.ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/src/utils/suffix/index.ts:
--------------------------------------------------------------------------------
1 | const suffix = (map) => {
2 | const timestamp = Math.round(new Date().getTime())
3 | return {
4 | ...map,
5 | timestamp,
6 | }
7 | }
8 | export default suffix
9 |
--------------------------------------------------------------------------------
/prettier.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | tabWidth: 2,
3 | semi: true,
4 | trailingComma: 'all',
5 | printWidth: 100,
6 | proseWrap: 'never',
7 | endOfLine: 'lf',
8 | singleQuote: true,
9 | };
10 |
--------------------------------------------------------------------------------
/uno.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, presetUno } from 'unocss';
2 |
3 | export default defineConfig({
4 | content: {
5 | filesystem: ['./src/**/*.{html,js,ts,jsx,tsx}'],
6 | },
7 | presets: [presetUno()],
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/stateless/FixLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const FixLayout = ({ children }) =>
{children}
4 |
5 | export default FixLayout
6 |
--------------------------------------------------------------------------------
/src/utils/tryCatch/index.js:
--------------------------------------------------------------------------------
1 | const tryCatch = (tryer) => {
2 | try {
3 | const result = tryer()
4 | return [result, null]
5 | } catch (error) {
6 | return [null, error]
7 | }
8 | }
9 |
10 | export default tryCatch
11 |
--------------------------------------------------------------------------------
/src/components/stateless/AlignCenter/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AlignCenter = ({ children }: { children: React.ReactNode }) => (
4 | {children}
5 | );
6 |
7 | export default AlignCenter;
8 |
--------------------------------------------------------------------------------
/src/components/stateless/FixTabPanel/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const FixTabPanel = ({ children }: { children: React.ReactNode }) => (
4 | {children}
5 | );
6 |
7 | export default FixTabPanel;
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "javascript.suggest.paths": true,
3 | "typescript.suggest.paths": true,
4 | "editor.codeActionsOnSave": {
5 | "source.organizeImports": "explicit",
6 | "source.fixAll": "explicit"
7 | },
8 | "cSpell.words": ["partialize", "zustand"]
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/stateless/Exception/exception500.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Result } from 'antd'
3 |
4 | const Exception500 = () => (
5 | <>
6 |
7 | >
8 | )
9 |
10 | export default Exception500
11 |
--------------------------------------------------------------------------------
/typings/i18next.d.ts:
--------------------------------------------------------------------------------
1 | import 'i18next';
2 | import translation from '../src/locales/zh/translation';
3 |
4 | declare module 'i18next' {
5 | interface CustomTypeOptions {
6 | defaultNS: 'translation';
7 | resources: {
8 | translation: typeof translation;
9 | };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/stateless/Exception/exception404.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Result } from 'antd'
3 |
4 | const Exception404 = () => (
5 | <>
6 |
7 | >
8 | )
9 |
10 | export default Exception404
11 |
--------------------------------------------------------------------------------
/src/components/stateless/Exception/exception401.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Result } from 'antd'
4 |
5 | const Exception401 = () => (
6 | <>
7 |
8 | >
9 | )
10 |
11 | export default Exception401
12 |
--------------------------------------------------------------------------------
/src/pages/signIn/LoginCard/index.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from 'react';
2 | import './index.less';
3 |
4 | export const LoginCard = ({ children }: { children: ReactNode }) => {
5 | return (
6 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/src/layout/proHeader/components/UserInfo/index.less:
--------------------------------------------------------------------------------
1 | .user-info {
2 | height: 100%;
3 |
4 | .cl-ant-popover-inner-content &__card {
5 | margin: -12px -16px;
6 |
7 | .cl-ant-card-body {
8 | padding: 24px 12px 16px 12px;
9 | }
10 |
11 | .cl-ant-card-actions > li {
12 | margin: 8px 0;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.1",
3 | "configurations": [
4 | {
5 | "name": "Run Application",
6 | "type": "node",
7 | "request": "launch",
8 | "runtimeExecutable": "npm",
9 | "runtimeArgs": [
10 | "run",
11 | "dev"
12 | ],
13 | "cwd": "${workspaceFolder}"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/src/components/stateless/ContainerLoading/index.md:
--------------------------------------------------------------------------------
1 | ## 整个页面 loading
2 |
3 | 若页面父容器不是 Card 等自带 loading 的组件,在接口 loading 时可以使用该组件
4 |
5 | ### Demo:
6 |
7 | ```tsx
8 | import { useState } from 'react';
9 | import * as React from 'react';
10 | import { ContainerLoading } from 'r5-ui';
11 |
12 | export default () => {
13 | return ;
14 | };
15 | ```
16 |
--------------------------------------------------------------------------------
/src/pages/coupons/home/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Alert } from 'antd'
3 |
4 | const HomeCoupons = () => (
5 | <>
6 |
12 | >
13 | )
14 |
15 | export default HomeCoupons
16 |
--------------------------------------------------------------------------------
/src/components/stateless/Loading/index.tsx:
--------------------------------------------------------------------------------
1 | import { LoadingOutlined } from '@ant-design/icons';
2 | import { Spin } from 'antd';
3 |
4 | const antIcon = ;
5 | const Loading = () => (
6 |
7 |
8 |
9 | );
10 |
11 | export default Loading;
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rsbuild Project
2 |
3 | ## Setup
4 |
5 | Install the dependencies:
6 |
7 | ```bash
8 | pnpm install
9 | ```
10 |
11 | ## Get Started
12 |
13 | Start the dev server:
14 |
15 | ```bash
16 | pnpm dev
17 | ```
18 |
19 | Build the app for production:
20 |
21 | ```bash
22 | pnpm build
23 | ```
24 |
25 | Preview the production build locally:
26 |
27 | ```bash
28 | pnpm preview
29 | ```
30 |
--------------------------------------------------------------------------------
/src/components/stateless/ContainerLoading/index.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from 'antd';
2 | import type { CSSProperties, FC } from 'react';
3 |
4 | const ContainerLoading: FC<{ style?: CSSProperties; bordered?: boolean }> = ({
5 | style,
6 | bordered = true,
7 | }) => {
8 | return ;
9 | };
10 | export default ContainerLoading;
11 |
--------------------------------------------------------------------------------
/src/pages/demo/index.tsx:
--------------------------------------------------------------------------------
1 | import FixTabPanel from '@/components/stateless/FixTabPanel';
2 | import { Input } from 'antd';
3 |
4 | const ProDemo = () => {
5 | return (
6 |
7 |
8 | 项目文档待完善
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default ProDemo;
16 |
--------------------------------------------------------------------------------
/src/components/stateless/ScrollToTop/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import { useLocation } from 'react-router-dom'
3 |
4 | const ScrollToTop = (props) => {
5 | const { pathname } = useLocation()
6 | useEffect(() => {
7 | window.scrollTo({
8 | top: 0,
9 | left: 0,
10 | behavior: 'smooth',
11 | })
12 | }, [pathname])
13 | return props.children
14 | }
15 |
16 | export default ScrollToTop
17 |
--------------------------------------------------------------------------------
/src/pages/error/index.jsx:
--------------------------------------------------------------------------------
1 | import FixTabPanel from '@/components/stateless/FixTabPanel';
2 | import React from 'react';
3 |
4 | const MyError = () => {
5 | const error = { error: 'error' };
6 | return (
7 |
8 | Cool! Hi, React && Ant Design
9 | {error.map((item) => (
10 | {item}
11 | ))}
12 |
13 | );
14 | };
15 |
16 | export default MyError;
17 |
--------------------------------------------------------------------------------
/src/layout/proHeader/components/SwitchWorkspace/index.module.less:
--------------------------------------------------------------------------------
1 | .switch-workspace {
2 | margin: 0 12px;
3 |
4 | :global {
5 | .cl-ant-dropdown-menu-item,
6 | .cl-ant-dropdown-menu-submenu-title {
7 | padding: 5px 20px;
8 | }
9 |
10 | .cl-ant-dropdown-menu-item-selected,
11 | .cl-ant-dropdown-menu-submenu-title-selected {
12 | color: #fff;
13 | background: var(--theme-color);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/pages/home/index.tsx:
--------------------------------------------------------------------------------
1 | import FixTabPanel from '@/components/stateless/FixTabPanel';
2 | import TypedText from '@/components/stateless/TypedText';
3 | import { Input } from 'antd';
4 | import { version } from 'react';
5 |
6 | const Home = () => {
7 | return (
8 |
9 |
10 | Cool! Hi, React & Ant Design!
11 |
12 | React version: {version}
13 |
14 |
15 | );
16 | };
17 |
18 | export default Home;
19 |
--------------------------------------------------------------------------------
/src/utils/encrypt/index.ts:
--------------------------------------------------------------------------------
1 | import JsEncrypt from 'jsencrypt';
2 |
3 | /**
4 | * rsa加密密码
5 | * @param {string} password
6 | * @returns
7 | */
8 | export const encryptPassword = (password: string) => {
9 | const encrypt = new JsEncrypt({});
10 | encrypt.setPublicKey(
11 | 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRzv+ez8OvPfzncOQrS4bz2bSTRecaxjKp9fV7iukfLeU9kpOX0+ZNneahtSq5/HJWXBE4P4D5j+STlHIF7rNbue620yRLteYOpYqI3m9QBF9yH08b6yAn1nsTcRy4TeMPA6xDCYzI961E/1e89HGRZ4JlS+L1I4df3RK6lUHYjQIDAQAB',
12 | );
13 | return encrypt.encrypt(password);
14 | };
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "lib": ["DOM", "ES2020"],
5 | "module": "ESNext",
6 | "jsx": "react-jsx",
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "isolatedModules": true,
10 | "resolveJsonModule": true,
11 | "moduleResolution": "bundler",
12 | "useDefineForClassFields": true,
13 | "baseUrl": ".",
14 | "paths": {
15 | "@/*": ["src/*"]
16 | } /* Specify a set of entries that re-map imports to additional lookup locations. */
17 | },
18 | "include": ["src", "typings"]
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/stateless/MultiColorBorder/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import styles from './index.module.less'
4 |
5 | const MultiColorBorder = ({ text, wrapperStyles = { color: '#fff' }, contentStyles = { color: '#fff' } }) => (
6 | <>
7 |
17 | >
18 | )
19 |
20 | export default MultiColorBorder
21 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand';
2 | import { devtools, persist } from 'zustand/middleware';
3 | import { TabsState, createTabsSlice } from './tabsSlice';
4 | import { UserInfoState, createUserInfoSlice } from './userInfoSlice';
5 |
6 | export const useGlobalStore = create()(
7 | devtools(
8 | persist(
9 | (...a) => ({
10 | ...createUserInfoSlice(...a),
11 | ...createTabsSlice(...a),
12 | }),
13 | { name: 'userInfo', partialize: (state) => ({ userInfo: state.userInfo }) }, // 挑选需要持久化的数据
14 | ),
15 | ),
16 | );
17 |
--------------------------------------------------------------------------------
/src/components/stateless/NoMatch/index.tsx:
--------------------------------------------------------------------------------
1 | import { useLocation, useNavigate } from 'react-router-dom';
2 | import { Button } from 'antd';
3 |
4 | const NoMatch = () => {
5 | const location = useLocation();
6 | const navigate = useNavigate();
7 |
8 | return (
9 |
10 |
11 | No match for {location.pathname}
12 |
13 |
14 |
17 |
18 | );
19 | };
20 |
21 | export default NoMatch;
22 |
--------------------------------------------------------------------------------
/src/store/userInfoSlice/index.ts:
--------------------------------------------------------------------------------
1 | import { StateCreator } from 'zustand';
2 | import { TabsState } from '../tabsSlice';
3 |
4 | type UserInfo = {
5 | username: string;
6 | lastLogin: string;
7 | roleName: string;
8 | };
9 |
10 | export interface UserInfoState {
11 | userInfo: UserInfo;
12 | updateUserInfo: (userInfo: UserInfo) => void;
13 | }
14 | export const createUserInfoSlice: StateCreator = (
15 | set,
16 | ) => ({
17 | userInfo: { username: '', lastLogin: '', roleName: '' },
18 | updateUserInfo: (userInfo: UserInfo) => set({ userInfo }),
19 | });
20 |
--------------------------------------------------------------------------------
/src/components/stateless/TypedText/index.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useEffect, useState } from 'react';
2 |
3 | const TypedText = ({ children, delay = 110 }: { children: string; delay?: number }) => {
4 | const [revealedLetters, setRevealedLetters] = useState(0);
5 | const interval = setInterval(() => setRevealedLetters((l) => l + 1), delay);
6 |
7 | useEffect(() => {
8 | if (revealedLetters === children.length) clearInterval(interval);
9 | }, [children, interval, revealedLetters]);
10 |
11 | useEffect(() => () => clearInterval(interval), [interval]);
12 |
13 | return <>{children.substring(0, revealedLetters)}>;
14 | };
15 |
16 | export default memo(TypedText);
17 |
--------------------------------------------------------------------------------
/src/utils/sentry/index.js:
--------------------------------------------------------------------------------
1 | import * as Sentry from '@sentry/react'
2 | import packageJson from '../../../package.json'
3 |
4 | const sentryInit = () => {
5 | // const nodeEnv = process.env.NODE_ENV
6 | // if (nodeEnv !== 'production') return
7 | Sentry.init({
8 | dsn: 'https://39892504629549fa9c0b040d98e87d03@o64827.ingest.sentry.io/5791911',
9 | integrations: [new Sentry.BrowserTracing()],
10 | tracesSampleRate: 1.0,
11 | release: packageJson.version,
12 | // environment: nodeEnv,
13 | // autoSessionTracking: nodeEnv === 'production',
14 | })
15 |
16 | Sentry.setExtra('projectOwner', '150****5870')
17 | }
18 |
19 | export default sentryInit
20 |
--------------------------------------------------------------------------------
/src/components/stateless/Exception/exception403.tsx:
--------------------------------------------------------------------------------
1 | import FixTabPanel from '@/components/stateless/FixTabPanel';
2 | import { Button, Result } from 'antd';
3 | import { useNavigate } from 'react-router-dom';
4 |
5 | const Exception403 = () => {
6 | const navigate = useNavigate();
7 | return (
8 |
9 | navigate('/')}>
13 | 去首页
14 |
15 | }
16 | subTitle="Sorry, you are not authorized to access this page."
17 | />
18 |
19 | );
20 | };
21 |
22 | export default Exception403;
23 |
--------------------------------------------------------------------------------
/src/layout/index.tsx:
--------------------------------------------------------------------------------
1 | import { Layout, Watermark } from 'antd';
2 | import ProContent from './proContent';
3 | import ProHeader from './proHeader';
4 | import ProSecNav from './proSecNav';
5 | import ProSider from './proSider';
6 |
7 | import styles from './index.module.less';
8 |
9 | const ProLayout = () => (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | export default ProLayout;
24 |
--------------------------------------------------------------------------------
/src/pages/coupons/edit/index.jsx:
--------------------------------------------------------------------------------
1 | import FixTabPanel from '@/components/stateless/FixTabPanel';
2 | import { Alert } from 'antd';
3 | import React from 'react';
4 | import { useSearchParams } from 'react-router-dom';
5 |
6 | const EditCoupons = () => {
7 | const [searchParams] = useSearchParams();
8 | const term = searchParams.get('id');
9 | return (
10 |
11 |
17 | Search Id: {term}
18 |
19 | );
20 | };
21 |
22 | export default EditCoupons;
23 |
--------------------------------------------------------------------------------
/src/components/stateless/LanguageSwitcher/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Space } from 'antd';
2 | import { useTranslation } from 'react-i18next';
3 |
4 | const LanguageSwitcher = () => {
5 | const { i18n } = useTranslation();
6 |
7 | const handleLanguageChange = (language: string) => {
8 | i18n.changeLanguage(language);
9 | // window.location.reload()
10 | };
11 |
12 | return (
13 |
14 | {(i18n.language === 'zh-CN' || i18n.language === 'zh') && (
15 |
16 | )}
17 | {i18n.language === 'en' && }
18 |
19 | );
20 | };
21 |
22 | export default LanguageSwitcher;
23 |
--------------------------------------------------------------------------------
/src/components/stateful/ErrorBoundary/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'antd';
2 | import { ErrorBoundary } from 'react-error-boundary';
3 | import styles from './index.module.less';
4 |
5 | const ErrorFallback = ({ error, resetErrorBoundary }) => (
6 |
7 |
Something went wrong:
8 |
{error.message}
9 |
12 |
13 | );
14 |
15 | export const MyErrorBoundary = (props) => (
16 | {
19 | if (props.fixError) {
20 | props.fixError();
21 | }
22 | }}
23 | >
24 | {props.children}
25 |
26 | );
27 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { I18nextProvider } from 'react-i18next';
4 | import { BrowserRouter } from 'react-router-dom';
5 | import ThemeIndex from './ThemeIndex';
6 | import i18n from './i18n/i18n';
7 | import './index.css';
8 | import { ProThemeProvider } from './theme/hooks';
9 |
10 | const root = ReactDOM.createRoot(document.getElementById('root')!);
11 | root.render(
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 | ,
23 | );
24 |
--------------------------------------------------------------------------------
/src/pages/coupons/detail/index.jsx:
--------------------------------------------------------------------------------
1 | import FixTabPanel from '@/components/stateless/FixTabPanel';
2 | import { Alert } from 'antd';
3 | import React from 'react';
4 | import { useParams, useSearchParams } from 'react-router-dom';
5 |
6 | const DetailCoupons = () => {
7 | const [searchParams] = useSearchParams();
8 |
9 | const id = searchParams.get('id');
10 |
11 | const params = useParams();
12 |
13 | return (
14 |
15 |
21 | useParams: {JSON.stringify(params, null, 2)}
22 | Search Id: {id}
23 |
24 | );
25 | };
26 |
27 | export default DetailCoupons;
28 |
--------------------------------------------------------------------------------
/src/components/stateless/MultiColorBorder/index.module.less:
--------------------------------------------------------------------------------
1 | .multiWrapper {
2 | border-radius: 3px;
3 | box-sizing: border-box;
4 | display: block;
5 | overflow: hidden;
6 | padding: 2px;
7 | position: relative;
8 | }
9 |
10 | .multiWrapper::before {
11 | background: linear-gradient(115deg, #4fcf70, #fad648, #a767e5, #12bcfe, #44ce7b);
12 | background-size: 50% 100%;
13 | content: '';
14 | height: 100%;
15 | width: 200%;
16 | left: 0;
17 | position: absolute;
18 | top: 0;
19 | }
20 |
21 | .multiWrapper:hover::before {
22 | animation: wrapper-animation 0.75s linear infinite;
23 | }
24 |
25 | .multiWrapper .multiContent {
26 | background: #000;
27 | position: relative;
28 | border-radius: 3px;
29 | color: #fff;
30 | }
31 |
32 | @keyframes wrapper-animation {
33 | to {
34 | transform: translateX(-50%);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/i18n/i18n.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next'
2 | import { initReactI18next } from 'react-i18next'
3 | import LanguageDetector from 'i18next-browser-languagedetector'
4 | import translationInZh from '../locales/zh/translation'
5 | import translationInEn from '../locales/en/translation'
6 |
7 | i18n
8 | .use(LanguageDetector)
9 | .use(initReactI18next)
10 | .init({
11 | resources: {
12 | en: {
13 | translation: translationInEn,
14 | },
15 | zh: {
16 | translation: translationInZh,
17 | },
18 | },
19 | lng: 'zh',
20 | fallbackLng: 'en', // 默认语言
21 | debug: process.env.NODE_ENV !== 'production', // 开启调试模式
22 | interpolation: {
23 | escapeValue: false, // 不转义特殊字符
24 | },
25 | detection: {
26 | order: ['localStorage', 'navigator'],
27 | },
28 | })
29 |
30 | export default i18n
31 |
--------------------------------------------------------------------------------
/src/components/stateless/UserIP/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | // import UserAgent from 'user-agents'
3 |
4 | const UserIP = () => {
5 | const [userIp, setUserIp] = useState('')
6 | // const userAgent = new UserAgent()
7 | useEffect(() => {
8 | getUserIp()
9 | }, [])
10 |
11 | const getUserIp = () => {
12 | fetch('https://api.ipify.org?format=json')
13 | .then((response) => response.json())
14 | .then((data) => {
15 | const ipAddress = data.ip
16 | setUserIp(ipAddress ?? '0.0.0.0')
17 | })
18 | .catch(() => {
19 | setUserIp('0.0.0.0')
20 | })
21 | }
22 |
23 | return (
24 | <>
25 | 欢迎您,来自远方的朋友!
26 | 您的IP: {userIp}
27 | {/* {userAgent.toString()}
*/}
28 | >
29 | )
30 | }
31 |
32 | export default UserIP
33 |
--------------------------------------------------------------------------------
/src/theme/hooks.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useMemo, useState } from 'react';
2 |
3 | const defaultTheme = 'light';
4 | type ThemeContextType = {
5 | myTheme: string;
6 | setMyTheme: React.Dispatch>;
7 | };
8 |
9 | const ProThemeContext = createContext({} as ThemeContextType);
10 | const useProThemeContext = () => useContext(ProThemeContext);
11 |
12 | const ProThemeProvider = ({ children }: { children: React.ReactNode }) => {
13 | const [myTheme, setMyTheme] = useState(defaultTheme);
14 |
15 | const themeProvider = useMemo(
16 | () => ({
17 | myTheme,
18 | setMyTheme,
19 | }),
20 | [myTheme, setMyTheme],
21 | );
22 | return {children};
23 | };
24 |
25 | export { ProThemeProvider, useProThemeContext };
26 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { useRoutes } from 'react-router-dom';
3 | import Loading from './components/stateless/Loading';
4 | import rootRouter from './routers';
5 | import AuthRouter from './routers/authRouter';
6 |
7 | // import { sentryInit } from './utils';
8 |
9 | const App = () => {
10 | // const { i18n } = useTranslation()
11 | const [loading, setLoading] = useState(true);
12 | const asyncCall = () => new Promise((resolve) => setTimeout(() => resolve(), 500));
13 | useEffect(() => {
14 | // sentryInit();
15 | asyncCall()
16 | .then(() => setLoading(false))
17 | .catch(() => setLoading(false));
18 | }, []);
19 |
20 | const element = useRoutes(rootRouter);
21 |
22 | if (loading) {
23 | return ;
24 | }
25 |
26 | return {element};
27 | };
28 |
29 | export default App;
30 |
--------------------------------------------------------------------------------
/src/layout/proHeader/components/HeaderSearch/index.module.less:
--------------------------------------------------------------------------------
1 | // @import '~antd/es/style/themes/default.less';
2 | /* stylelint-disable selector-class-pattern */
3 | .headerSearch {
4 | // & > .cl-ant-select {
5 | // border: none;
6 | // }
7 | // & > .cl-ant-select > .cl-ant-select-selector {
8 | // border: none;
9 | // }
10 | .input {
11 | width: 0;
12 | min-width: 0;
13 | overflow: hidden;
14 | background: transparent;
15 | border-radius: 0;
16 | transition:
17 | width 0.3s,
18 | margin-left 0.3s;
19 |
20 | input {
21 | padding-right: 0;
22 | padding-left: 0;
23 | border: 0;
24 | box-shadow: none !important;
25 | }
26 | // &,
27 | // &:hover,
28 | // &:focus {
29 | // // border-bottom: 1px solid @border-color-base;
30 | // }
31 | &.show {
32 | width: 210px;
33 | margin-left: 8px;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/layout/proHeader/components/HeaderRight/index.module.less:
--------------------------------------------------------------------------------
1 | @pro-header-hover-bg: rgba(0, 0, 0, 0.025);
2 |
3 | .header {
4 | &__right {
5 | display: flex;
6 | align-items: center;
7 | justify-content: flex-end;
8 | height: 52px;
9 | transition: all 0.3s;
10 |
11 | .cl-ant-badge-count-sm {
12 | padding: 0 2px;
13 | }
14 | }
15 |
16 | &__action-icons {
17 | display: flex;
18 | align-items: center;
19 | height: 100%;
20 | }
21 |
22 | &__action-icon {
23 | margin-right: 20px;
24 |
25 | &:last-child {
26 | margin-right: 0;
27 | }
28 | }
29 | }
30 |
31 | .action {
32 | display: flex;
33 | align-items: center;
34 | height: 100%;
35 | cursor: pointer;
36 | transition: all 0.3s;
37 |
38 | > span {
39 | vertical-align: middle;
40 | }
41 |
42 | &:hover {
43 | background: transparent;
44 | }
45 |
46 | &:global(.opened) {
47 | background: @pro-header-hover-bg;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/routers/authRouter.tsx:
--------------------------------------------------------------------------------
1 | import { getKeyName, getLocalStorage } from '@/utils/publicFn';
2 | import { Navigate, useLocation } from 'react-router-dom';
3 |
4 | const AuthRouter = (props) => {
5 | const { pathname } = useLocation();
6 | const route = getKeyName(pathname);
7 |
8 | if (!route?.auth) return props.children;
9 |
10 | const { token } = getLocalStorage('token') || { token: null };
11 | if (!token) return ;
12 |
13 | // * 后端返回有权限路由列表 暂时硬编码 需要结合 proSecNav组件中的menuItems
14 | //TODO:初始化activeKey和panes,目前是写死初始化为/,home组件
15 | const routerList = [
16 | '/',
17 | '/home',
18 | '/demo',
19 | '/parallax',
20 | '/dashboard',
21 | '/tilt',
22 | '/prism',
23 | '/three',
24 | '/echarts',
25 | '/video',
26 | '/crypto',
27 | ];
28 | if (routerList.indexOf(pathname) === -1) return ;
29 |
30 | return props.children;
31 | };
32 |
33 | export default AuthRouter;
34 |
--------------------------------------------------------------------------------
/src/pages/coupons/add/index.jsx:
--------------------------------------------------------------------------------
1 | import FixTabPanel from '@/components/stateless/FixTabPanel';
2 | import { useTabsStore } from '@/store/proTabsContext';
3 | import { Alert, Button } from 'antd';
4 | import React from 'react';
5 | import { useNavigate } from 'react-router-dom';
6 |
7 | const AddCoupons = () => {
8 | const { activeKey, removeTab } = useTabsStore();
9 | const navigate = useNavigate();
10 | const closeActiveOpenAngular = () => {
11 | removeTab(activeKey, () => {
12 | navigate('coupons/edit', { replace: true });
13 | });
14 | };
15 | return (
16 |
17 |
20 |
26 |
27 | );
28 | };
29 |
30 | export default AddCoupons;
31 |
--------------------------------------------------------------------------------
/src/layout/proHeader/components/TabsHistory/index.module.less:
--------------------------------------------------------------------------------
1 | .section-layout-tabs {
2 | // height: 100%;
3 |
4 | :global {
5 | .cl-ant-tabs-nav > .cl-ant-tabs-nav-wrap > .cl-ant-tabs-nav-list > .cl-ant-tabs-tab {
6 | margin-left: 0;
7 | border-top: none;
8 | border-bottom: none;
9 | border-left: none;
10 | border-radius: 0;
11 | }
12 |
13 | .cl-ant-tabs-nav
14 | > .cl-ant-tabs-nav-wrap
15 | > .cl-ant-tabs-nav-list
16 | > .cl-ant-tabs-tab
17 | .cl-ant-tabs-tab-remove {
18 | margin-left: 0;
19 | }
20 |
21 | .cl-ant-tabs-nav {
22 | margin-bottom: 0 !important;
23 | background: var(--bg-color);
24 | // box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
25 |
26 | &::before {
27 | border: none !important;
28 | }
29 | }
30 |
31 | .cl-ant-tabs-nav .cl-ant-tabs-nav-wrap,
32 | div > .cl-ant-tabs-nav .cl-ant-tabs-nav-wrap {
33 | flex: 0 1 auto !important;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/rsbuild.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from '@rsbuild/core';
2 | import { pluginReact } from '@rsbuild/plugin-react';
3 | import UnoCSS from '@unocss/postcss';
4 | import { pluginLess } from '@rsbuild/plugin-less';
5 |
6 | export default defineConfig({
7 | plugins: [pluginReact(),pluginLess()],
8 | tools: {
9 | postcss: {
10 | postcssOptions: {
11 | plugins: [UnoCSS()],
12 | },
13 | },
14 | },
15 | server: {
16 | port: 9999,
17 | },
18 | output: {
19 | sourceMap: {
20 | js: process.env.NODE_ENV === 'production' ? false : 'source-map',
21 | css: false,
22 | },
23 | assetPrefix: '/react-antd-admin-pro',
24 | },
25 | source: {
26 | define: {
27 | 'process.env.AUTH_USER': JSON.stringify(process.env.AUTH_USER),
28 | 'process.env.AUTH_PASSWORD': JSON.stringify(process.env.AUTH_PASSWORD),
29 | 'process.env.APP_BASE_URL': JSON.stringify(process.env.APP_BASE_URL),
30 | },
31 | alias: {
32 | '@': './src',
33 | },
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/src/pages/coupons/index.jsx:
--------------------------------------------------------------------------------
1 | import FixTabPanel from '@/components/stateless/FixTabPanel';
2 | import { Button } from 'antd';
3 | import React from 'react';
4 | import { useNavigate } from 'react-router-dom';
5 |
6 | const Coupons = () => {
7 | const navigate = useNavigate();
8 | const redirectTo = (path) => {
9 | navigate(path);
10 | };
11 |
12 | return (
13 |
14 |
17 |
20 |
23 |
30 |
31 | );
32 | };
33 |
34 | export default Coupons;
35 |
--------------------------------------------------------------------------------
/src/layout/proSider/index.tsx:
--------------------------------------------------------------------------------
1 | import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
2 | import { Layout } from 'antd';
3 | import React, { useState } from 'react';
4 |
5 | const ProSider = ({ children }: { children: React.ReactNode }) => {
6 | const [collapsed, setCollapsed] = useState(false);
7 |
8 | const onCollapse = () => {
9 | setCollapsed(!collapsed);
10 | };
11 |
12 | return (
13 |
22 | {children}
23 |
24 | {collapsed ? (
25 |
26 | ) : (
27 |
28 | )}
29 |
30 |
31 | );
32 | };
33 |
34 | export default ProSider;
35 |
--------------------------------------------------------------------------------
/src/utils/tryCatch/runPromise.js:
--------------------------------------------------------------------------------
1 | // https://github.com/rahuulmiishra/trendyAwait/blob/main/runPromise.js
2 |
3 | const runPromise = async (fn) => {
4 | try {
5 | const d = await fn()
6 | return [d, null]
7 | } catch (e) {
8 | return [null, e]
9 | }
10 | }
11 |
12 | const promise = () =>
13 | new Promise((res) => {
14 | setTimeout(() => {
15 | console.log('1')
16 | res('promise')
17 | }, 3000)
18 | })
19 |
20 | async function test() {
21 | const [res] = await runPromise(promise)
22 | console.log('23', res)
23 | }
24 |
25 | test()
26 |
27 | // Demonstration of using params.
28 | const fetchDataFromServer = (params) => () =>
29 | new Promise((res) => {
30 | setTimeout(() => {
31 | res(`Received Data' ${params}`)
32 | }, 3000)
33 | })
34 |
35 | async function main() {
36 | const [res1, error1] = await runPromise(fetchDataFromServer('123'))
37 | const [res2, error2] = await runPromise(fetchDataFromServer())
38 | console.log('res1', res1)
39 | console.log('error1', error1)
40 | console.log('res2', res2)
41 | console.log('error2', error2)
42 | }
43 |
44 | main()
45 |
--------------------------------------------------------------------------------
/src/ThemeIndex.tsx:
--------------------------------------------------------------------------------
1 | import { StyleProvider } from '@ant-design/cssinjs';
2 | import { ConfigProvider, theme } from 'antd';
3 | import 'antd/dist/reset.css';
4 | import dayjs from 'dayjs';
5 | import 'dayjs/locale/zh-cn';
6 | import './styles/reset.css';
7 |
8 | import App from './App';
9 | import { useProThemeContext } from './theme/hooks';
10 | import myThemes from './theme/index';
11 |
12 | dayjs.locale('zh-cn');
13 |
14 | const ThemeIndex = () => {
15 | const { myTheme } = useProThemeContext();
16 | console.log(myTheme);
17 | ConfigProvider.config({
18 | prefixCls: 'cl-ant',
19 | iconPrefixCls: 'cl-icon',
20 | });
21 | return (
22 |
23 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default ThemeIndex;
38 |
--------------------------------------------------------------------------------
/src/layout/proHeader/components/Breadcrumb/util.ts:
--------------------------------------------------------------------------------
1 | export const getRouteItem = (arrList, queryItem) => {
2 | let result
3 | if (Array.isArray(arrList)) {
4 | result = arrList.find((item) => item.key === queryItem || getRouteItem(item.children, queryItem))
5 | }
6 | return result
7 | }
8 |
9 | export const getRouteList = (result, arrList, queryItem) => {
10 | if (Array.isArray(arrList)) {
11 | arrList.forEach((item) => {
12 | if (item.key === queryItem) {
13 | result.push({
14 | path: item.path,
15 | key: item.key,
16 | name: item.name,
17 | isSubMenu: item.isSubMenu,
18 | i18nKey: item.i18nKey,
19 | })
20 | } else {
21 | result.push({
22 | path: item.path,
23 | key: item.key,
24 | name: item.name,
25 | isSubMenu: item.isSubMenu,
26 | i18nKey: item.i18nKey,
27 | })
28 | getRouteList(
29 | result,
30 | getRouteItem(item.children, queryItem) ? [getRouteItem(item.children, queryItem)] : [],
31 | queryItem
32 | )
33 | }
34 | })
35 | }
36 | return result
37 | }
38 |
--------------------------------------------------------------------------------
/src/layout/proHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import antd from '@/assets/svg/antd.svg';
2 | import { Layout, theme } from 'antd';
3 | import { useNavigate } from 'react-router-dom';
4 | import ProBreadcrumb from './components/Breadcrumb';
5 | import { HeaderRight } from './components/HeaderRight';
6 | import styles from './index.module.less';
7 |
8 | const ProHeader = () => {
9 | const navigate = useNavigate();
10 | const redirectTo = (path: string) => {
11 | navigate(path);
12 | };
13 |
14 | const {
15 | token: { colorBgContainer, colorBorder },
16 | } = theme.useToken();
17 |
18 | return (
19 |
23 |
24 |
redirectTo('/')}>
25 |

26 |
React Antd Admin Pro
27 |
28 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default ProHeader;
38 |
--------------------------------------------------------------------------------
/src/layout/fullscreen/index.tsx:
--------------------------------------------------------------------------------
1 | import { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
2 | import { Space, Tooltip, message } from 'antd';
3 | import { useEffect, useState } from 'react';
4 | import screenfull from 'screenfull';
5 |
6 | const FullScreen = ({ ele, tips = '全屏', placement = 'bottom' }: any) => {
7 | const [fullScreen, setFullScreen] = useState(false);
8 |
9 | useEffect(() => {
10 | screenfull.on('change', () => {
11 | if (screenfull.isFullscreen) setFullScreen(true);
12 | else setFullScreen(false);
13 | return () => screenfull.off('change', () => {});
14 | });
15 | }, []);
16 |
17 | const handleFullScreen = () => {
18 | if (!screenfull.isEnabled) message.warning('当前您的浏览器不支持全屏');
19 | const dom = document.querySelector(ele) || undefined;
20 | screenfull.toggle(dom);
21 | };
22 | return (
23 |
24 |
25 | {fullScreen ? (
26 |
27 | ) : (
28 |
29 | )}
30 |
31 |
32 | );
33 | };
34 | export default FullScreen;
35 |
--------------------------------------------------------------------------------
/src/pages/echarts/index.jsx:
--------------------------------------------------------------------------------
1 | import FixTabPanel from '@/components/stateless/FixTabPanel';
2 | import ReactEcharts from 'echarts-for-react';
3 | import React from 'react';
4 |
5 | const Echarts = () => {
6 | const option = {
7 | title: {
8 | text: '某站点用户访问来源',
9 | subtext: '纯属虚构',
10 | x: 'center',
11 | },
12 | tooltip: {
13 | trigger: 'item',
14 | formatter: '{a}
{b} : {c} ({d}%)',
15 | },
16 | legend: {
17 | orient: 'vertical',
18 | left: 'left',
19 | data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎'],
20 | },
21 | series: [
22 | {
23 | name: '访问来源',
24 | type: 'pie',
25 | radius: '55%',
26 | center: ['50%', '60%'],
27 | data: [
28 | { value: 335, name: '直接访问' },
29 | { value: 310, name: '邮件营销' },
30 | { value: 234, name: '联盟广告' },
31 | { value: 135, name: '视频广告' },
32 | { value: 1548, name: '搜索引擎' },
33 | ],
34 | itemStyle: {
35 | emphasis: {
36 | shadowBlur: 10,
37 | shadowOffsetX: 0,
38 | shadowColor: 'rgba(0, 0, 0, 0.5)',
39 | },
40 | },
41 | },
42 | ],
43 | };
44 | return (
45 |
46 | Welcome to echarts!
47 |
48 |
49 | );
50 | };
51 |
52 | export default Echarts;
53 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Rsbuild Deployment
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | workflow_dispatch:
8 |
9 | permissions:
10 | contents: read
11 | pages: write
12 | id-token: write
13 |
14 | concurrency:
15 | group: pages
16 | cancel-in-progress: false
17 |
18 | jobs:
19 | # Build job
20 | build:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@v4
25 | with:
26 | fetch-depth: 0 # Not needed if lastUpdated is not enabled
27 | - uses: pnpm/action-setup@v3 # pnpm is optional but recommended, you can also use npm / yarn
28 | with:
29 | version: 8
30 | - name: Setup Node
31 | uses: actions/setup-node@v4
32 | with:
33 | node-version: 20
34 | cache: pnpm
35 | - name: Setup Pages
36 | uses: actions/configure-pages@v5
37 | - name: Install dependencies
38 | run: pnpm install
39 | - name: Build with Rsbuild
40 | run: |
41 | pnpm run build
42 | - name: Upload artifact
43 | uses: actions/upload-pages-artifact@v3
44 | with:
45 | path: dist
46 |
47 | # Deployment job
48 | deploy:
49 | environment:
50 | name: github-pages
51 | url: ${{ steps.deployment.outputs.page_url }}
52 | needs: build
53 | runs-on: ubuntu-latest
54 | name: Deploy
55 | steps:
56 | - name: Deploy to GitHub Pages
57 | id: deployment
58 | uses: actions/deploy-pages@v4
59 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import pluginJs from '@eslint/js';
2 | import reactPlugin from 'eslint-plugin-react';
3 | import reactHooksPlugin from 'eslint-plugin-react-hooks';
4 | import pluginReactConfig from 'eslint-plugin-react/configs/recommended.js';
5 | import globals from 'globals';
6 | import tseslint from 'typescript-eslint';
7 |
8 | export default [
9 | { languageOptions: { globals: globals.browser } },
10 | pluginJs.configs.recommended,
11 | ...tseslint.configs.recommended,
12 | {
13 | languageOptions: pluginReactConfig.languageOptions,
14 | plugins: {
15 | react: reactPlugin,
16 | },
17 | rules: {
18 | ...pluginReactConfig.rules,
19 | 'react/no-string-refs': 0,
20 | 'react/display-name': 0,
21 | 'react/no-direct-mutation-state': 0,
22 | 'react/prop-types': 0,
23 | 'react/jsx-no-undef': 0,
24 | 'react/jsx-uses-react': 0,
25 | 'react/jsx-uses-vars': 0,
26 | 'react/no-danger-with-children': 0,
27 | 'react/require-render-return': 0,
28 | 'react/react-in-jsx-scope': 0,
29 | },
30 | },
31 | {
32 | files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'],
33 | languageOptions: {
34 | parserOptions: {
35 | ecmaFeatures: {
36 | jsx: true,
37 | },
38 | },
39 | },
40 | plugins: {
41 | 'react-hooks': reactHooksPlugin,
42 | },
43 | rules: {
44 | 'react-hooks/rules-of-hooks': 'warn',
45 | 'react-hooks/exhaustive-deps': 'warn',
46 | },
47 | settings: {
48 | react: {
49 | version: 'detect',
50 | },
51 | },
52 | },
53 | ];
54 |
--------------------------------------------------------------------------------
/src/layout/proSecNav/index.tsx:
--------------------------------------------------------------------------------
1 | import { Menu } from 'antd';
2 | import { useLocation, useNavigate } from 'react-router-dom';
3 |
4 | import { useGlobalStore } from '@/store';
5 | import { getKeyName } from '@/utils/publicFn';
6 | import { useShallow } from 'zustand/shallow';
7 | import styles from './index.module.less';
8 | const ProSecNav = () => {
9 | const { menus, addTab, openKeys, selectedKeys, setOpenKeys, setSelectedKeys } = useGlobalStore(
10 | useShallow((state) => ({
11 | menus: state.menus,
12 | addTab: state.addTab,
13 | openKeys: state.openKeys,
14 | setOpenKeys: state.setOpenKeys,
15 | selectedKeys: state.selectedKeys,
16 | setSelectedKeys: state.setSelectedKeys,
17 | })),
18 | );
19 | const navigate = useNavigate();
20 | const { pathname } = useLocation();
21 |
22 | return (
23 |