Skip to content

Pxcharts Pro 技术文档

体验地址:pro版多维表格编辑器

📋 项目概述

Pxcharts Pro 是一款基于 React 18+ 构建的现代化多维表格管理系统。该项目提供了强大的数据管理、可视化展示和交互功能,支持多种视图模式(表格视图、图表视图、看板视图等)。

🎯 核心特性

  • 多表格管理: 支持创建和管理多个独立的数据表格
  • 多种视图模式: 表格视图、图表视图、看板视图
  • 高性能渲染: 支持虚拟化滚动,优化大数据量渲染性能
  • 丰富的交互功能: 拖拽排序、筛选、搜索、分组等
  • 数据导入导出: 支持多种格式的数据导入和导出
  • 自定义配置: 灵活的字段配置、颜色规则、主题设置

🏗️ 技术架构

整体架构图

┌─────────────────────────────────────────────────────────────┐
│                    Presentation Layer                       │
├─────────────────────────────────────────────────────────────┤
│  Table View  │  Chart View  │  Kanban View  │  Other Views │
├─────────────────────────────────────────────────────────────┤
│                    Component Layer                          │
├─────────────────────────────────────────────────────────────┤
│  Table Toolbar  │  Dialogs  │  UI Components  │  Hooks      │
├─────────────────────────────────────────────────────────────┤
│                    State Management Layer                   │
├─────────────────────────────────────────────────────────────┤
│              MultiTableProvider (Context)                  │
├─────────────────────────────────────────────────────────────┤
│                    Data Layer                              │
├─────────────────────────────────────────────────────────────┤
│  Local Storage  │  Mock Data  │  Future: API Integration  │
└─────────────────────────────────────────────────────────────┘

技术栈详解

核心框架

  • Next.js 15.2.4: React 全栈框架,提供 SSR/SSG 支持
  • React 19: 最新版本的 React,支持并发特性和新的 Hooks
  • TypeScript 5: 类型安全的 JavaScript 超集

UI 组件库

  • Radix UI: 无样式的可访问组件库,提供基础组件
  • Tailwind CSS 3.4.17: 实用优先的 CSS 框架
  • Shadcn/ui: 基于 Radix UI 的现代化组件库
  • Lucide React: 精美的图标库

数据管理

  • React Hook Form 7.54.1: 高性能表单状态管理
  • Zod 3.24.1: TypeScript 优先的模式验证
  • Date-fns 4.1.0: 现代化的日期处理库

图表和可视化

  • Recharts: 基于 React 的图表库
  • React Window: 虚拟化滚动组件,优化大数据量渲染

其他工具

  • Sonner: 现代化的 Toast 通知
  • CMDK: 命令面板组件
  • React Resizable Panels: 可调整大小的面板
  • Embla Carousel: 轮播组件

🔧 核心架构设计

1. 状态管理架构

项目采用 React Context + Hooks 的状态管理模式:

typescript
// 核心状态接口
interface MultiTableContextType {
  tables: TableConfig[]           // 所有表格配置
  currentTableId: string         // 当前选中的表格ID
  currentTable: TableConfig | null  // 当前表格配置
  displayData: TaskData[]        // 当前显示的数据(包含过滤)
  // ... 操作方法
}

优势:

  • 避免 Props Drilling
  • 状态集中管理,易于调试
  • 支持多表格切换
  • 响应式状态更新

2. 组件分层架构

App (page.tsx)
├── MultiTableProvider (Context Provider)
├── Header
│   └── TableManager (表格选择器)
├── ViewTabs (视图切换)
├── TableToolbar (工具栏)
└── Main Content
    ├── CrossGroupTableView (表格视图)
    ├── FixedChartView (图表视图)
    └── PerformanceMonitor (性能监控)

3. 数据流架构

用户操作 → 组件事件 → Context 方法 → 状态更新 → 组件重渲染

数据变更 → 触发相关计算 → 更新 displayData → 视图更新

📊 数据模型设计

核心数据结构

TaskData 接口

typescript
interface TaskData {
  id: string                    // 唯一标识
  title: string                // 任务标题
  description: string          // 任务描述
  status: "进行中" | "已完成" | "待开始"  // 任务状态
  priority: "重要紧急" | "紧急不重要" | "重要不紧急" | "不重要不紧急"  // 优先级
  assignee: string             // 执行人
  startDate: string            // 开始日期
  dueDate: string              // 截止日期
  completedDate?: string       // 完成日期(可选)
  isOverdue: boolean           // 是否延期
  progress: string             // 进度描述
  [key: string]: any           // 支持自定义字段
}

TableConfig 接口

