【前端知识】使用React+Vite构建企业级项目模板

使用React+Vite构建企业级项目模板

    • **一、项目结构升级**
    • **二、核心步骤与代码**
      • [**1. 初始化与安装依赖**](#1. 初始化与安装依赖)
      • [**2. 配置 Vite (`vite.config.js`)**](#2. 配置 Vite (vite.config.js))
      • [**3. 国际化配置 (`src/i18n.js`)**](#3. 国际化配置 (src/i18n.js))
      • [**4. 创建翻译文件**](#4. 创建翻译文件)
      • [**5. 认证状态管理 (`src/store/authStore.js`)**](#5. 认证状态管理 (src/store/authStore.js))
      • [**6. 路由守卫 (`src/components/AuthGuard.jsx`)**](#6. 路由守卫 (src/components/AuthGuard.jsx))
      • [**7. 路由配置 (`src/router/index.jsx`)**](#7. 路由配置 (src/router/index.jsx))
      • [**8. 主入口与布局 (`src/main.jsx`, `src/App.jsx`)**](#8. 主入口与布局 (src/main.jsx, src/App.jsx))
      • [**9. 关键页面与组件**](#9. 关键页面与组件)
      • [**10. 全局样式 (`src/styles/global.css`)**](#10. 全局样式 (src/styles/global.css))
      • [**11. 更新 `index.html` 和 `package.json`**](#11. 更新 index.htmlpackage.json)
    • **三、运行与测试**
    • **四、总结**

一、项目结构升级

复制代码
react-enterprise-template/
├─ public/
├─ src/
│  ├─ api/                 # API 请求
│  ├─ assets/              # 静态资源(图片、字体)
│  ├─ components/          # 公共组件
│  │  ├─ AuthGuard.jsx     # 路由守卫组件
│  │  ├─ LanguageSwitcher.jsx # 语言切换器
│  │  └─ Sidebar.jsx       # 侧边栏
│  ├─ hooks/               # 自定义 Hooks
│  │  └─ useAuth.js        # 认证相关逻辑
│  ├─ locales/             # 国际化翻译文件
│  │  ├─ en/              # 英文
│  │  │  └─ translation.json
│  │  └─ zh/              # 中文
│  │      └─ translation.json
│  ├─ pages/               # 页面
│  │  ├─ Login.jsx         # 登录页
│  │  ├─ Dashboard.jsx     # 仪表盘(需登录)
│  │  ├─ Admin.jsx         # 管理页(需 admin 角色)
│  │  └─ Unauthorized.jsx  # 无权限页
│  ├─ router/              # 路由配置
│  │  └─ index.jsx
│  ├─ store/               # Zustand 状态
│  │  ├─ authStore.js      # 认证状态
│  │  └─ counterStore.js
│  ├─ styles/              # 全局样式
│  ├─ utils/               # 工具函数
│  ├─ App.jsx
│  ├─ main.jsx
│  └─ i18n.js             # i18n 配置
├─ index.html
├─ package.json
└─ vite.config.js

二、核心步骤与代码

1. 初始化与安装依赖

bash 复制代码
# 1. 创建基础 Vite 项目
npm create vite@latest react-enterprise-template -- --template react
cd react-enterprise-template
npm install

# 2. 安装所有核心依赖
npm install react-router-dom zustand antd @ant-design/icons @tanstack/react-query axios framer-motion

# 3. 安装新增功能依赖
npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend
npm install vite-plugin-style-import -D

2. 配置 Vite (vite.config.js)

js 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import styleImport from 'vite-plugin-style-import'

export default defineConfig({
  plugins: [
    react(),
    styleImport({
      libs: [
        {
          libraryName: 'antd',
          esModule: true,
          resolveStyle: (name) => `antd/es/${name}/style/index`,
        },
      ],
    }),
  ],
})

3. 国际化配置 (src/i18n.js)

这是实现多语言的核心。

js 复制代码
// src/i18n.js
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import Backend from 'i18next-http-backend' // 用于加载外部 JSON 文件

i18n
  .use(Backend) // 加载翻译文件
  .use(LanguageDetector) // 检测用户语言
  .use(initReactI18next) // 将 i18n 传递给 react-i18next
  .init({
    fallbackLng: 'en', // 默认语言
    debug: true,
    interpolation: {
      escapeValue: false, // 不转义 HTML
    },
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json', // 翻译文件路径
    },
  })

export default i18n

4. 创建翻译文件

src/locales/en/translation.json:

json 复制代码
{
  "app_title": "Enterprise Dashboard",
  "login": "Login",
  "logout": "Logout",
  "dashboard": "Dashboard",
  "admin_panel": "Admin Panel",
  "unauthorized": "Unauthorized Access",
  "welcome_user": "Welcome, {{name}}!",
  "language": "Language",
  "english": "English",
  "chinese": "Chinese",
  "switch_role": "Switch Role (Demo)",
  "role_admin": "Admin",
  "role_user": "User",
  "secret_content": "This is a secret admin-only content.",
  "public_content": "This is public content."
}

src/locales/zh/translation.json:

json 复制代码
{
  "app_title": "企业管理系统",
  "login": "登录",
  "logout": "退出登录",
  "dashboard": "仪表盘",
  "admin_panel": "管理员面板",
  "unauthorized": "无权访问",
  "welcome_user": "欢迎您,{{name}}!",
  "language": "语言",
  "english": "英文",
  "chinese": "中文",
  "switch_role": "切换角色 (演示)",
  "role_admin": "管理员",
  "role_user": "普通用户",
  "secret_content": "这是仅供管理员查看的秘密内容。",
  "public_content": "这是公开的内容。"
}

5. 认证状态管理 (src/store/authStore.js)

使用 Zustand 管理用户登录状态和角色。

js 复制代码
// src/store/authStore.js
import create from 'zustand'

const useAuthStore = create((set, get) => ({
  user: null, // { name: 'Alice', role: 'admin' }
  token: null,

  login: (user, token) => {
    set({ user, token })
    localStorage.setItem('auth_token', token)
    localStorage.setItem('auth_user', JSON.stringify(user))
  },

  logout: () => {
    set({ user: null, token: null })
    localStorage.removeItem('auth_token')
    localStorage.removeItem('auth_user')
  },

  initialize: () => {
    // 页面刷新时恢复状态
    const token = localStorage.getItem('auth_token')
    const userStr = localStorage.getItem('auth_user')
    if (token && userStr) {
      try {
        const user = JSON.parse(userStr)
        set({ user, token })
      } catch (e) {
        console.error("Failed to parse user from local storage")
      }
    }
  },

  switchRole: () => {
    // 演示用:切换当前用户的角色
    const currentUser = get().user
    if (!currentUser) return

    const newRole = currentUser.role === 'admin' ? 'user' : 'admin'
    const updatedUser = { ...currentUser, role: newRole }
    set({ user: updatedUser })
    localStorage.setItem('auth_user', JSON.stringify(updatedUser))
  }
}))

export default useAuthStore

6. 路由守卫 (src/components/AuthGuard.jsx)

这是一个高阶组件(HOC),用于保护路由。

jsx 复制代码
// src/components/AuthGuard.jsx
import React from 'react'
import { Navigate, useLocation } from 'react-router-dom'
import useAuthStore from '../store/authStore'

/**
 * 路由守卫组件
 * @param {React.ReactNode} children - 需要保护的组件
 * @param {string[]} allowedRoles - 允许访问的角色数组,为空则不检查角色
 */
const AuthGuard = ({ children, allowedRoles = [] }) => {
  const { user, initialize } = useAuthStore()
  const location = useLocation()

  // 初始化认证状态(从 localStorage 恢复)
  React.useEffect(() => {
    initialize()
  }, [])

  // 1. 检查是否已登录
  if (!user) {
    // 未登录,重定向到登录页,并记录当前尝试访问的地址
    return <Navigate to="/login" state={{ from: location }} replace />
  }

  // 2. 检查角色权限
  if (allowedRoles.length > 0 && !allowedRoles.includes(user.role)) {
    // 已登录但无权限
    return <Navigate to="/unauthorized" replace />
  }

  // 3. 通过所有检查,渲染子组件
  return children
}

export default AuthGuard

7. 路由配置 (src/router/index.jsx)

定义所有路由,并应用守卫。

jsx 复制代码
// src/router/index.jsx
import { createBrowserRouter } from 'react-router-dom'
import App from '../App'
import AuthGuard from '../components/AuthGuard'
import Login from '../pages/Login'
import Dashboard from '../pages/Dashboard'
import Admin from '../pages/Admin'
import Unauthorized from '../pages/Unauthorized'
import { useTranslation } from 'react-i18next'

// 一个包装组件,用于在渲染时触发 i18n 更新
const I18nWrapper = ({ children }) => {
  const { i18n } = useTranslation()
  // 当语言改变时,强制重新渲染以应用新语言
  // 注意:在真实大型应用中,可能不需要这个,但 Vite/React 热更新有时需要它来触发 re-render
  return <>{children}</>
}

const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [
      { index: true, element: <Navigate to="/dashboard" replace /> },
      { path: 'login', element: <Login /> },
      { path: 'unauthorized', element: <Unauthorized /> },
      {
        path: 'dashboard',
        element: (
          <I18nWrapper>
            <AuthGuard>
              <Dashboard />
            </AuthGuard>
          </I18nWrapper>
        ),
      },
      {
        path: 'admin',
        element: (
          <I18nWrapper>
            <AuthGuard allowedRoles={['admin']}>
              <Admin />
            </AuthGuard>
          </I18nWrapper>
        ),
      },
    ],
  },
])

export default router

8. 主入口与布局 (src/main.jsx, src/App.jsx)

整合路由、i18n 和全局布局。

jsx 复制代码
// src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RouterProvider } from 'react-router-dom'
import App from './App'
import router from './router'
import './i18n' // 必须在 RouterProvider 之前导入以初始化 i18n
import './styles/global.css'

const queryClient = new QueryClient()

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={router} />
    </QueryClientProvider>
  </React.StrictMode>,
)
jsx 复制代码
// src/App.jsx
import React from 'react'
import { Layout } from 'antd'
import { Outlet } from 'react-router-dom'
import Sidebar from './components/Sidebar'
import LanguageSwitcher from './components/LanguageSwitcher'
import useAuthStore from './store/authStore'
import { useTranslation } from 'react-i18next'

const { Header, Content, Footer } = Layout

export default function App() {
  const { user } = useAuthStore()
  const { t } = useTranslation()

  return (
    <Layout style={{ minHeight: '100vh' }}>
      {/* 侧边栏:仅登录后显示 */}
      {user && <Sidebar />}

      <Layout>
        <Header style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '0 24px' }}>
          <h2 style={{ color: 'white', margin: 0 }}>{t('app_title')}</h2>
          <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
            {user && <span style={{ color: 'white' }}>Hi, {user.name}</span>}
            <LanguageSwitcher />
          </div>
        </Header>

        <Content style={{ margin: '24px' }}>
          <div style={{ padding: 24, minHeight: 360, background: '#fff', borderRadius: 8 }}>
            <Outlet />
          </div>
        </Content>

        <Footer style={{ textAlign: 'center' }}>
          React Enterprise Template ©{new Date().getFullYear()}
        </Footer>
      </Layout>
    </Layout>
  )
}

9. 关键页面与组件

src/components/Sidebar.jsx (带权限的动态菜单)

jsx 复制代码
// src/components/Sidebar.jsx
import React from 'react'
import { Menu } from 'antd'
import { Link, useLocation } from 'react-router-dom'
import useAuthStore from '../store/authStore'
import { useTranslation } from 'react-i18next'
import {
  DashboardOutlined,
  UserOutlined,
  SettingOutlined,
  LockOutlined,
  TranslationOutlined,
} from '@ant-design/icons'

const Sidebar = () => {
  const { user } = useAuthStore()
  const location = useLocation()
  const { t } = useTranslation()

  // 根据角色定义菜单项
  const menuItems = [
    {
      key: '/dashboard',
      icon: <DashboardOutlined />,
      label: <Link to="/dashboard">{t('dashboard')}</Link>,
    },
    {
      key: 'admin-section',
      label: t('admin_panel'),
      icon: <SettingOutlined />,
      children: [
        {
          key: '/admin',
          icon: <LockOutlined />,
          label: <Link to="/admin">{t('admin_panel')}</Link>,
        },
      ],
      // 权限控制:只有 admin 能看到这个父菜单
      hidden: user?.role !== 'admin',
    },
    {
      key: '/unauthorized',
      icon: <LockOutlined />,
      label: <Link to="/unauthorized">{t('unauthorized')}</Link>,
    },
  ]

  return (
    <Menu
      theme="dark"
      mode="inline"
      selectedKeys={[location.pathname]}
      items={menuItems.filter(item => !item.hidden)}
    />
  )
}

export default Sidebar

src/components/LanguageSwitcher.jsx

jsx 复制代码
// src/components/LanguageSwitcher.jsx
import React from 'react'
import { Select } from 'antd'
import { useTranslation } from 'react-i18next'

const { Option } = Select

const LanguageSwitcher = () => {
  const { i18n } = useTranslation()

  const changeLanguage = (lng) => {
    i18n.changeLanguage(lng)
  }

  return (
    <Select
      defaultValue={i18n.language}
      style={{ width: 120 }}
      onChange={changeLanguage}
      options={[
        { value: 'en', label: 'English' },
        { value: 'zh', label: '中文' },
      ]}
    />
  )
}

export default LanguageSwitcher

src/pages/Login.jsx

jsx 复制代码
// src/pages/Login.jsx
import React, { useState } from 'react'
import { Form, Input, Button, Card, message } from 'antd'
import { UserOutlined, LockOutlined } from '@ant-design/icons'
import { useNavigate, useLocation } from 'react-router-dom'
import useAuthStore from '../store/authStore'
import { useTranslation } from 'react-i18next'

const Login = () => {
  const [loading, setLoading] = useState(false)
  const navigate = useNavigate()
  const location = useLocation()
  const { login } = useAuthStore()
  const { t } = useTranslation()

  const onFinish = (values) => {
    setLoading(true)
    // 模拟 API 登录请求
    setTimeout(() => {
      // 演示逻辑:输入任意用户名密码登录,默认给 admin 角色
      const mockUser = { name: values.username || 'Demo User', role: 'admin' }
      const mockToken = 'fake-jwt-token'
      
      login(mockUser, mockToken)
      message.success(t('login') + ' Success!')

      // 重定向到之前尝试访问的页面或仪表盘
      const from = location.state?.from?.pathname || '/dashboard'
      navigate(from, { replace: true })
      setLoading(false)
    }, 800)
  }

  return (
    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '80vh' }}>
      <Card title={t('login')} style={{ width: 400 }}>
        <Form onFinish={onFinish}>
          <Form.Item
            name="username"
            rules={[{ required: true, message: 'Please input your Username!' }]}
          >
            <Input prefix={<UserOutlined />} placeholder="Username" />
          </Form.Item>
          <Form.Item
            name="password"
            rules={[{ required: true, message: 'Please input your Password!' }]}
          >
            <Input.Password prefix={<LockOutlined />} placeholder="Password" />
          </Form.Item>
          <Form.Item>
            <Button type="primary" htmlType="submit" loading={loading} block>
              {t('login')}
            </Button>
          </Form.Item>
        </Form>
      </Card>
    </div>
  )
}

export default Login

src/pages/Dashboard.jsx

jsx 复制代码
// src/pages/Dashboard.jsx
import React from 'react'
import { Card, Button, Typography } from 'antd'
import { useTranslation } from 'react-i18next'
import useAuthStore from '../store/authStore'

const { Title, Paragraph } = Typography

const Dashboard = () => {
  const { t } = useTranslation()
  const { user, switchRole } = useAuthStore()

  return (
    <div>
      <Title level={3}>{t('welcome_user', { name: user?.name })}</Title>
      <Paragraph>{t('public_content')}</Paragraph>
      
      <Card title={t('switch_role')} style={{ marginTop: 24 }}>
        <p>Your current role is: <strong>{user?.role.toUpperCase()}</strong></p>
        <Button onClick={switchRole}>
          Switch to {user?.role === 'admin' ? 'User' : 'Admin'}
        </Button>
      </Card>
    </div>
  )
}

export default Dashboard

src/pages/Admin.jsx

jsx 复制代码
// src/pages/Admin.jsx
import React from 'react'
import { Card, Alert } from 'antd'
import { useTranslation } from 'react-i18next'

const Admin = () => {
  const { t } = useTranslation()

  return (
    <Card title={t('admin_panel')}>
      <Alert
        message={t('secret_content')}
        type="info"
        showIcon
        description="This content is only visible because you have the 'admin' role."
      />
    </Card>
  )
}

export default Admin

src/pages/Unauthorized.jsx

jsx 复制代码
// src/pages/Unauthorized.jsx
import React from 'react'
import { Result, Button } from 'antd'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'

const Unauthorized = () => {
  const { t } = useTranslation()
  const navigate = useNavigate()

  return (
    <Result
      status="403"
      title="403"
      subTitle={t('unauthorized')}
      extra={
        <Button type="primary" onClick={() => navigate('/')}>
          Back Home
        </Button>
      }
    />
  )
}

export default Unauthorized

10. 全局样式 (src/styles/global.css)

css 复制代码
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  background-color: #f0f2f5;
}

#root {
  min-height: 100vh;
}

