智能图书馆管理系统开发实战系列(三):前端工程化实践 - Electron + React + TypeScript

前言

基于前面文章中验证的高保真原型,本文将详细介绍如何构建现代化的前端工程。我们的前端项目位于 code/frontend/ 目录,采用 Electron + React + TypeScript 技术栈,实现了从Web原型到桌面应用的完美转换。

前端技术架构设计

整体架构概览

我们的前端架构采用了现代化的工程化方案:

code/frontend/
├── src/                    # 源代码目录
│   ├── components/        # React组件
│   ├── App.tsx           # 主应用组件
│   └── main.tsx          # 应用入口
├── electron/              # Electron配置
│   ├── main.ts           # 主进程
│   └── preload.ts        # 预加载脚本
├── public/               # 静态资源
├── dll/                  # C++ DLL集成
├── package.json          # 项目配置
├── vite.config.ts        # Vite构建配置
└── tsconfig.json         # TypeScript配置

技术栈选择分析

Electron:桌面应用框架

选择理由:

  • 跨平台支持: 一次开发,多平台运行
  • Web技术栈: 利用现有的前端技术积累
  • 系统API访问: 可以调用操作系统原生功能
  • C++集成: 通过koffi无缝集成C++ DLL

code/frontend/package.json 中可以看到核心依赖:

{
  "name": "ILMS",
  "main": "dist-electron/main.js",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build && electron-builder",
    "preview": "vite preview",
    "test": "playwright test"
  },
  "dependencies": {
    "koffi": "^2.12.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "chart.js": "^4.5.0"
  },
  "devDependencies": {
    "electron": "^28.0.0",
    "electron-builder": "^24.9.1",
    "vite": "^5.0.8",
    "@playwright/test": "^1.54.1",
    "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17"
  }
}
React 18 + TypeScript:用户界面开发

技术优势:

  • 组件化开发: 提高代码复用性和维护性
  • 虚拟DOM: 优化大数据量场景下的渲染性能
  • 类型安全: TypeScript提供编译时类型检查
  • 生态丰富: 丰富的第三方组件库

code/frontend/tsconfig.json 配置可以看出严格的类型检查设置:

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["ESNext", "DOM"],
    "module": "ESNext",
    "strict": true,
    "noImplicitAny": true,
    "exactOptionalPropertyTypes": true
  }
}
Vite:现代化构建工具

性能优势:

  • 快速启动: 基于ESBuild的超快冷启动
  • 热重载: 毫秒级的模块热更新
  • 按需编译: 只编译当前页面需要的模块
  • 插件生态: 丰富的插件支持

项目工程化配置

1. Vite构建优化

code/frontend/vite.config.ts 的关键配置:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import electron from 'vite-plugin-electron'
import path from 'path'

export default defineConfig({
  build: {
    outDir: 'dist-electron',
    rollupOptions: {
      external: ['koffi']
    }
  },
  plugins: [
    react(),
    electron([
      {
        entry: 'electron/main.ts',
        vite: {
          build: {
            outDir: 'dist-electron',
            rollupOptions: {
              external: ['electron', 'koffi'],
            },
            minify: false,
          }
        }
      },
      {
        entry: 'electron/preload.ts',
        vite: {
          build: {
            outDir: 'dist-electron',
            rollupOptions: {
              external: ['koffi']
            },
            minify: false,
          }
        },
        onstart(options) {
          options.reload()
        },
      }
    ])
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  },
  optimizeDeps: {
    exclude: ['koffi']
  }
})

配置亮点:

  • 开发调试支持: 支持VSCode调试模式
  • 源码映射: 生产环境保留源码映射便于调试
  • 外部依赖: koffi作为外部依赖,避免打包冲突

2. TypeScript类型系统

为了确保类型安全,我们建立了完整的类型定义体系:

// src/types/index.ts
export interface Book {
  id: string;
  title: string;
  author: string;
  isbn: string;
  category: BookCategory;
  status: BookStatus;
  publishDate: Date;
  addedDate: Date;
}

export interface User {
  id: string;
  name: string;
  email: string;
  phone: string;
  role: UserRole;
  status: UserStatus;
  registeredDate: Date;
}

export interface BorrowRecord {
  id: string;
  bookId: string;
  userId: string;
  borrowDate: Date;
  dueDate: Date;
  returnDate?: Date;
  status: BorrowStatus;
}

export type BookCategory = 'literature' | 'science' | 'history' | 'art' | 'other';
export type BookStatus = 'available' | 'borrowed' | 'reserved' | 'maintenance';
export type UserRole = 'admin' | 'librarian' | 'reader';
export type UserStatus = 'active' | 'suspended' | 'inactive';
export type BorrowStatus = 'active' | 'overdue' | 'returned';

