一万行代码实现的多维分析表格,让数据处理效率提升 300%

上个月在 趣谈AI 发布了我实现的多维表格1.0版本,没有用到任何第三方组件,完全组件化设计。最近对多维表格进行了进一步的升级优化,满打满算花了接近3个月时间,累计代码接近1w行。

接下来就和大家聊聊我做的 flowmix/mute多维表格 的核心功能和技术实现。

核心功能介绍

1. 多视图模式

目前多维表格支持多种视图模式:表格视图,看板视图,人员分配视图。用户可以轻松在不同视图下切换并进行可视化操作数据。

2. 多条件筛选功能

我们可以基于不同维度进行筛选和排序,并支持组合筛选。

3. 多维度分组功能

表格视图中,我们可以基于用户,优先级,状态,对数据进行分组管理,提高表格数据的查看效率。

4. 表格字段管理功能

多维表格中不仅支持字段的管理控制,同时还支持添加自定义字段:

5. 表格行列支持自定义拖拽排序功能

表格我们不仅仅支持列的宽度拖拽,还支持拖拽调整列的排序,同时表格的行也支持拖拽,可以跨分组进行拖拽,也支持在组内进行拖拽排序,极大的提高了数据管理的效率。

6. 表格支持一键编辑

我们可以在菜单按钮中开启编辑模式,也可以双击编辑单元格一键编辑表格内容,同时大家还可以进行扩展。

7. 表格支持一键转换为可视化分析视图表

我们可以将表格数据转换为可视化分析图表,帮助管理者更好地掌握数据动向。

8. 表格支持一键导入任务数据

目前多维表格支持导出和导入json数据,并一键渲染为多维表格。技术实现多维表格的设计我采用了组件化的实现的方式, 并支持数据持久化,具体使用如下:

html 复制代码
<div className="flex-1 bg-gray-50">
    {currentView === "tasks" && <TaskManagementTable sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />}
    {currentView === "statistics" && <StatisticsView />}
    {currentView === "documentation" && <DocumentationView />}
    {currentView === "assignment" && <AssignmentView />}
    {currentView === "deployment" && <DeploymentView />}
</div>

在开发多维表格的过程中其实需要考虑很多复杂逻辑,比如表格用什么方式渲染,如何优化表格性能,如何实现表格的列排序,行排序,表格编辑等。传统表格组件大多基于div模拟行列,虽然灵活但渲染性能差。所以可以做如下优化:

  • 虚拟滚动当数据量超过 500 行时,启用虚拟滚动机制,仅渲染可见区域的 DOM 节点,内存占用降低 70%;
  • 行列冻结 通过固定定位position: sticky实现表头和固定列冻结,解决大数据表格的滚动迷失问题;
  • 异步加载 采用Intersection Observer监听表格滚动事件,动态加载可视区域外的数据,避免一次性请求全量数据。

接下来分享一下简版的虚拟滚动的实现方案:

js 复制代码
// 虚拟滚动核心代码(简化版)
function renderVirtualTable(data, visibleHeight) {
  const totalRows = data.length;
  const rowHeight = 40; // 行高固定
  const visibleRows = Math.ceil(visibleHeight / rowHeight);
  const startIndex = scrollTop / rowHeight | 0;
  const endIndex = startIndex + visibleRows;
  // 渲染可见区域数据
  const fragment = document.createDocumentFragment();
  for (let i = startIndex; i < endIndex; i++) {
    const row = document.createElement('tr');
    row.innerHTML = data[i].cells.map(cell => `<td>${cell.value}</td>`).join('');
    fragment.appendChild(row);
  }
  // 更新滚动条高度和偏移量
  table.scrollHeight = totalRows * rowHeight;
  table.innerHTML = `<thead>${header}</thead><tbody>${fragment}</tbody>`;
}

对于大表格数据量需要在本地缓存,所以需要设计表格数据的缓存处理逻辑,目前我采用的是hooks的实现方案,具体实现如下:

js 复制代码
import { useState, useEffect } from "react"
export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void] {
  // 初始化状态
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      // 获取本地存储中的值
      if (typeof window === "undefined") {
        return initialValue
      }
      const item = window.localStorage.getItem(key)
      // 解析存储的JSON或返回初始值
      return item ? JSON.parse(item) : initialValue
    } catch (error) {
      // 如果出错,返回初始值
      console.error(`Error reading localStorage key "${key}":`, error)
      return initialValue
    }
  })
  // 返回一个包装版本的 useState setter 函数
  // 将新值同步到 localStorage
  const setValue = (value: T | ((val: T) => T)) => {
    try {
      // 允许值是一个函数
      const valueToStore = value instanceof Function ? value(storedValue) : value
      // 保存到 state
      setStoredValue(valueToStore)
      // 保存到 localStorage
      if (typeof window !== "undefined") {
        window.localStorage.setItem(key, JSON.stringify(valueToStore))
      }
    } catch (error) {
      console.error(`Error setting localStorage key "${key}":`, error)
    }
  }
  // 监听其他标签页的变化
  useEffect(() => {
    const handleStorageChange = (e: StorageEvent) => {
      if (e.key === key && e.newValue) {
        try {
          setStoredValue(JSON.parse(e.newValue))
        } catch (error) {
          console.error(`Error parsing localStorage item "${key}":`, error)
        }
      }
    }
    // 添加事件监听器
    if (typeof window !== "undefined") {
      window.addEventListener("storage", handleStorageChange)
    }
    // 清理事件监听器
    return () => {
      if (typeof window !== "undefined") {
        window.removeEventListener("storage", handleStorageChange)
      }
    }
  }, [key])
  return [storedValue, setValue]
}