/* 覆盖 Ant Design 默认样式以配合布局 */
.ant-layout-sider {
  background: #001529 !important;
}

11. 更新 index.htmlpackage.json

index.html 保持不变,主要更新 package.jsondependenciesscripts

json 复制代码
// package.json
{
  "name": "react-enterprise-template",
  "private": true,
  "version": "0.0.1",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@ant-design/icons": "^5.2.6",
    "@tanstack/react-query": "^5.17.19",
    "antd": "^5.12.5",
    "axios": "^1.6.7",
    "framer-motion": "^11.0.5",
    "i18next": "^23.7.11",
    "i18next-browser-languagedetector": "^7.2.0",
    "i18next-http-backend": "^2.4.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-i18next": "^14.0.1",
    "react-router-dom": "^6.22.1",
    "zustand": "^4.5.1"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.2.1",
    "vite": "^5.1.4",
    "vite-plugin-style-import": "^2.0.0"
  }
}

三、运行与测试

  1. 安装依赖并启动

    bash 复制代码
    npm install
    npm run dev

    打开 http://localhost:5173

  2. 功能测试流程

    • 登录 :访问 /login,输入任意用户名密码登录。
    • 语言切换:点击右上角下拉框,在"English"和"中文"之间切换,观察页面内容变化。
    • 路由守卫 :直接访问 /dashboard,会被自动跳转到 /login
    • 权限控制
      • 登录后,侧边栏会显示"Admin Panel"菜单。
      • 点击进入 /admin 页面,可以看到管理员内容。
      • 点击"Switch Role"按钮,将角色从 admin 切换到 user
      • 刷新页面后,你会发现"Admin Panel"菜单消失了,因为 AuthGuard 检测到你不再是 admin 角色,无法再访问 /admin 路由。
    • 无权限页面 :手动在地址栏输入 /admin,如果当前角色是 user,会被重定向到 /unauthorized 页面。