typescript
interface TableConfig {
  id: string                   // 表格唯一标识
  name: string                 // 表格名称
  description?: string         // 表格描述
  columns: ColumnConfig[]      // 列配置
  data: TaskData[]            // 表格数据
  groupBy?: string            // 分组字段
  sortBy?: string             // 排序字段
  filters: Record<string, any> // 过滤条件
  rowHeight?: number          // 行高
  colorRules?: ColorRule[]    // 颜色规则
  filteredData?: TaskData[]   // 过滤后的数据
  enableVirtualization?: boolean  // 是否启用虚拟化
  enableSearch?: boolean      // 是否启用搜索
  enableFilters?: boolean     // 是否启用过滤
  enableSort?: boolean        // 是否启用排序
  enableExport?: boolean      // 是否启用导出
  theme?: string              // 主题设置
}

ColumnConfig 接口

typescript
interface ColumnConfig {
  id: string                   // 列标识
  title: string               // 列标题
  width: number               // 列宽度
  type: "text" | "select" | "date" | "user" | "progress" | "checkbox"  // 列类型
  editable: boolean           // 是否可编辑
  visible: boolean            // 是否可见
  options?: string[]          // 选项(用于select类型)
}

数据关系模型

TableConfig (表格配置)
├── columns: ColumnConfig[] (列配置)
├── data: TaskData[] (数据行)
├── filters (过滤条件)
├── colorRules (颜色规则)
└── settings (表格设置)

TaskData (数据行)
├── 基础字段 (id, title, description)
├── 状态字段 (status, priority)
├── 时间字段 (startDate, dueDate, completedDate)
├── 人员字段 (assignee)
└── 自定义字段 (通过 [key: string]: any 支持)

🚀 性能优化策略

1. 虚拟化滚动

使用 react-window 实现虚拟化滚动,只渲染可视区域的数据:

typescript
// 虚拟化表格组件
export function VirtualizedTable({ data, columns, height = 600, itemHeight = 50 }) {
  return (
    <List height={height} itemCount={data.length} itemSize={itemHeight} width="100%">
      {Row}
    </List>
  )
}

性能提升:

  • 大数据量下渲染性能提升 10x+
  • 内存占用减少 80%+
  • 滚动流畅度显著提升

2. 计算优化

使用 useMemo 优化计算密集型操作:

typescript
const groupedData = useMemo(() => {
  if (!currentTable?.data) return {}
  
  const groups: Record<string, TaskData[]> = {}
  currentTable.data.forEach((task) => {
    const groupKey = getGroupKey(task)
    if (!groups[groupKey]) {
      groups[groupKey] = []
    }
    groups[groupKey].push(task)
  })
  
  return groups
}, [currentTable?.data, currentTable?.groupBy])

3. 状态更新优化

批量状态更新,减少不必要的重渲染:

typescript
const updateTable = (tableId: string, updates: Partial<TableConfig>) => {
  setTables((prev) => prev.map((table) => 
    table.id === tableId ? { ...table, ...updates } : table
  ))
}

🔌 扩展性设计

1. 插件化架构

通过接口定义和组件组合实现功能扩展:

typescript
// 视图扩展接口
interface ViewExtension {
  id: string
  name: string
  component: React.ComponentType
  icon: React.ComponentType
  enabled: boolean
}

// 工具栏扩展接口
interface ToolbarExtension {
  id: string
  component: React.ComponentType
  position: 'left' | 'right'
  order: number
}

2. 配置驱动

表格行为通过配置驱动,支持运行时修改:

typescript
// 动态启用/禁用功能
const tableConfig: TableConfig = {
  enableVirtualization: true,    // 启用虚拟化
  enableSearch: true,            // 启用搜索
  enableFilters: true,           // 启用过滤
  enableSort: true,              // 启用排序
  enableExport: true,            // 启用导出
}

3. 主题系统

支持多主题切换和自定义主题:

typescript
// 主题配置
interface ThemeConfig {
  name: string
  colors: {
    primary: string
    secondary: string
    background: string
    foreground: string
  }
  spacing: {
    small: string
    medium: string
    large: string
  }
}

🚀 快速开始

环境要求

  • Node.js 18.0 或更高版本
  • pnpm 8.0 或更高版本(推荐)或 npm 9.0+

安装依赖

bash
# 使用 pnpm(推荐)
pnpm install

# 或使用 npm
npm install

# 或使用 yarn
yarn install

开发环境启动

bash
# 启动开发服务器
pnpm dev

# 或使用 npm
npm run dev

开发服务器将在 http://localhost:3000 启动。

构建生产版本

bash
# 构建生产版本
pnpm build

# 启动生产服务器
pnpm start

二次开发介绍

核心文件说明

1. multi-table-provider.tsx - 状态管理核心