3. 代码质量保证

ESLint配置

code/frontend/eslint.config.js 提供了严格的代码规范:

import tseslint from 'typescript-eslint'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'

export default tseslint.config([
  {
    files: ['**/*.{ts,tsx}'],
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
      '@typescript-eslint/no-unused-vars': 'error',
      '@typescript-eslint/explicit-function-return-type': 'warn'
    }
  }
])
Tailwind CSS样式管理

实际项目使用的是 @tailwindcss/postcss7-compat 版本,code/frontend/tailwind.config.js 配置:

module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

同时在 package.json 中使用兼容版本:

"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17"

## 核心组件架构

### 1. 主应用组件结构

`src/App.tsx` 实现了应用的主体结构:

```typescript
import React, { useState } from 'react';
import { Dashboard } from './components/Dashboard';
import { BookManager } from './components/BookManager';
import { UserManager } from './components/UserManager';
import { BorrowManager } from './components/BorrowManager';
import { StatisticsReport } from './components/StatisticsReport';

type ActiveView = 'dashboard' | 'books' | 'users' | 'borrow' | 'statistics';

const App: React.FC = () => {
  const [activeView, setActiveView] = useState<ActiveView>('dashboard');

  const renderActiveComponent = (): JSX.Element => {
    switch (activeView) {
      case 'dashboard':
        return <Dashboard />;
      case 'books':
        return <BookManager />;
      case 'users':
        return <UserManager />;
      case 'borrow':
        return <BorrowManager />;
      case 'statistics':
        return <StatisticsReport />;
      default:
        return <Dashboard />;
    }
  };

  return (
    <div className="flex h-screen bg-gray-100">
      {/* 侧边栏导航 */}
      <nav className="w-64 bg-white shadow-sm">
        <div className="p-4">
          <h1 className="text-xl font-bold text-gray-800">图书馆管理系统</h1>
        </div>
        <ul className="mt-6">
          {navigationItems.map((item) => (
            <li key={item.id}>
              <button
                onClick={() => setActiveView(item.id)}
                className={`w-full text-left px-4 py-2 hover:bg-gray-50 ${
                  activeView === item.id ? 'bg-blue-50 text-blue-600' : 'text-gray-700'
                }`}
              >
                <i className={`${item.icon} mr-3`}></i>
                {item.label}
              </button>
            </li>
          ))}
        </ul>
      </nav>

      {/* 主内容区域 */}
      <main className="flex-1 overflow-auto">
        {renderActiveComponent()}
      </main>
    </div>
  );

**[截图占位]**: React应用运行截图,展示侧边栏导航和主内容区域
};

export default App;

设计特点:

  • 状态管理: 使用React Hook管理当前视图状态
  • 组件懒加载: 按需渲染当前激活的组件
  • 响应式布局: 使用Tailwind CSS实现响应式设计

2. 功能组件设计模式

Dashboard组件示例
// src/components/Dashboard.tsx
import React, { useEffect, useState } from 'react';
import { useBackendAPI } from '../hooks/useBackendAPI';

interface DashboardStats {
  totalBooks: number;
  totalUsers: number;
  activeBorrows: number;
  overdueBooks: number;
}

export const Dashboard: React.FC = () => {
  const [stats, setStats] = useState<DashboardStats | null>(null);
  const [loading, setLoading] = useState(true);
  const { getDashboardStats } = useBackendAPI();

  useEffect(() => {
    loadDashboardData();
  }, []);

  const loadDashboardData = async (): Promise<void> => {
    try {
      setLoading(true);
      const data = await getDashboardStats();
      setStats(data);
    } catch (error) {
      console.error('Failed to load dashboard data:', error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return <div className="flex justify-center items-center h-full">
      <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
    </div>;
  }

  return (
    <div className="p-6">
      <h2 className="text-2xl font-bold text-gray-800 mb-6">仪表板概览</h2>
      
      {/* 统计卡片 */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
        <StatCard
          title="图书总数"
          value={stats?.totalBooks || 0}
          icon="fas fa-book"
          color="blue"
        />
        <StatCard
          title="用户总数"
          value={stats?.totalUsers || 0}
          icon="fas fa-users"
          color="green"
        />
        <StatCard
          title="借阅中"
          value={stats?.activeBorrows || 0}
          icon="fas fa-hand-holding"
          color="yellow"
        />
        <StatCard
          title="逾期图书"
          value={stats?.overdueBooks || 0}
          icon="fas fa-exclamation-triangle"
          color="red"
        />
      </div>

      {/* 图表区域 */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <BorrowTrendChart />
        <CategoryDistributionChart />
      </div>
    </div>
  );
};

组件设计原则:

  • 单一职责: 每个组件只负责一个功能领域
  • 状态管理: 使用React Hooks管理组件状态
  • 错误处理: 完善的loading和error状态处理
  • 类型安全: 完整的TypeScript类型定义

3. 自定义Hook封装

useBackendAPI Hook
// src/hooks/useBackendAPI.ts
import { useCallback } from 'react';
import { Book, User, BorrowRecord } from '../types';

interface BackendAPI {
  // 图书管理
  getBookList: () => Promise<Book[]>;
  addBook: (book: Omit<Book, 'id'>) => Promise<boolean>;
  updateBook: (id: string, book: Partial<Book>) => Promise<boolean>;
  deleteBook: (id: string) => Promise<boolean>;
  
  // 用户管理
  getUserList: () => Promise<User[]>;
  addUser: (user: Omit<User, 'id'>) => Promise<boolean>;
  updateUser: (id: string, user: Partial<User>) => Promise<boolean>;
  
  // 借阅管理
  getBorrowRecords: () => Promise<BorrowRecord[]>;
  borrowBook: (bookId: string, userId: string) => Promise<boolean>;
  returnBook: (recordId: string) => Promise<boolean>;
  
  // 仪表板数据
  getDashboardStats: () => Promise<DashboardStats>;
}

export const useBackendAPI = (): BackendAPI => {
  // 通过koffi调用C++ DLL的实现
  const callBackend = useCallback(async (functionName: string, params?: any) => {
    try {
      // 这里会在下一篇文章中详细介绍koffi集成
      const result = await window.electronAPI.callBackend(functionName, params);
      return result;
    } catch (error) {
      console.error(`Backend call failed: ${functionName}`, error);
      throw error;
    }
  }, []);

  return {
    getBookList: () => callBackend('GetBookList'),
    addBook: (book) => callBackend('AddBook', book),
    updateBook: (id, book) => callBackend('UpdateBook', { id, ...book }),
    deleteBook: (id) => callBackend('DeleteBook', { id }),
    
    getUserList: () => callBackend('GetUserList'),
    addUser: (user) => callBackend('AddUser', user),
    updateUser: (id, user) => callBackend('UpdateUser', { id, ...user }),
    
    getBorrowRecords: () => callBackend('GetBorrowRecords'),
    borrowBook: (bookId, userId) => callBackend('BorrowBook', { bookId, userId }),
    returnBook: (recordId) => callBackend('ReturnBook', { recordId }),
    
    getDashboardStats: () => callBackend('GetDashboardStats'),
  };
};

Electron集成实践

1. 主进程配置

electron/main.ts 配置了应用的主进程:

import { app, BrowserWindow, ipcMain } from 'electron';
import path from 'path';

const createWindow = (): void => {
  const mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    minWidth: 1000,
    minHeight: 600,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      enableRemoteModule: false,
      preload: path.join(__dirname, 'preload.js')
    },
    titleBarStyle: 'default',
    icon: path.join(__dirname, '../assets/icon.png')
  });

  // 开发环境加载Vite开发服务器
  if (process.env.NODE_ENV === 'development') {
    mainWindow.loadURL('http://localhost:5173');
    mainWindow.webContents.openDevTools();
  } else {
    mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
  }
};

app.whenReady().then(createWindow);

// 处理应用激活
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

// 处理窗口关闭
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// IPC通信处理
ipcMain.handle('call-backend', async (event, functionName, params) => {
  // 在下一篇文章中详细介绍C++ DLL调用实现
  try {
    return await callCppDLL(functionName, params);
  } catch (error) {
    throw error;
  }
});

2. 预加载脚本

electron/preload.ts 提供了安全的API接口:

import { contextBridge, ipcRenderer } from 'electron';

// 向渲染进程暴露安全的API
contextBridge.exposeInMainWorld('electronAPI', {
  callBackend: (functionName: string, params?: any) => 
    ipcRenderer.invoke('call-backend', functionName, params),
  
  // 文件操作API
  selectFile: () => ipcRenderer.invoke('select-file'),
  saveFile: (data: any, filename: string) => 
    ipcRenderer.invoke('save-file', data, filename),
  
  // 应用控制API
  minimize: () => ipcRenderer.invoke('minimize-window'),
  maximize: () => ipcRenderer.invoke('maximize-window'),
  close: () => ipcRenderer.invoke('close-window'),
});

// 类型定义
declare global {
  interface Window {
    electronAPI: {
      callBackend: (functionName: string, params?: any) => Promise<any>;
      selectFile: () => Promise<string>;
      saveFile: (data: any, filename: string) => Promise<boolean>;
      minimize: () => Promise<void>;
      maximize: () => Promise<void>;
      close: () => Promise<void>;
    };
  }
}

性能优化实践

1. 组件级别优化

// 使用React.memo优化组件重渲染
export const BookListItem = React.memo<BookListItemProps>(({ book, onEdit, onDelete }) => {
  return (
    <tr className="hover:bg-gray-50">
      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
        {book.title}
      </td>
      <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
        {book.author}
      </td>
      <td className="px-6 py-4 whitespace-nowrap">
        <StatusBadge status={book.status} />
      </td>
      <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
        <button onClick={() => onEdit(book)} className="text-blue-600 hover:text-blue-900 mr-2">
          编辑
        </button>
        <button onClick={() => onDelete(book.id)} className="text-red-600 hover:text-red-900">
          删除
        </button>
      </td>
    </tr>
  );
});

// 使用useCallback优化事件处理器
const BookManager: React.FC = () => {
  const [books, setBooks] = useState<Book[]>([]);
  
  const handleEditBook = useCallback((book: Book) => {
    // 编辑逻辑
  }, []);
  
  const handleDeleteBook = useCallback((bookId: string) => {
    // 删除逻辑
  }, []);
  
  return (
    <div>
      {books.map(book => (
        <BookListItem
          key={book.id}
          book={book}
          onEdit={handleEditBook}
          onDelete={handleDeleteBook}
        />
      ))}
    </div>
  );
};

2. 虚拟滚动优化

对于大数据量的表格展示,我们实现了虚拟滚动:

import { FixedSizeList as List } from 'react-window';

const VirtualizedBookList: React.FC<{ books: Book[] }> = ({ books }) => {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      <BookListItem book={books[index]} />
    </div>
  );

  return (
    <List
      height={600}
      itemCount={books.length}
      itemSize={60}
      width="100%"
    >
      {Row}
    </List>
  );
};

3. 数据缓存策略

// 使用React Query进行数据缓存
import { useQuery, useMutation, useQueryClient } from 'react-query';

export const useBooks = () => {
  const { getBookList, addBook, updateBook, deleteBook } = useBackendAPI();
  
  return useQuery(['books'], getBookList, {
    staleTime: 5 * 60 * 1000, // 5分钟内数据视为新鲜
    cacheTime: 10 * 60 * 1000, // 10分钟缓存时间
  });
};

export const useAddBook = () => {
  const queryClient = useQueryClient();
  const { addBook } = useBackendAPI();
  
  return useMutation(addBook, {
    onSuccess: () => {
      queryClient.invalidateQueries(['books']);
    },
  });
};

开发工作流

1. 开发环境启动

# code/frontend/ 目录下
npm install          # 安装依赖
npm run dev         # 启动开发服务器
npm run electron    # 启动Electron应用

[截图占位]: 开发环境启动过程截图,展示终端命令执行和应用启动

2. 构建流程

npm run build       # 构建生产版本
npm run preview     # 预览构建结果
npm run dist        # 打包Electron应用

3. 调试配置

在VSCode中配置调试:

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Electron Main",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}/code/frontend",
      "runtimeExecutable": "${workspaceFolder}/code/frontend/node_modules/.bin/electron",
      "args": ["--inspect=5858", "."],
      "env": {
        "NODE_ENV": "development"
      }
    }
  ]
}

下期预告

在下一篇文章中,我们将深入介绍后端C++ DLL的开发实践,包括模块化设计、CMake构建配置,以及如何实现高性能的业务逻辑处理。

总结

本文详细介绍了基于Electron + React + TypeScript的前端工程化实践,包括:

  1. 现代化技术栈: 选择合适的技术组合
  2. 工程化配置: 完善的构建和开发工具链
  3. 组件化架构: 可维护的代码结构
  4. 性能优化: 多层次的性能优化策略
  5. 开发工作流: 高效的开发和调试流程

通过这些实践,我们成功地将高保真原型转化为了高质量的桌面应用前端,为后续的前后端集成奠定了坚实的基础。

系列文章目录

  1. 项目架构设计与技术选型
  2. 高保真原型设计与用户体验测试
  3. 前端工程化实践:Electron + React + TypeScript
  4. 后端C++ DLL开发与模块化设计
  5. 前后端集成:koffi调用与接口设计
  6. Google Test单元测试实践
  7. CMake构建系统与持续集成
  8. 性能优化与部署发布

通过这个系列文章,您将学习到现代桌面应用开发的完整流程和最佳实践。

程序及源码附件下载

程序及源码

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值