完成了用户注册的后端功能之后,我们也就完成了:
- 数据库:数据库搭建
- 后端:后端项目整体搭建
- 后端功能测试:增加用户注册功能
等功能。
现在开始完成如下功能:
- 前端项目搭建
- 添加一个测试用的页面(用户注册页面)
这篇文章主要介绍前端React项目的框架搭建。
1. 用 create-react-app 创建React项目。
首先,还是要用到React官方推荐的项目生成工具,生成项目大体框架。具体指令为:
npx create-react-app . --template typescript
然后安装一些必要的依赖:
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material react-router-dom axios @reduxjs/toolkit react-redux
创建必要的目录(也可自己手动创建):
mkdir -p src/{components,pages,services,store,utils,types,layouts,assets}
2. 创建一些基本的文件
首先放上最终的文件结构图:
1. 创建一个基本的布局组件(MainLayout.tsx)
import React from 'react';
import { Box, Drawer, AppBar, Toolbar, Typography, List, ListItem, ListItemIcon, ListItemText, IconButton } from '@mui/material';
import {
Menu as MenuIcon,
Dashboard,
Inventory,
People,
LocalShipping,
Settings,
ShoppingCart,
AddBox,
Person,
Assignment
} from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
const drawerWidth = 240;
interface MainLayoutProps {
children: React.ReactNode;
}
const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const navigate = useNavigate();
const [mobileOpen, setMobileOpen] = React.useState(false);
const menuItems = [
{ text: 'Dashboard', icon: <Dashboard />, path: '/' },
{ text: 'Products', icon: <Inventory />, path: '/products' },
{ text: 'Orders', icon: <ShoppingCart />, path: '/orders' },
{ text: 'Inbound', icon: <AddBox />, path: '/inbound' },
{ text: 'Customers', icon: <Person />, path: '/customers' },
{ text: 'Delivery', icon: <LocalShipping />, path: '/delivery' },
{ text: 'Users', icon: <People />, path: '/users' },
{ text: 'Reports', icon: <Assignment />, path: '/reports' },
{ text: 'Settings', icon: <Settings />, path: '/settings' },
];
const drawer = (
<div>
<Toolbar>
<Typography variant="h6" noWrap component="div">
QuickStore
</Typography>
</Toolbar>
<List>
{menuItems.map((item) => (
<ListItem
key={item.text}
onClick={() => navigate(item.path)}
sx={{ cursor: 'pointer' }}
>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.text} />
</ListItem>
))}
</List>
</div>
);
return (
<Box sx={{ display: 'flex' }}>
<AppBar
position="fixed"
sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` },
}}
>
<Toolbar>
<IconButton
color="inherit"
edge="start"
onClick={() => setMobileOpen(!mobileOpen)}
sx={{ mr: 2, display: { sm: 'none' } }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
Aluminum Warehouse Management
</Typography>
</Toolbar>
</AppBar>
<Box
component="nav"
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
>
<Drawer
variant="temporary"
open={mobileOpen}
onClose={() => setMobileOpen(false)}
ModalProps={{
keepMounted: true,
}}
sx={{
display: { xs: 'block', sm: 'none' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
>
{drawer}
</Drawer>
<Drawer
variant="permanent"
sx={{
display: { xs: 'none', sm: 'block' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
open
>
{drawer}
</Drawer>
</Box>
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { sm: `calc(100% - ${drawerWidth}px)` },
mt: '64px',
}}
>
{children}
</Box>
</Box>
);
};
export default MainLayout;
2. 创建一个基本的页面组件(Dashboard.tsx)
import React from 'react';
import { Grid, Paper, Typography, Box } from '@mui/material';
import {
Inventory,
ShoppingCart,
LocalShipping,
TrendingUp,
Person,
Assignment
} from '@mui/icons-material';
const Dashboard: React.FC = () => {
const stats = [
{
title: 'Total Products',
value: '156',
icon: <Inventory />,
color: '#1976d2',
description: 'Different types of aluminum products'
},
{
title: 'Pending Orders',
value: '23',
icon: <ShoppingCart />,
color: '#2e7d32',
description: 'Orders waiting for processing'
},
{
title: 'Delivery Tasks',
value: '8',
icon: <LocalShipping />,
color: '#ed6c02',
description: 'Pending deliveries'
},
{
title: 'Active Customers',
value: '45',
icon: <Person />,
color: '#9c27b0',
description: 'Regular customers'
},
{
title: 'Monthly Sales',
value: '$45,678',
icon: <TrendingUp />,
color: '#d32f2f',
description: 'Total sales this month'
},
{
title: 'Low Stock Items',
value: '12',
icon: <Assignment />,
color: '#7b1fa2',
description: 'Products need restocking'
},
];
return (
<Box>
<Typography variant="h4" gutterBottom>
Dashboard
</Typography>
<Grid container spacing={3}>
{stats.map((stat) => (
<Grid
key={stat.title}
sx={{
width: {
xs: '100%',
sm: '50%',
md: '33.33%'
}
}}
>
<Paper
sx={{
p: 2,
display: 'flex',
flexDirection: 'column',
height: 160,
bgcolor: stat.color,
color: 'white',
}}
>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="h6" component="div">
{stat.title}
</Typography>
{stat.icon}
</Box>
<Typography variant="h4" component="div" sx={{ mt: 2 }}>
{stat.value}
</Typography>
<Typography variant="body2" sx={{ mt: 1, opacity: 0.8 }}>
{stat.description}
</Typography>
</Paper>
</Grid>
))}
</Grid>
</Box>
);
};
export default Dashboard;
3. 更新APP.tsx来设置路由和布局
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { ThemeProvider, createTheme } from '@mui/material';
import MainLayout from './layouts/MainLayout';
import Dashboard from './pages/Dashboard';
// 临时占位组件,后续会替换为实际页面组件
const PlaceholderPage = () => <div>Page under construction</div>;
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<Router>
<MainLayout>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/products" element={<PlaceholderPage />} />
<Route path="/orders" element={<PlaceholderPage />} />
<Route path="/inbound" element={<PlaceholderPage />} />
<Route path="/customers" element={<PlaceholderPage />} />
<Route path="/delivery" element={<PlaceholderPage />} />
<Route path="/users" element={<PlaceholderPage />} />
<Route path="/reports" element={<PlaceholderPage />} />
<Route path="/settings" element={<PlaceholderPage />} />
</Routes>
</MainLayout>
</Router>
</ThemeProvider>
);
}
export default App;
4. 更新index.css,设置一些基本的格式
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #f5f5f5;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
* {
box-sizing: border-box;
}
至此,我们就搭建好了一个基本的项目框架。它包含了:
- 使用
Typescript的React项目 Material-UI组件库React Router用于路由管理- 基本的项目结构如下:
src/
├── assets/ # 静态资源
├── components/ # 可重用组件
├── layouts/ # 布局组件
├── pages/ # 页面组件
├── services/ # API 服务
├── store/ # Redux store
├── types/ # TypeScript 类型定义
├── utils/ # 工具函数
├── App.tsx # 主应用组件
├── index.css # 全局样式
├── index.tsx # 应用入口
└── react-app-env.d.ts # TypeScript 声明文件
主要功能有:
- 响应式侧边栏导航
- 仪表盘页面,显示关键指标
- 主题定制
- 路由系统
因为我们这是一个仓库管理系统,根据前面文章的建表语句,左侧导航栏我们设计如下:
- Dashboard(仪表盘)
- 显示关键业务指标
- 包括产品总数、待处理订单、配送任务等
- Products(产品管理)
- 铝合金产品的列表
- 产品详情(规格、单位、价格、库存等)
- 库存管理功能
- Orders(订单管理)
- 订单列表
- 订单详情
- 订单状态管理(pending/fulfilled/canceled)
- 区分自取和配送订单
- Inbound(入库管理)
- 入库记录
- 新增入库
- 入库历史查询
- Customers(客户管理)
- 客户列表
- 客户详情
- 客户订单历史
- Delivery(配送管理)
- 配送任务列表
- 配送状态跟踪
- 配送记录管理
- Users(用户管理)
- 用户列表(admin/staff/warehouse)
- 用户权限管理
- 用户操作记录
- Reports(报表)
- 销售报表
- 库存报表
- 配送报表
- 客户分析
- Settings(系统设置)
- 系统配置
- 权限设置
- 其他设置
每个页面都会根据用户角色(admin/staff/warehouse)显示不同的功能和数据。
运行项目
进入项目所在文件夹,输入指令:
npm start
即可启动项目。
如果成功,即可访问:localhost:3000看到页面。
补充一些关于.gitignore文件的知识。
.gitignore需要忽略的通常为以下文件:
- 依赖目录 (node_modules)
- 构建输出目录 (build)
- 环境变量文件 (.env)
- IDE 配置文件
- 操作系统生成的文件
- 日志文件
- 测试覆盖率报告
源码如下:
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.env
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# IDE
.idea/
.vscode/
*.swp
*.swo
# TypeScript
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
.gitignore文件是否应该被上传到github。
需要上传到git服务器。这有助于:
- 其他开发者克隆项目时,会立即知道哪些文件不需要被追踪
- 确保团队所有成员都使用相同的忽略规则
- 防止有人不小心提交了不应该提交的文件
所以其他人clone项目的时候,也应该把.gitignore文件clone下来。
.json文件是否应上传github?
需要上传到git服务器。
前端项目中的几个.json文件为:
-
package-lock.json
- 必须上传
- 作用:锁定所有依赖包的具体版本号
- 重要性:确保所有开发者使用完全相同的依赖版本,避免"在我机器上能运行"的问题
- 如果不提交:其他开发者可能安装到不同版本的依赖,导致项目运行不一致
-
package.json
- 必须上传
- 作用:定义项目的基本信息、依赖包、脚本命令等
- 重要性:其他开发者需要知道项目依赖和可用的命令
- 如果不提交:其他开发者无法知道项目需要哪些依赖
-
tsconfig.json
- 必须上传
- 作用:TypeScript 的配置文件
- 重要性:确保所有开发者使用相同的 TypeScript 编译设置
- 如果不提交:可能导致类型检查结果不一致
-
.eslintrc.json(如果有)
- 建议上传
- 作用:ESLint 的配置文件
- 重要性:保持代码风格一致
- 如果不提交:可能导致代码风格不一致
-
.prettierrc.json(如果有)
- 建议上传
- 作用:Prettier 的配置文件
- 重要性:保持代码格式化规则一致
- 如果不提交:可能导致代码格式化不一致
不需要上传的 JSON 文件:
- 包含敏感信息的配置文件(如包含 API 密钥、密码等)
- 本地开发环境的特定配置
- 临时生成的 JSON 文件
前端框架搭建完成。
下一篇,编写用户注册页面。