这是整个应用的状态管理中心,定义了:

  • 数据模型接口
  • 状态管理逻辑
  • 数据操作方法
  • 上下文提供者

2. page.tsx - 主页面

应用的主入口,负责:

  • 布局结构
  • 视图切换
  • 组件组合

3. table-toolbar.tsx - 工具栏

提供表格操作的主要工具,包括:

  • 搜索功能
  • 过滤设置
  • 排序配置
  • 数据导入导出

🔧 组件开发指南

组件开发规范

1. 文件命名规范

typescript
// 使用 kebab-case 命名
// ✅ 正确
enhanced-table-view.tsx
add-field-dialog.tsx
color-dialog.tsx

// ❌ 错误
EnhancedTableView.tsx
addFieldDialog.tsx
ColorDialog.tsx

2. 组件导出规范

typescript
// 使用命名导出
export function EnhancedTableView() {
  // 组件实现
}

// 避免默认导出
// ❌ 不推荐
export default function EnhancedTableView() {
  // 组件实现
}

3. TypeScript 类型定义

typescript
// 定义 Props 接口
interface EnhancedTableViewProps {
  className?: string
  onDataChange?: (data: TaskData[]) => void
  enableVirtualization?: boolean
}

// 使用 Props 接口
export function EnhancedTableView({ 
  className, 
  onDataChange, 
  enableVirtualization = true 
}: EnhancedTableViewProps) {
  // 组件实现
}

创建新组件

1. 基础组件模板

typescript
"use client"

import { useState, useMemo } from "react"
import { useMultiTable } from "@/components/multi-table-provider"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"

interface CustomViewProps {
  className?: string
  title?: string
}

export function CustomView({ className, title = "自定义视图" }: CustomViewProps) {
  const { currentTable, displayData } = useMultiTable()
  const [localState, setLocalState] = useState("")

  // 计算派生状态
  const computedData = useMemo(() => {
    if (!displayData) return []
    // 自定义计算逻辑
    return displayData.filter(item => /* 过滤条件 */)
  }, [displayData])

  // 事件处理函数
  const handleAction = () => {
    // 处理逻辑
  }

  if (!currentTable) {
    return <div>请选择表格</div>
  }

  return (
    <div className={className}>
      <Card>
        <CardHeader>
          <CardTitle>{title}</CardTitle>
        </CardHeader>
        <CardContent>
          {/* 组件内容 */}
          <div className="space-y-4">
            {computedData.map((item) => (
              <div key={item.id} className="p-4 border rounded">
                {/* 项目内容 */}
              </div>
            ))}
          </div>
          
          <Button onClick={handleAction} className="mt-4">
            执行操作
          </Button>
        </CardContent>
      </Card>
    </div>
  )
}

2. 对话框组件模板

typescript
"use client"

import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

interface CustomDialogProps {
  open: boolean
  onOpenChange: (open: boolean) => void
  onConfirm?: (data: any) => void
}

export function CustomDialog({ open, onOpenChange, onConfirm }: CustomDialogProps) {
  const [formData, setFormData] = useState({
    name: "",
    description: ""
  })

  const handleSubmit = () => {
    if (onConfirm) {
      onConfirm(formData)
    }
    onOpenChange(false)
    // 重置表单
    setFormData({ name: "", description: "" })
  }

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>自定义对话框</DialogTitle>
        </DialogHeader>
        
        <div className="space-y-4">
          <div>
            <Label htmlFor="name">名称</Label>
            <Input
              id="name"
              value={formData.name}
              onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
              placeholder="请输入名称"
            />
          </div>
          
          <div>
            <Label htmlFor="description">描述</Label>
            <Input
              id="description"
              value={formData.description}
              onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
              placeholder="请输入描述"
            />
          </div>
          
          <div className="flex justify-end space-x-2">
            <Button variant="outline" onClick={() => onOpenChange(false)}>
              取消
            </Button>
            <Button onClick={handleSubmit}>
              确认
            </Button>
          </div>
        </div>
      </DialogContent>
    </Dialog>
  )
}

扩展现有组件

1. 添加新的表格列类型

typescript
// 在 multi-table-provider.tsx 中扩展 ColumnConfig 接口
export interface ColumnConfig {
  id: string
  title: string
  width: number
  type: "text" | "select" | "date" | "user" | "progress" | "checkbox" | "rating" | "color" // 新增类型
  editable: boolean
  visible: boolean
  options?: string[]
  // 新增配置选项
  minValue?: number
  maxValue?: number
  step?: number
  colors?: string[]
}

