前端微服务架构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测试
  • 灰度发布:可按微应用维度进行灰度

🎯 使用建议

何时使用前端微服务?

  1. 大型企业应用:多个团队协作开发
  2. 遗留系统迁移:逐步替换老系统
  3. 多产品线:不同产品共享基础组件
  4. 独立业务单元:业务功能相对独立

何时避免使用?

  1. 小型项目:过度设计会增加复杂度
  2. 团队规模小:微前端需要额外协作成本
  3. 性能敏感:额外的通信开销可能影响性能

🔧 快速开始命令

# 克隆模板
git clone https://github.com/your-org/microfrontend-template.git
cd microfrontend-template

# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev

# 构建所有应用
pnpm build

# 运行测试
pnpm test

# 启动所有应用
pnpm start

这个前端微服务CRUD模板提供了完整的现代化前端架构方案,涵盖了从开发到部署的全流程。

posted @ 2025-12-18 15:05  XiaoZhengTou  阅读(25)  评论(0)    收藏  举报