前端微服务架构CRUD模板
🎨 前端微服务架构CRUD模板(纯前端版)
📋 前端微服务架构核心概念
- 微前端:将前端应用拆分为独立可部署的微应用
- 模块联邦:Webpack 5的跨应用模块共享
- 独立部署:每个微前端可独立开发、测试、部署
- 技术栈无关:不同微应用可使用不同框架
🎯 前端微服务CRUD模板
模板25:前端微服务CRUD生成器
基于微前端架构创建独立的前端CRUD微服务:
## 一、微应用定义
微应用名称:[应用名称] (如:order-management, user-admin, product-catalog)
业务范围:[具体业务功能]
主应用集成点:[集成到哪个主应用/门户]
## 二、技术栈要求
核心框架:
- 主框架:[React 18 / Vue 3 / Angular 15] (选择其一)
- 状态管理:[Redux Toolkit / Pinia / NgRx]
- 路由:[React Router / Vue Router / Angular Router]
- UI组件库:[Ant Design / Element Plus / Material-UI]
- 构建工具:[Vite / Webpack 5]
- 微前端方案:[Module Federation / Qiankun / Single-SPA]
开发工具链:
- 语言:[TypeScript 5.x]
- CSS方案:[Tailwind CSS / Styled Components / SCSS]
- 代码规范:[ESLint + Prettier + Husky]
- 测试框架:[Vitest / Jest + React Testing Library / Cypress]
- 包管理:[pnpm / npm / yarn]
## 三、架构分层要求
1. **应用外壳(Shell App)**
- 微应用加载器
- 共享依赖管理
- 全局状态管理
- 路由集成配置
2. **微应用层(Micro App)**
- 独立开发环境
- 独立构建配置
- 独立部署流水线
- 独立版本管理
3. **共享层(Shared Layer)**
- 公共组件库
- 工具函数库
- 类型定义
- API客户端
- 状态管理工具
4. **基础设施层(Infrastructure)**
- CI/CD配置
- 容器化部署
- 环境配置管理
- 监控埋点
## 四、微前端集成方案
1. **模块联邦(Webpack 5)**
- 远程模块声明
- 共享依赖配置
- 运行时加载策略
2. **独立路由管理**
- 路由命名空间
- 路由懒加载
- 路由权限控制
3. **状态隔离与共享**
- 微应用状态隔离
- 跨应用状态通信
- 全局事件总线
4. **样式隔离**
- CSS Modules
- Shadow DOM
- CSS-in-JS作用域
## 五、CRUD功能要求
1. **数据展示层**
- 智能表格(排序、筛选、分页)
- 卡片视图/列表视图切换
- 虚拟滚动(大数据量)
- 骨架屏加载
2. **表单交互层**
- 表单验证(实时/提交时)
- 表单步骤化(向导式)
- 批量操作支持
- 草稿保存
3. **数据管理层**
- 乐观更新UI
- 离线数据缓存
- 数据同步策略
- 错误重试机制
4. **API通信层**
- RESTful API客户端
- GraphQL客户端(可选)
- WebSocket实时更新
- 请求拦截/响应处理
## 六、生成内容清单
1. 微应用脚手架代码
2. 模块联邦配置
3. 共享组件库代码
4. API服务封装
5. 状态管理实现
6. 路由配置方案
7. 样式系统配置
8. 单元测试套件
9. E2E测试脚本
10. Docker部署配置
11. CI/CD流水线配置
12. 性能优化配置
## 七、非功能性需求
1. 性能:首屏加载 < 2s,交互响应 < 100ms
2. 可访问性:WCAG 2.1 AA标准
3. 响应式:支持桌面、平板、移动端
4. 浏览器兼容:Chrome 90+, Firefox 88+, Safari 14+
5. 包体积:微应用主包 < 200KB (gzipped)
请生成完整的前端微服务CRUD实现,包含所有配置文件和最佳实践示例。
🛍️ 实战示例:订单管理微前端应用
具体化模板
基于微前端架构创建独立的前端CRUD微服务:
## 一、微应用定义
微应用名称:order-management-app
业务范围:电商平台订单管理,包括订单列表、订单详情、订单创建、订单处理、数据统计
主应用集成点:集成到主门户应用`admin-portal`的`/orders/*`路由下
## 二、技术栈要求
核心框架:
- 主框架:React 18 + TypeScript 5.3
- 状态管理:Redux Toolkit + RTK Query
- 路由:React Router v6
- UI组件库:Ant Design 5.x + Ant Design Charts
- 构建工具:Vite 5.0 + Webpack Module Federation
- 微前端方案:Webpack 5 Module Federation
开发工具链:
- 语言:TypeScript 5.3 (严格模式)
- CSS方案:Tailwind CSS 3.4 + CSS Modules
- 代码规范:ESLint 8.56 + Prettier 3.1 + TypeScript ESLint
- 测试框架:Vitest + React Testing Library + Cypress 13
- 包管理:pnpm 8.0
- 代码提交:Husky + commitlint + lint-staged
## 三、架构分层要求
1. **应用外壳(Shell App)**
- 动态模块加载器(基于Module Federation)
- 共享React、ReactDOM、Ant Design等依赖
- 全局错误边界
- 微应用间通信总线
2. **微应用层(Micro App)**
- 独立开发服务器(端口3001)
- 独立构建配置(支持development/production)
- 独立部署到CDN路径 `/micro-apps/order-management/`
- 独立版本号管理(语义化版本)
3. **共享层(Shared Layer)**
- 公共组件:按钮、弹窗、表格、表单
- 工具函数:日期格式化、金额格式化、权限检查
- 类型定义:订单相关TypeScript接口
- API客户端:基于axios的封装,支持拦截器
- 状态共享:通过custom event进行跨应用通信
4. **基础设施层**
- GitHub Actions CI/CD流水线
- Docker容器化(Nginx部署)
- 环境变量管理(Vite环境变量)
- Sentry错误监控
- Google Analytics埋点
## 四、微前端集成方案
1. **模块联邦配置**
- 导出模块:OrderApp(主组件)、OrderRoutes(路由配置)
- 共享依赖:react、react-dom、react-router-dom、antd
- 远程入口:`orderManagement@http://localhost:3001/remoteEntry.js`
2. **独立路由管理**
- 路由前缀:`/orders`
- 路由配置:
* `/orders` - 订单列表
* `/orders/create` - 创建订单
* `/orders/:id` - 订单详情
* `/orders/:id/edit` - 编辑订单
* `/orders/analytics` - 订单统计
- 路由懒加载:基于React.lazy的代码分割
3. **状态隔离与共享**
- 微应用内部:Redux Toolkit局部store
- 跨应用通信:自定义事件 + Context API
- 全局状态:通过主应用props传入
4. **样式隔离**
- CSS Modules所有组件样式
- Tailwind CSS前缀配置(`om-`前缀)
- 动态样式加载/卸载
## 五、CRUD功能要求
1. **数据展示层**
- 订单列表:表格展示,支持多列排序、复合筛选
- 卡片视图:订单卡片展示,拖拽排序
- 虚拟滚动:支持10,000+订单流畅滚动
- 骨架屏:数据加载时的骨架屏效果
2. **表单交互层**
- 订单表单:包含商品选择、收货地址、支付方式
- 表单验证:Yup schema验证,实时错误提示
- 步骤化表单:创建订单分3步(商品->地址->支付)
- 批量操作:批量发货、批量打印、批量导出
- 草稿保存:LocalStorage自动保存表单草稿
3. **数据管理层**
- 乐观更新:操作后立即更新UI,后台同步
- 离线缓存:IndexedDB存储最近1000条订单
- 数据同步:SWR策略,定时刷新数据
- 错误重试:指数退避重试机制
4. **API通信层**
- RESTful客户端:基于axios,支持取消请求
- 请求拦截:自动添加token、处理loading状态
- 响应拦截:统一错误处理、消息提示
- WebSocket:实时接收订单状态更新
## 六、生成内容清单
[按上面模板要求生成完整代码]
## 七、非功能性需求
1. 性能:首屏加载 < 1.5s,LCP < 2.5s,CLS < 0.1
2. 可访问性:支持键盘导航、屏幕阅读器
3. 响应式:桌面(≥1024px)、平板(768px-1023px)、手机(<768px)
4. 浏览器兼容:Chrome 90+、Firefox 88+、Safari 14+、Edge 90+
5. 包体积:主包 < 150KB,总包 < 500KB (gzipped)
6. PWA支持:可安装为桌面应用,离线可用
🏗️ 生成的前端微服务代码结构
1. 完整项目结构
order-management-microfrontend/
├── packages/
│ ├── shell-app/ # 主应用外壳
│ │ ├── src/
│ │ │ ├── bootstrap.tsx
│ │ │ ├── App.tsx
│ │ │ ├── components/
│ │ │ │ ├── AppShell/
│ │ │ │ ├── MicroAppLoader/
│ │ │ │ └── ErrorBoundary/
│ │ │ ├── routes/
│ │ │ ├── types/
│ │ │ └── utils/
│ │ ├── index.html
│ │ ├── vite.config.ts
│ │ ├── module-federation.config.js
│ │ └── package.json
│ │
│ ├── order-app/ # 订单微应用
│ │ ├── src/
│ │ │ ├── bootstrap.tsx
│ │ │ ├── App.tsx
│ │ │ ├── components/
│ │ │ │ ├── OrderList/
│ │ │ │ ├── OrderDetail/
│ │ │ │ ├── OrderForm/
│ │ │ │ ├── OrderAnalytics/
│ │ │ │ └── shared/
│ │ │ ├── features/
│ │ │ │ ├── orders/
│ │ │ │ │ ├── api/
│ │ │ │ │ ├── components/
│ │ │ │ │ ├── hooks/
│ │ │ │ │ └── slices/
│ │ │ │ └── analytics/
│ │ │ ├── hooks/
│ │ │ ├── services/
│ │ │ ├── store/
│ │ │ ├── types/
│ │ │ ├── utils/
│ │ │ └── routes/
│ │ ├── index.html
│ │ ├── vite.config.ts
│ │ ├── module-federation.config.js
│ │ └── package.json
│ │
│ ├── shared/ # 共享模块
│ │ ├── components/
│ │ │ ├── Button/
│ │ │ ├── Table/
│ │ │ ├── Modal/
│ │ │ └── Form/
│ │ ├── hooks/
│ │ ├── utils/
│ │ ├── types/
│ │ └── styles/
│ │
│ └── shared-deps/ # 共享依赖配置
│ └── package.json
│
├── configs/ # 共享配置
│ ├── eslint/
│ ├── typescript/
│ ├── tailwind/
│ └── jest/
│
├── scripts/ # 构建脚本
│ ├── build-all.mjs
│ ├── deploy.mjs
│ └── dev.mjs
│
├── docker/
│ ├── nginx.conf
│ ├── Dockerfile
│ └── docker-compose.yml
│
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── cd.yml
│
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
└── README.md
2. 核心文件代码示例
2.1 模块联邦配置 (order-app)
// packages/order-app/module-federation.config.js
import { defineConfig } from '@originjs/vite-plugin-federation';
export default defineConfig({
name: 'orderManagement',
filename: 'remoteEntry.js',
exposes: {
'./OrderApp': './src/bootstrap.tsx',
'./OrderRoutes': './src/routes/index.tsx',
'./OrderStore': './src/store/store.ts',
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.2.0',
},
'react-dom': {
singleton: true,
requiredVersion: '^18.2.0',
},
'react-router-dom': {
singleton: true,
requiredVersion: '^6.20.0',
},
'@reduxjs/toolkit': {
singleton: true,
requiredVersion: '^1.9.7',
},
'react-redux': {
singleton: true,
requiredVersion: '^8.1.3',
},
antd: {
singleton: true,
requiredVersion: '^5.12.0',
},
},
remotes: {
shared: 'shared@http://localhost:3002/remoteEntry.js',
},
});
2.2 订单列表组件 (核心CRUD)
// packages/order-app/src/features/orders/components/OrderList/OrderList.tsx
import React, { useState, useCallback } from 'react';
import {
Table,
Button,
Space,
Input,
Select,
Tag,
DatePicker,
Card,
Row,
Col,
Statistic,
Dropdown,
message,
} from 'antd';
import {
SearchOutlined,
PlusOutlined,
DownloadOutlined,
FilterOutlined,
MoreOutlined,
} from '@ant-design/icons';
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import type { FilterValue, SorterResult } from 'antd/es/table/interface';
import { useGetOrdersQuery, useDeleteOrderMutation } from '../api/orderApi';
import { Order, OrderStatus, OrderListParams } from '../types';
import { formatCurrency, formatDate } from '@/utils/formatters';
import { useDebounce } from '@/hooks/useDebounce';
import { ExportButton } from '@/components/shared/ExportButton';
import { OrderStatusBadge } from './OrderStatusBadge';
const { RangePicker } = DatePicker;
const { Option } = Select;
interface TableParams {
pagination: TablePaginationConfig;
sortField?: string;
sortOrder?: string;
filters?: Record<string, FilterValue | null>;
}
export const OrderList: React.FC = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [tableParams, setTableParams] = useState<TableParams>({
pagination: {
current: 1,
pageSize: 20,
showSizeChanger: true,
showQuickJumper: true,
},
});
// 搜索条件
const [searchParams, setSearchParams] = useState<OrderListParams>({});
const debouncedSearch = useDebounce(searchParams, 500);
// RTK Query hooks
const { data, isLoading, isFetching, refetch } = useGetOrdersQuery({
page: tableParams.pagination?.current || 1,
limit: tableParams.pagination?.pageSize || 20,
...debouncedSearch,
});
const [deleteOrder] = useDeleteOrderMutation();
// 列定义
const columns: ColumnsType<Order> = [
{
title: '订单号',
dataIndex: 'orderNumber',
key: 'orderNumber',
width: 180,
fixed: 'left',
sorter: true,
render: (text, record) => (
<a href={`/orders/${record.id}`} className="font-mono">
{text}
</a>
),
},
{
title: '客户',
dataIndex: 'customer',
key: 'customer',
width: 150,
render: (customer) => (
<div>
<div className="font-medium">{customer.name}</div>
<div className="text-gray-500 text-sm">{customer.phone}</div>
</div>
),
},
{
title: '金额',
dataIndex: 'totalAmount',
key: 'totalAmount',
width: 120,
sorter: true,
render: (amount) => (
<span className="font-bold text-green-600">
{formatCurrency(amount)}
</span>
),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 120,
filters: [
{ text: '待支付', value: 'PENDING' },
{ text: '已支付', value: 'PAID' },
{ text: '已发货', value: 'SHIPPED' },
{ text: '已完成', value: 'DELIVERED' },
{ text: '已取消', value: 'CANCELLED' },
],
render: (status: OrderStatus) => <OrderStatusBadge status={status} />,
},
{
title: '下单时间',
dataIndex: 'createdAt',
key: 'createdAt',
width: 180,
sorter: true,
render: (date) => formatDate(date, 'YYYY-MM-DD HH:mm'),
},
{
title: '操作',
key: 'action',
width: 120,
fixed: 'right',
render: (_, record) => (
<Dropdown
menu={{
items: [
{
key: 'view',
label: '查看详情',
onClick: () => handleView(record),
},
{
key: 'edit',
label: '编辑订单',
onClick: () => handleEdit(record),
},
{
key: 'cancel',
label: '取消订单',
disabled: !['PENDING', 'PAID'].includes(record.status),
onClick: () => handleCancel(record.id),
},
{
key: 'delete',
label: '删除订单',
danger: true,
disabled: record.status !== 'CANCELLED',
onClick: () => handleDelete(record.id),
},
],
}}
>
<Button type="text" icon={<MoreOutlined />} />
</Dropdown>
),
},
];
// 处理表格变化
const handleTableChange = (
pagination: TablePaginationConfig,
filters: Record<string, FilterValue | null>,
sorter: SorterResult<Order> | SorterResult<Order>[]
) => {
setTableParams({
pagination,
filters,
sortField: Array.isArray(sorter) ? undefined : sorter.field,
sortOrder: Array.isArray(sorter) ? undefined : sorter.order,
});
// 构建API参数
const apiParams: Partial<OrderListParams> = {};
if (filters.status) {
apiParams.status = filters.status as OrderStatus[];
}
if (!Array.isArray(sorter) && sorter.field && sorter.order) {
apiParams.sortBy = String(sorter.field);
apiParams.sortOrder = sorter.order === 'ascend' ? 'ASC' : 'DESC';
}
setSearchParams((prev) => ({ ...prev, ...apiParams }));
};
// 搜索处理
const handleSearch = (values: any) => {
setSearchParams((prev) => ({
...prev,
...values,
page: 1, // 重置到第一页
}));
setTableParams((prev) => ({
...prev,
pagination: { ...prev.pagination, current: 1 },
}));
};
// 批量操作
const handleBatchDelete = async () => {
if (selectedRowKeys.length === 0) return;
try {
await Promise.all(
selectedRowKeys.map((id) => deleteOrder(id.toString()).unwrap())
);
message.success(`成功删除 ${selectedRowKeys.length} 个订单`);
setSelectedRowKeys([]);
refetch();
} catch (error) {
message.error('删除失败');
}
};
// 导出数据
const handleExport = useCallback(async () => {
try {
// 实现导出逻辑
message.success('导出任务已开始,请稍后查看下载');
} catch (error) {
message.error('导出失败');
}
}, []);
// 统计数据
const stats = data?.stats || {
total: 0,
totalAmount: 0,
pendingCount: 0,
avgOrderValue: 0,
};
return (
<div className="order-list-container">
{/* 统计卡片 */}
<Row gutter={[16, 16]} className="mb-6">
<Col xs={24} sm={12} lg={6}>
<Card size="small">
<Statistic
title="总订单数"
value={stats.total}
valueStyle={{ color: '#1890ff' }}
/>
</Card>
</Col>
<Col xs={24} sm={12} lg={6}>
<Card size="small">
<Statistic
title="总金额"
value={stats.totalAmount}
precision={2}
prefix="¥"
valueStyle={{ color: '#52c41a' }}
/>
</Card>
</Col>
<Col xs={24} sm={12} lg={6}>
<Card size="small">
<Statistic
title="待处理"
value={stats.pendingCount}
valueStyle={{ color: '#fa8c16' }}
/>
</Card>
</Col>
<Col xs={24} sm={12} lg={6}>
<Card size="small">
<Statistic
title="平均订单价值"
value={stats.avgOrderValue}
precision={2}
prefix="¥"
/>
</Card>
</Col>
</Row>
{/* 搜索和筛选区域 */}
<Card className="mb-6" size="small">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<Space wrap>
<Input
placeholder="搜索订单号/客户"
prefix={<SearchOutlined />}
allowClear
style={{ width: 200 }}
onChange={(e) =>
setSearchParams((prev) => ({ ...prev, keyword: e.target.value }))
}
/>
<Select
placeholder="订单状态"
allowClear
style={{ width: 120 }}
onChange={(value) =>
setSearchParams((prev) => ({ ...prev, status: value }))
}
>
<Option value="PENDING">待支付</Option>
<Option value="PAID">已支付</Option>
<Option value="SHIPPED">已发货</Option>
<Option value="DELIVERED">已完成</Option>
<Option value="CANCELLED">已取消</Option>
</Select>
<RangePicker
onChange={(dates) =>
setSearchParams((prev) => ({
...prev,
startDate: dates?.[0]?.toISOString(),
endDate: dates?.[1]?.toISOString(),
}))
}
/>
<Button
type="primary"
icon={<FilterOutlined />}
onClick={() => handleSearch(searchParams)}
>
筛选
</Button>
</Space>
<Space>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => (window.location.href = '/orders/create')}
>
新建订单
</Button>
<ExportButton onExport={handleExport} />
{selectedRowKeys.length > 0 && (
<Button danger onClick={handleBatchDelete}>
批量删除 ({selectedRowKeys.length})
</Button>
)}
</Space>
</div>
</Card>
{/* 数据表格 */}
<Card>
<Table
columns={columns}
rowKey="id"
dataSource={data?.items || []}
pagination={{
...tableParams.pagination,
total: data?.total || 0,
showTotal: (total, range) =>
`第 ${range[0]}-${range[1]} 条,共 ${total} 条`,
}}
loading={isLoading || isFetching}
onChange={handleTableChange}
rowSelection={{
selectedRowKeys,
onChange: setSelectedRowKeys,
}}
scroll={{ x: 1500, y: 600 }}
sticky
size="middle"
/>
</Card>
</div>
);
};
export default OrderList;
2.3 RTK Query API层
// packages/order-app/src/features/orders/api/orderApi.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import type {
Order,
OrderListParams,
OrderListResponse,
CreateOrderRequest,
UpdateOrderRequest,
} from '../types';
import { API_BASE_URL } from '@/config/constants';
export const orderApi = createApi({
reducerPath: 'orderApi',
baseQuery: fetchBaseQuery({
baseUrl: `${API_BASE_URL}/api/v1/orders`,
prepareHeaders: (headers) => {
const token = localStorage.getItem('access_token');
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
headers.set('Content-Type', 'application/json');
return headers;
},
}),
tagTypes: ['Order'],
endpoints: (builder) => ({
// 获取订单列表
getOrders: builder.query<OrderListResponse, OrderListParams>({
query: (params) => ({
url: '',
params: {
page: params.page || 1,
limit: params.limit || 20,
...params,
},
}),
providesTags: (result) =>
result
? [
...result.items.map(({ id }) => ({ type: 'Order' as const, id })),
{ type: 'Order', id: 'LIST' },
]
: [{ type: 'Order', id: 'LIST' }],
}),
// 获取单个订单
getOrder: builder.query<Order, string>({
query: (id) => `/${id}`,
providesTags: (result, error, id) => [{ type: 'Order', id }],
}),
// 创建订单
createOrder: builder.mutation<Order, CreateOrderRequest>({
query: (body) => ({
url: '',
method: 'POST',
body,
}),
invalidatesTags: [{ type: 'Order', id: 'LIST' }],
// 乐观更新
async onQueryStarted(body, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
orderApi.util.updateQueryData('getOrders', {}, (draft) => {
draft.items.unshift({
id: 'temp-id',
...body,
status: 'PENDING',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
draft.total += 1;
})
);
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
},
}),
// 更新订单
updateOrder: builder.mutation<Order, { id: string; data: UpdateOrderRequest }>({
query: ({ id, data }) => ({
url: `/${id}`,
method: 'PUT',
body: data,
}),
invalidatesTags: (result, error, { id }) => [{ type: 'Order', id }],
}),
// 删除订单
deleteOrder: builder.mutation<void, string>({
query: (id) => ({
url: `/${id}`,
method: 'DELETE',
}),
invalidatesTags: (result, error, id) => [
{ type: 'Order', id },
{ type: 'Order', id: 'LIST' },
],
}),
// 批量操作
batchUpdateOrders: builder.mutation<
void,
{ ids: string[]; status: Order['status'] }
>({
query: ({ ids, status }) => ({
url: '/batch',
method: 'PATCH',
body: { ids, status },
}),
invalidatesTags: [{ type: 'Order', id: 'LIST' }],
}),
// 导出订单
exportOrders: builder.query<Blob, OrderListParams>({
query: (params) => ({
url: '/export',
params,
responseHandler: (response) => response.blob(),
}),
}),
}),
});
export const {
useGetOrdersQuery,
useGetOrderQuery,
useCreateOrderMutation,
useUpdateOrderMutation,
useDeleteOrderMutation,
useBatchUpdateOrdersMutation,
useLazyExportOrdersQuery,
} = orderApi;
2.4 状态管理配置
// packages/order-app/src/store/store.ts
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { orderApi } from '../features/orders/api/orderApi';
import orderReducer from '../features/orders/slices/orderSlice';
import uiReducer from '../slices/uiSlice';
export const store = configureStore({
reducer: {
[orderApi.reducerPath]: orderApi.reducer,
orders: orderReducer,
ui: uiReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST'],
},
}).concat(orderApi.middleware),
devTools: process.env.NODE_ENV !== 'production',
});
// 设置RTK Query监听器
setupListeners(store.dispatch);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
2.5 共享组件:智能表格组件
// packages/shared/components/Table/DataTable.tsx
import React, { useState, useMemo } from 'react';
import {
Table as AntTable,
TableProps,
PaginationProps,
Spin,
Empty,
Alert,
} from 'antd';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useInView } from 'react-intersection-observer';
import './DataTable.css';
export interface DataTableProps<T> extends TableProps<T> {
loading?: boolean;
error?: Error | null;
total?: number;
onPageChange?: (page: number, pageSize: number) => void;
onSortChange?: (sortBy: string, sortOrder: 'ascend' | 'descend') => void;
onFilterChange?: (filters: Record<string, any>) => void;
virtualScroll?: boolean;
rowHeight?: number;
}
export function DataTable<T extends object>({
loading = false,
error = null,
dataSource = [],
total = 0,
pagination = {},
onPageChange,
onSortChange,
onFilterChange,
virtualScroll = false,
rowHeight = 55,
className = '',
...tableProps
}: DataTableProps<T>) {
const [ref, inView] = useInView();
const [tableRef] = useState<HTMLDivElement | null>(null);
// 虚拟滚动配置
const rowVirtualizer = useVirtualizer({
count: dataSource.length,
getScrollElement: () => tableRef,
estimateSize: () => rowHeight,
overscan: 10,
});
// 处理分页变化
const handlePaginationChange: PaginationProps['onChange'] = (page, pageSize) => {
onPageChange?.(page, pageSize);
};
// 处理排序变化
const handleTableChange = (
pagination: any,
filters: any,
sorter: any
) => {
if (sorter && sorter.field && sorter.order) {
onSortChange?.(sorter.field, sorter.order);
}
if (filters) {
onFilterChange?.(filters);
}
};
// 合并分页配置
const mergedPagination = useMemo(() => {
if (pagination === false) return false;
return {
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number, range: [number, number]) =>
`第 ${range[0]}-${range[1]} 条,共 ${total} 条`,
pageSizeOptions: ['10', '20', '50', '100'],
...pagination,
total,
onChange: handlePaginationChange,
};
}, [pagination, total]);
// 渲染内容
const renderContent = () => {
if (error) {
return (
<div className="data-table-error">
<Alert
message="数据加载失败"
description={error.message}
type="error"
showIcon
/>
</div>
);
}
if (loading) {
return (
<div className="data-table-loading">
<Spin size="large" />
</div>
);
}
if (!dataSource || dataSource.length === 0) {
return (
<div className="data-table-empty">
<Empty description="暂无数据" />
</div>
);
}
if (virtualScroll && tableRef) {
const virtualRows = rowVirtualizer.getVirtualItems();
const paddingTop = virtualRows.length > 0 ? virtualRows[0].start : 0;
const paddingBottom =
virtualRows.length > 0
? rowVirtualizer.getTotalSize() - virtualRows[virtualRows.length - 1].end
: 0;
return (
<div
ref={tableRef}
className="virtual-scroll-container"
style={{ height: '600px', overflow: 'auto' }}
>
<div style={{ paddingTop, paddingBottom }}>
<AntTable<T>
{...tableProps}
dataSource={dataSource.slice(
virtualRows[0]?.index || 0,
virtualRows[virtualRows.length - 1]?.index || 0
)}
pagination={false}
components={{
body: {
row: ({ children, ...props }: any) => (
<tr {...props}>{children}</tr>
),
},
}}
/>
</div>
</div>
);
}
return (
<AntTable<T>
{...tableProps}
dataSource={dataSource}
pagination={mergedPagination}
onChange={handleTableChange}
/>
);
};
return (
<div className={`data-table ${className}`} ref={ref}>
{inView ? renderContent() : <div className="data-table-placeholder" />}
</div>
);
}
🚀 部署与集成配置
Docker部署配置
# packages/order-app/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
# 安装pnpm
RUN npm install -g pnpm
# 复制依赖文件
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# 复制源码
COPY . .
# 构建
RUN pnpm build
# 生产环境
FROM nginx:alpine
# 复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# Nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --quiet --tries=1 --spider http://localhost:80/health || exit 1
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Nginx配置
# packages/order-app/nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/javascript application/xml+rss
application/json;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 应用路由 - SPA支持
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# 健康检查端点
location /health {
access_log off;
return 200 "healthy\n";
}
# Module Federation远程入口
location ~ ^/remoteEntry\.js$ {
expires 1h;
add_header Cache-Control "public, max-age=3600";
}
}
GitHub Actions CI/CD
# .github/workflows/order-app-ci.yml
name: Order App CI/CD
on:
push:
branches: [ main, develop ]
paths:
- 'packages/order-app/**'
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}/order-app
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Lint
run: pnpm lint
- name: Type check
run: pnpm type-check
- name: Unit tests
run: pnpm test:unit
- name: Build
run: pnpm build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-output
path: packages/order-app/dist/
deploy:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-output
path: packages/order-app/dist/
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
- name: Build and push
uses: docker/build-push-action@v4
with:
context: ./packages/order-app
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/order-app \
order-app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
env:
KUBECONFIG: ${{ secrets.KUBECONFIG }}
📊 前端微服务优势总结
1. 开发效率提升
- 独立开发:每个团队可独立开发、测试、部署
- 快速迭代:单个微应用可独立发布,不影响其他应用
- 技术栈自由:不同微应用可使用最适合的技术栈
2. 性能优化
- 按需加载:用户只加载当前需要的功能
- 独立构建:每个应用有独立的构建优化
- 缓存策略:静态资源可独立缓存
3. 可维护性
- 代码隔离:问题定位更简单
- 复杂度分解:大应用拆分为小应用
- 团队自治:每个团队负责完整功能
4. 业务价值
- 渐进式升级:可逐步迁移老系统
- A/B测试:可针对特定功能进行A/B测试
- 灰度发布:可按微应用维度进行灰度
🎯 使用建议
何时使用前端微服务?
- 大型企业应用:多个团队协作开发
- 遗留系统迁移:逐步替换老系统
- 多产品线:不同产品共享基础组件
- 独立业务单元:业务功能相对独立
何时避免使用?
- 小型项目:过度设计会增加复杂度
- 团队规模小:微前端需要额外协作成本
- 性能敏感:额外的通信开销可能影响性能
🔧 快速开始命令
# 克隆模板
git clone https://github.com/your-org/microfrontend-template.git
cd microfrontend-template
# 安装依赖
pnpm install
# 启动开发服务器
pnpm dev
# 构建所有应用
pnpm build
# 运行测试
pnpm test
# 启动所有应用
pnpm start
这个前端微服务CRUD模板提供了完整的现代化前端架构方案,涵盖了从开发到部署的全流程。

浙公网安备 33010602011771号