// 在 enhanced-table-view.tsx 中添加新的渲染逻辑
const renderCell = (task: TaskData, column: ColumnConfig) => {
  switch (column.type) {
    case "rating":
      return (
        <div className="flex space-x-1">
          {[1, 2, 3, 4, 5].map((star) => (
            <button
              key={star}
              onClick={() => handleRatingChange(task.id, star)}
              className={`text-lg ${star <= (task[column.id] || 0) ? 'text-yellow-400' : 'text-gray-300'}`}
            >

            </button>
          ))}
        </div>
      )
    
    case "color":
      return (
        <div className="flex space-x-2">
          {column.colors?.map((color) => (
            <button
              key={color}
              onClick={() => handleColorChange(task.id, color)}
              className={`w-6 h-6 rounded-full border-2 ${
                task[column.id] === color ? 'border-gray-800' : 'border-gray-300'
              }`}
              style={{ backgroundColor: color }}
            />
          ))}
        </div>
      )
    
    default:
      return <span>{task[column.id]}</span>
  }
}

2. 添加新的视图模式

typescript
// 1. 创建新的视图组件
export function TimelineView() {
  const { currentTable, displayData } = useMultiTable()
  
  const timelineData = useMemo(() => {
    if (!displayData) return []
    
    return displayData
      .sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime())
      .map((task, index) => ({
        ...task,
        position: index % 2 === 0 ? 'left' : 'right'
      }))
  }, [displayData])

  return (
    <div className="relative">
      {/* 时间线实现 */}
      <div className="absolute left-1/2 transform -translate-x-1/2 w-1 bg-gray-300 h-full" />
      
      {timelineData.map((task) => (
        <div
          key={task.id}
          className={`relative mb-8 ${
            task.position === 'left' ? 'mr-auto pr-8' : 'ml-auto pl-8'
          }`}
          style={{ width: '45%' }}
        >
          <div className="bg-white p-4 rounded-lg shadow border">
            <h3 className="font-semibold">{task.title}</h3>
            <p className="text-sm text-gray-600">{task.description}</p>
            <div className="text-xs text-gray-500 mt-2">
              {task.startDate} - {task.dueDate}
            </div>
          </div>
        </div>
      ))}
    </div>
  )
}

// 2. 在 view-tabs.tsx 中添加新的视图选项
export function ViewTabs({ currentView, onViewChange }: ViewTabsProps) {
  const tabs = [
    { id: "table" as const, label: "任务管理表", icon: Table },
    { id: "chart" as const, label: "人员任务分配看板", icon: BarChart3 },
    { id: "timeline" as const, label: "时间线视图", icon: Clock }, // 新增
  ]

  return (
    <div className="bg-white border-b border-gray-200 px-6">
      <div className="flex items-center space-x-1">
        {tabs.map((tab) => {
          const Icon = tab.icon
          return (
            <Button
              key={tab.id}
              variant={currentView === tab.id ? "default" : "ghost"}
              size="sm"
              onClick={() => onViewChange(tab.id)}
              className="flex items-center space-x-2 px-4 py-2"
            >
              <Icon className="h-4 w-4" />
              <span>{tab.label}</span>
            </Button>
          )
        })}
      </div>
    </div>
  )
}

// 3. 在主页面中添加新的视图渲染
export default function HomePage() {
  const [currentView, setCurrentView] = useState<"table" | "chart" | "timeline">("table")

  return (
    <MultiTableProvider>
      {/* ... 其他内容 ... */}
      
      <main className="p-6">
        <PerformanceMonitor />
        {currentView === "table" && <CrossGroupTableView />}
        {currentView === "chart" && <FixedChartView />}
        {currentView === "timeline" && <TimelineView />} {/* 新增 */}
      </main>
    </MultiTableProvider>
  )
}

🎨 样式定制指南

Tailwind CSS 配置

1. 扩展颜色系统

typescript
// tailwind.config.ts
import type { Config } from "tailwindcss"

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      colors: {
        // 自定义品牌色
        brand: {
          50: '#f0f9ff',
          100: '#e0f2fe',
          500: '#0ea5e9',
          600: '#0284c7',
          700: '#0369a1',
          900: '#0c4a6e',
        },
        // 自定义状态色
        status: {
          success: '#10b981',
          warning: '#f59e0b',
          error: '#ef4444',
          info: '#3b82f6',
        }
      },
      spacing: {
        // 自定义间距
        '18': '4.5rem',
        '88': '22rem',
        '128': '32rem',
      },
      animation: {
        // 自定义动画
        'fade-in': 'fadeIn 0.5s ease-in-out',
        'slide-up': 'slideUp 0.3s ease-out',
        'bounce-gentle': 'bounceGentle 2s infinite',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        slideUp: {
          '0%': { transform: 'translateY(20px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' },
        },
        bounceGentle: {
          '0%, 100%': { transform: 'translateY(0)' },
          '50%': { transform: 'translateY(-10px)' },
        },
      }
    },
  },
  plugins: [require("tailwindcss-animate")],
}