其实在实现多维表格的过程中,我也调研了很多开源的方案,但是对于扩展性,灵活度和功能复杂度上,都略显简单,所以我才考虑花时间来实现这款多维表格方案。另一个比较复杂的逻辑是表格的列拖拽和排序,我们需要对可展开折叠的表格支持排序和拖拽,并保持优秀的用户体验:

技术实现如下:

js 复制代码
import { useState, useEffect } from "react"
export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void] {
  // 初始化状态
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      // 获取本地存储中的值
      if (typeof window === "undefined") {
        return initialValue
      }
      const item = window.localStorage.getItem(key)
      // 解析存储的JSON或返回初始值
      return item ? JSON.parse(item) : initialValue
    } catch (error) {
      // 如果出错,返回初始值
      console.error(`Error reading localStorage key "${key}":`, error)
      return initialValue
    }
  })
  // 返回一个包装版本的 useState setter 函数
  // 将新值同步到 localStorage
  const setValue = (value: T | ((val: T) => T)) => {
    try {
      // 允许值是一个函数
      const valueToStore = value instanceof Function ? value(storedValue) : value
      // 保存到 state
      setStoredValue(valueToStore)
      // 保存到 localStorage
      if (typeof window !== "undefined") {
        window.localStorage.setItem(key, JSON.stringify(valueToStore))
      }
    } catch (error) {
      console.error(`Error setting localStorage key "${key}":`, error)
    }
  }
  // 监听其他标签页的变化
  useEffect(() => {
    const handleStorageChange = (e: StorageEvent) => {
      if (e.key === key && e.newValue) {
        try {
          setStoredValue(JSON.parse(e.newValue))
        } catch (error) {
          console.error(`Error parsing localStorage item "${key}":`, error)
        }
      }
    }
    // 添加事件监听器
    if (typeof window !== "undefined") {
      window.addEventListener("storage", handleStorageChange)
    }
    // 清理事件监听器
    return () => {
      if (typeof window !== "undefined") {
        window.removeEventListener("storage", handleStorageChange)
      }
    }
  }, [key])
  return [storedValue, setValue]
}

多维表格还支持多种视图的转换,比如可以将表格视图一键转换为可视化分析图表:

对用户和团队进行多维度的数据分析。技术实现如下:

js 复制代码
import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer } from "recharts"
import type { Task } from "@/lib/types"
interface PriorityDistributionChartProps {
  tasks: Task[]
}
export function PriorityDistributionChart({ tasks }: PriorityDistributionChartProps) {
  // 计算每个优先级的任务数量
  const priorityCounts: Record<string, number> = {}
  tasks.forEach((task) => {
    const priority = task.priority || "未设置"
    priorityCounts[priority] = (priorityCounts[priority] || 0) + 1
  })
  // 转换为图表数据格式
  const chartData = Object.entries(priorityCounts).map(([priority, count]) => ({
    priority,
    count,
  }))
  // 为不同优先级设置不同颜色
  const COLORS = ["#FF8042", "#FFBB28", "#00C49F", "#0088FE"]
  return (
    <div className="w-full h-[300px]">
      <ResponsiveContainer width="100%" height="100%">
        <PieChart>
          <Pie
            data={chartData}
            cx="50%"
            cy="50%"
            labelLine={true}
            outerRadius={80}
            fill="#8884d8"
            dataKey="count"
            nameKey="priority"
            label={({ priority, percent }) => `${priority}: ${(percent * 100).toFixed(0)}%`}
          >
            {chartData.map((entry, index) => (
              <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
            ))}
          </Pie>
          <Tooltip formatter={(value, name, props) => [`${value} 个任务`, props.payload.priority]} />
          <Legend />
        </PieChart>
      </ResponsiveContainer>
    </div>
  )
}

项目的体验地址:mute.turntip.cn

如果大家有好的想法,欢迎评论区留言反馈~

相关推荐
伍哥的传说19 分钟前
鸿蒙系统(HarmonyOS)应用开发之手势锁屏密码锁(PatternLock)
前端·华为·前端框架·harmonyos·鸿蒙
yugi98783821 分钟前
前端跨域问题解决Access to XMLHttpRequest at xxx from has been blocked by CORS policy
前端
浪裡遊32 分钟前
Sass详解:功能特性、常用方法与最佳实践
开发语言·前端·javascript·css·vue.js·rust·sass
旧曲重听11 小时前
最快实现的前端灰度方案
前端·程序人生·状态模式
默默coding的程序猿2 小时前
3.前端和后端参数不一致,后端接不到数据的解决方案
java·前端·spring·ssm·springboot·idea·springcloud
夏梦春蝉2 小时前
ES6从入门到精通:常用知识点
前端·javascript·es6
鹏程十八少2 小时前
7.Android 设计模式 享元模式 在商业项目中的落地
架构
归于尽2 小时前
useEffect玩转React Hooks生命周期
前端·react.js
G等你下课2 小时前
React useEffect 详解与运用
前端·react.js
我想说一句2 小时前
当饼干遇上代码:一场HTTP与Cookie的奇幻漂流 🍪🌊
前端·javascript