四、总结

这个模板提供了一个坚实的企业级 React 应用基础,涵盖了现代前端开发的几乎所有核心需求:

  • 架构清晰:模块化设计,易于维护和扩展。
  • 用户体验:国际化支持、流畅的布局和动画。
  • 安全可靠:完善的路由守卫和基于角色的权限控制。
  • 开发效率:集成了最佳实践的状态管理、数据请求和 UI 组件库。
相关推荐
Dxy12393102162 小时前
HTML常用CSS样式推荐:打造高效、美观的网页设计
前端·css·html
酉鬼女又兒2 小时前
零基础入门前端JavaScript Object 对象完全指南:从基础到进阶(可用于备赛蓝桥杯Web应用开发赛道)
开发语言·前端·javascript·职场和发展·蓝桥杯
tlwlmy2 小时前
python excel图片批量拼接导出
前端·python·excel
R-sz2 小时前
坐标转换踩坑实录:UTM → WGS84 → GCJ02 前端后端一致实现
开发语言·前端·python
HWL56792 小时前
uni-app中路由的使用
前端·uni-app
程序员陆业聪2 小时前
上下文工程与提示词工程:拆解 OpenClaw 是如何「喂养」大模型的
前端
wuhen_n2 小时前
初识Function Calling:让AI学会“调用工具”
前端·vue.js·ai编程
wuhen_n2 小时前
异步组件与 Suspense:如何优雅地处理加载状态并优化首屏加载?
前端·javascript·vue.js
万物得其道者成2 小时前
uni-app App 端不支持 SSE?用 renderjs + XHR 流式解析实现稳定输出
前端·javascript·uni-app