export default config

2. 自定义组件样式

typescript
// 创建自定义样式组件
export function CustomButton({ 
  children, 
  variant = "default", 
  size = "md",
  className = "",
  ...props 
}: CustomButtonProps) {
  const baseClasses = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background"
  
  const variantClasses = {
    default: "bg-brand-600 text-white hover:bg-brand-700",
    secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200",
    outline: "border border-gray-300 bg-transparent hover:bg-gray-50",
    ghost: "hover:bg-gray-100 hover:text-gray-900",
    destructive: "bg-status-error text-white hover:bg-red-600",
  }
  
  const sizeClasses = {
    sm: "h-8 px-3 text-sm",
    md: "h-10 px-4 py-2",
    lg: "h-12 px-8 text-lg",
  }
  
  const classes = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`
  
  return (
    <button className={classes} {...props}>
      {children}
    </button>
  )
}

CSS 变量系统

1. 定义 CSS 变量

css
/* globals.css */
:root {
  /* 基础颜色 */
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --card: 0 0% 100%;
  --card-foreground: 222.2 84% 4.9%;
  
  /* 品牌颜色 */
  --brand-primary: 199 89% 48%;
  --brand-secondary: 199 89% 38%;
  
  /* 状态颜色 */
  --success: 142 76% 36%;
  --warning: 38 92% 50%;
  --error: 0 84% 60%;
  --info: 217 91% 60%;
  
  /* 间距 */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  
  /* 圆角 */
  --radius-sm: 0.25rem;
  --radius-md: 0.375rem;
  --radius-lg: 0.5rem;
  --radius-xl: 0.75rem;
}

/* 暗色主题 */
[data-theme="dark"] {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  --card: 222.2 84% 4.9%;
  --card-foreground: 210 40% 98%;
}

2. 使用 CSS 变量

typescript
// 在组件中使用 CSS 变量
export function ThemedComponent() {
  return (
    <div 
      className="p-4 rounded-lg"
      style={{
        backgroundColor: 'hsl(var(--card))',
        color: 'hsl(var(--card-foreground))',
        borderRadius: 'var(--radius-lg)',
      }}
    >
      <h2 className="text-lg font-semibold mb-2">主题化组件</h2>
      <p className="text-sm opacity-80">支持动态主题切换</p>
    </div>
  )
}

🔌 插件系统开发

插件接口定义

typescript
// 定义插件接口
export interface Plugin {
  id: string
  name: string
  version: string
  description: string
  author: string
  enabled: boolean
  
  // 生命周期钩子
  onInstall?: () => void
  onUninstall?: () => void
  onEnable?: () => void
  onDisable?: () => void
  
  // 功能扩展
  views?: ViewExtension[]
  toolbars?: ToolbarExtension[]
  columns?: ColumnExtension[]
  actions?: ActionExtension[]
}

// 视图扩展接口
export interface ViewExtension {
  id: string
  name: string
  component: React.ComponentType
  icon: React.ComponentType
  enabled: boolean
  order: number
}

// 工具栏扩展接口
export interface ToolbarExtension {
  id: string
  component: React.ComponentType
  position: 'left' | 'right'
  order: number
  enabled: boolean
}

// 列扩展接口
export interface ColumnExtension {
  id: string
  type: string
  renderer: (value: any, row: TaskData, column: ColumnConfig) => React.ReactNode
  editor?: (value: any, onChange: (value: any) => void) => React.ReactNode
  validator?: (value: any) => string | null
}

// 操作扩展接口
export interface ActionExtension {
  id: string
  name: string
  icon: React.ComponentType
  action: (data: any) => void
  enabled: boolean
  visible: (data: any) => boolean
}

插件管理器

typescript
// 插件管理器
export class PluginManager {
  private plugins: Map<string, Plugin> = new Map()
  private extensions: {
    views: ViewExtension[]
    toolbars: ToolbarExtension[]
    columns: ColumnExtension[]
    actions: ActionExtension[]
  } = {
    views: [],
    toolbars: [],
    columns: [],
    actions: []
  }

  // 注册插件
  registerPlugin(plugin: Plugin): void {
    if (this.plugins.has(plugin.id)) {
      throw new Error(`Plugin ${plugin.id} already registered`)
    }

    this.plugins.set(plugin.id, plugin)
    
    // 注册扩展
    if (plugin.views) {
      this.extensions.views.push(...plugin.views)
    }
    if (plugin.toolbars) {
      this.extensions.toolbars.push(...plugin.toolbars)
    }
    if (plugin.columns) {
      this.extensions.columns.push(...plugin.columns)
    }
    if (plugin.actions) {
      this.extensions.actions.push(...plugin.actions)
    }

    // 调用安装钩子
    if (plugin.onInstall) {
      plugin.onInstall()
    }
  }

  // 卸载插件
  unregisterPlugin(pluginId: string): void {
    const plugin = this.plugins.get(pluginId)
    if (!plugin) return

    // 移除扩展
    this.extensions.views = this.extensions.views.filter(v => !plugin.views?.find(pv => pv.id === v.id))
    this.extensions.toolbars = this.extensions.toolbars.filter(t => !plugin.toolbars?.find(pt => pt.id === t.id))
    this.extensions.columns = this.extensions.columns.filter(c => !plugin.columns?.find(pc => pc.id === c.id))
    this.extensions.actions = this.extensions.actions.filter(a => !plugin.actions?.find(pa => pa.id === a.id))

    // 调用卸载钩子
    if (plugin.onUninstall) {
      plugin.onUninstall()
    }

    this.plugins.delete(pluginId)
  }

  // 获取扩展
  getExtensions<T extends keyof typeof this.extensions>(type: T): typeof this.extensions[T] {
    return this.extensions[type].filter(ext => ext.enabled)
  }

  // 启用插件
  enablePlugin(pluginId: string): void {
    const plugin = this.plugins.get(pluginId)
    if (plugin && plugin.onEnable) {
      plugin.onEnable()
    }
  }

  // 禁用插件
  disablePlugin(pluginId: string): void {
    const plugin = this.plugins.get(pluginId)
    if (plugin && plugin.onDisable) {
      plugin.onDisable()
    }
  }
}

// 创建全局插件管理器实例
export const pluginManager = new PluginManager()

插件开发示例

typescript
// 示例插件:高级统计插件
export const AdvancedStatsPlugin: Plugin = {
  id: 'advanced-stats',
  name: '高级统计',
  version: '1.0.0',
  description: '提供高级数据统计功能',
  author: 'Developer',
  enabled: true,

  onInstall: () => {
    console.log('高级统计插件已安装')
  },

  onUninstall: () => {
    console.log('高级统计插件已卸载')
  },

  views: [
    {
      id: 'advanced-stats-view',
      name: '高级统计视图',
      component: AdvancedStatsView,
      icon: BarChart3,
      enabled: true,
      order: 10
    }
  ],

  toolbars: [
    {
      id: 'stats-export',
      component: StatsExportButton,
      position: 'right',
      order: 5,
      enabled: true
    }
  ],

  columns: [
    {
      id: 'completion-rate',
      type: 'completion-rate',
      renderer: (value, row, column) => (
        <div className="flex items-center space-x-2">
          <div className="w-16 bg-gray-200 rounded-full h-2">
            <div 
              className="bg-green-500 h-2 rounded-full" 
              style={{ width: `${value}%` }}
            />
          </div>
          <span className="text-sm text-gray-600">{value}%</span>
        </div>
      ),
      editor: (value, onChange) => (
        <input
          type="range"
          min="0"
          max="100"
          value={value}
          onChange={(e) => onChange(parseInt(e.target.value))}
          className="w-full"
        />
      ),
      validator: (value) => {
        if (value < 0 || value > 100) {
          return '完成率必须在 0-100 之间'
        }
        return null
      }
    }
  ],

  actions: [
    {
      id: 'generate-report',
      name: '生成报告',
      icon: FileText,
      action: (data) => {
        // 生成报告逻辑
        console.log('生成报告:', data)
      },
      enabled: true,
      visible: (data) => data.length > 0
    }
  ]
}

// 注册插件
pluginManager.registerPlugin(AdvancedStatsPlugin)

🧪 测试指南

单元测试

1. 测试环境配置

bash
# 安装测试依赖
pnpm add -D jest @testing-library/react @testing-library/jest-dom @testing-library/user-event jest-environment-jsdom

# 创建 Jest 配置文件
typescript
// jest.config.js
const nextJest = require('next/jest')

const createJestConfig = nextJest({
  dir: './',
})

const customJestConfig = {
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  testEnvironment: 'jsdom',
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/$1',
  },
}

module.exports = createJestConfig(customJestConfig)
typescript
// jest.setup.js
import '@testing-library/jest-dom'

2. 组件测试示例

typescript
// __tests__/components/enhanced-table-view.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { EnhancedTableView } from '@/components/enhanced-table-view'
import { MultiTableProvider } from '@/components/multi-table-provider'

// 测试数据
const mockTable = {
  id: 'test-table',
  name: '测试表格',
  columns: [
    { id: 'title', title: '标题', width: 200, type: 'text', editable: true, visible: true },
    { id: 'status', title: '状态', width: 100, type: 'select', editable: true, visible: true, options: ['进行中', '已完成'] }
  ],
  data: [
    { id: '1', title: '测试任务1', status: '进行中', priority: '重要紧急', assignee: '张三', startDate: '2024-01-01', dueDate: '2024-01-31', isOverdue: false, progress: '50%', description: '测试描述' },
    { id: '2', title: '测试任务2', status: '已完成', priority: '重要不紧急', assignee: '李四', startDate: '2024-01-01', dueDate: '2024-01-31', isOverdue: false, progress: '100%', description: '测试描述' }
  ],
  groupBy: 'priority',
  filters: {}
}

// 测试包装器
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
  <MultiTableProvider>
    {children}
  </MultiTableProvider>
)

describe('EnhancedTableView', () => {
  it('应该正确渲染表格数据', () => {
    render(
      <TestWrapper>
        <EnhancedTableView />
      </TestWrapper>
    )

    expect(screen.getByText('测试任务1')).toBeInTheDocument()
    expect(screen.getByText('测试任务2')).toBeInTheDocument()
  })

  it('应该支持分组显示', () => {
    render(
      <TestWrapper>
        <EnhancedTableView />
      </TestWrapper>
    )

    expect(screen.getByText('重要紧急')).toBeInTheDocument()
    expect(screen.getByText('重要不紧急')).toBeInTheDocument()
  })

  it('应该支持行拖拽', () => {
    render(
      <TestWrapper>
        <EnhancedTableView />
      </TestWrapper>
    )

    const firstRow = screen.getByText('测试任务1').closest('div')
    expect(firstRow).toHaveAttribute('draggable', 'true')
  })
})

3. Hook 测试示例

typescript
// __tests__/hooks/use-multi-table.test.tsx
import { renderHook, act } from '@testing-library/react'
import { useMultiTable } from '@/hooks/use-multi-table'
import { MultiTableProvider } from '@/components/multi-table-provider'

const TestWrapper = ({ children }: { children: React.ReactNode }) => (
  <MultiTableProvider>
    {children}
  </MultiTableProvider>
)

describe('useMultiTable', () => {
  it('应该返回正确的初始状态', () => {
    const { result } = renderHook(() => useMultiTable(), {
      wrapper: TestWrapper
    })

    expect(result.current.tables).toHaveLength(1)
    expect(result.current.currentTable).toBeDefined()
    expect(result.current.currentTableId).toBe('task-management')
  })

  it('应该能够添加新表格', () => {
    const { result } = renderHook(() => useMultiTable(), {
      wrapper: TestWrapper
    })

    const newTable = {
      id: 'new-table',
      name: '新表格',
      columns: [],
      data: [],
      filters: {}
    }

    act(() => {
      result.current.addTable(newTable)
    })

    expect(result.current.tables).toHaveLength(2)
    expect(result.current.tables[1].id).toBe('new-table')
  })

  it('应该能够更新任务数据', () => {
    const { result } = renderHook(() => useMultiTable(), {
      wrapper: TestWrapper
    })

    const taskId = result.current.currentTable?.data[0]?.id
    if (!taskId) return

    act(() => {
      result.current.updateTaskData(taskId, { title: '更新后的标题' })
    })

    const updatedTask = result.current.currentTable?.data.find(t => t.id === taskId)
    expect(updatedTask?.title).toBe('更新后的标题')
  })
})

集成测试

1. 用户交互测试

typescript
// __tests__/integration/user-interactions.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { HomePage } from '@/app/page'
import { MultiTableProvider } from '@/components/multi-table-provider'

const TestWrapper = ({ children }: { children: React.ReactNode }) => (
  <MultiTableProvider>
    {children}
  </MultiTableProvider>
)

describe('用户交互测试', () => {
  it('应该能够创建新表格', async () => {
    const user = userEvent.setup()
    
    render(
      <TestWrapper>
        <HomePage />
      </TestWrapper>
    )

    // 点击表格管理器
    const tableManager = screen.getByText(/任务管理/)
    await user.click(tableManager)

    // 点击新建表格
    const createButton = screen.getByText('新建表格')
    await user.click(createButton)

    // 填写表格信息
    const nameInput = screen.getByLabelText('表格名称')
    await user.type(nameInput, '新测试表格')

    const descriptionInput = screen.getByLabelText('表格描述')
    await user.type(descriptionInput, '这是一个测试表格')

    // 提交表单
    const submitButton = screen.getByText('创建表格')
    await user.click(submitButton)

    // 验证表格已创建
    await waitFor(() => {
      expect(screen.getByText('新测试表格')).toBeInTheDocument()
    })
  })

  it('应该能够搜索任务', async () => {
    const user = userEvent.setup()
    
    render(
      <TestWrapper>
        <HomePage />
      </TestWrapper>
    )

    // 在搜索框中输入搜索词
    const searchInput = screen.getByPlaceholderText('搜索任务...')
    await user.type(searchInput, '财务报告')

    // 验证搜索结果
    await waitFor(() => {
      expect(screen.getByText('找到 1 条结果')).toBeInTheDocument()
    })
  })

  it('应该能够拖拽排序任务', async () => {
    const user = userEvent.setup()
    
    render(
      <TestWrapper>
        <HomePage />
      </TestWrapper>
    )

    // 找到第一个任务行
    const firstTask = screen.getByText('完成年度财务报告').closest('div')
    const secondTask = screen.getByText('组织年度员工团建活动').closest('div')

    if (!firstTask || !secondTask) return

    // 拖拽第一个任务到第二个任务位置
    await user.click(firstTask)
    await user.pointer([
      { target: firstTask, keys: '[MouseLeft>]' },
      { target: secondTask, coords: { x: 0, y: 0 } }
    ])

    // 验证拖拽结果(这里需要根据实际实现调整验证逻辑)
    expect(firstTask).toBeInTheDocument()
  })
})

📦 构建和部署

构建配置

1. Next.js 配置

typescript
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 启用实验性功能
  experimental: {
    // 启用 React 19 特性
    reactCompiler: true,
  },
  
  // 图片优化配置
  images: {
    domains: ['example.com'],
    formats: ['image/webp', 'image/avif'],
  },
  
  // 环境变量配置
  env: {
    CUSTOM_KEY: process.env.CUSTOM_KEY,
  },
  
  // 构建输出配置
  output: 'standalone',
  
  // 重定向配置
  async redirects() {
    return [
      {
        source: '/old-page',
        destination: '/new-page',
        permanent: true,
      },
    ]
  },
  
  // 头部配置
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          { key: 'Access-Control-Allow-Origin', value: '*' },
          { key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE,OPTIONS' },
        ],
      },
    ]
  },
}

export default nextConfig

2. 环境变量配置

bash
# .env.local
NEXT_PUBLIC_APP_NAME=Mute Table Pro
NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_ENABLE_ANALYTICS=true

# 私有环境变量
DATABASE_URL=postgresql://user:password@localhost:5432/mute_pro
JWT_SECRET=your-secret-key
bash
# .env.production
NEXT_PUBLIC_APP_NAME=Mute Table Pro
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
NEXT_PUBLIC_ENABLE_ANALYTICS=true

DATABASE_URL=postgresql://user:password@prod-db:5432/mute_pro
JWT_SECRET=your-production-secret-key

部署配置

1. Docker 部署

dockerfile
# Dockerfile
FROM node:18-alpine AS base

# 安装依赖
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

# 复制包管理文件
COPY package.json pnpm-lock.yaml* ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile

# 构建应用
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# 设置环境变量
ENV NEXT_TELEMETRY_DISABLED 1

# 构建应用
RUN npm run build

# 生产环境
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# 复制构建产物
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]

2. Docker Compose 配置

yaml
# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:password@db:5432/mute_pro
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=mute_pro
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - app
    restart: unless-stopped

volumes:
  postgres_data:

3. Nginx 配置

nginx
# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream app {
        server app:3000;
    }

    server {
        listen 80;
        server_name yourdomain.com;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name yourdomain.com;

        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;

        # 安全配置
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers off;

        # 代理到应用
        location / {
            proxy_pass http://app;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
        }

        # 静态资源缓存
        location /_next/static {
            proxy_pass http://app;
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
}

CI/CD 配置

1. GitHub Actions

yaml
# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build application
        run: npm run build
        env:
          NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}

      - name: Deploy to server
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          script: |
            cd /opt/mute-pro
            git pull origin main
            docker-compose down
            docker-compose build --no-cache
            docker-compose up -d

2. 部署脚本

bash
#!/bin/bash
# deploy.sh

set -e

echo "🚀 开始部署 Mute Table Pro..."

# 检查环境
if [ -z "$NODE_ENV" ]; then
    export NODE_ENV=production
fi

# 安装依赖
echo "📦 安装依赖..."
pnpm install --frozen-lockfile

# 运行测试
echo "🧪 运行测试..."
pnpm test

# 构建应用
echo "🔨 构建应用..."
pnpm build

# 重启服务
echo "🔄 重启服务..."
pm2 restart mute-pro || pm2 start npm --name "mute-pro" -- start

echo "✅ 部署完成!"

这是二次开发指南文档,包含了详细的开发流程、扩展方法和部署配置。接下来将继续提供服务端对接、数据流转等详细内容。

让技术更平权,致力于高性价办公协同解决方案