乐于分享
好东西不私藏

支持买断+二开!这款多维表格编辑器,助你低成本构建复杂数据报表

支持买断+二开!这款多维表格编辑器,助你低成本构建复杂数据报表

👆点击趣谈AI>点击右上角“···”>设为星标🌟

嗨,大家好,我是徐小夕。 曾任职多家上市公司,多年架构经验,打造过上亿用户规模的产品,目前全职创业,聚集于“Dooring AI零代码搭建平台”和“flowmixAI多模态办公软件”。

最近推出《架构师精选专栏,在专栏中分享一线企业技术实践和架构经验和大家拆解可视化搭建平台,AI产品,办公协同软件的源码实现

今天聊聊多维表格编辑器。
之前在趣谈AI公众号和大家分享过我花了3个月编写的多维表格,如上图所示,从架构设计到具体功能实现,工作量非常巨大,好在如期上线,也收到了非常多网友的反馈,最终定了1.0版本,代码量接近1w行。
体验地址:http://mute.turntip.cn
为什么要做多维表格编辑器
之所以会立项做这个事情,一方面是我们研发的flowmix多模态办公软件的需要,另一方面是我在调研了大量的开源项目和商业化项目和多维表格相关的编辑器,得出了几个结论:
  • 开源项目基本都是基础业务组件,复杂度和定制性不够,也没有成熟的多维表格开源
  • 商业化的多维表格基本无法定制开发,并且私有化部署的成本基本都在6位数以上
所以我一咬牙,决定自研。
多维表格编辑器上线后,很多人希望我开源或者购买源码,思考这个事情是非常痛苦的,在与技术信仰斗争7 * 7 = 52小时之后,我决定给前100名有需求的公众号粉丝, 以399的价格交付全套源码

多维表格编辑器核心功能介绍

1. 多视图模式
目前多维表格支持多种视图模式:表格视图看板视图人员分配视图。用户可以轻松在不同视图下切换并进行可视化操作数据。
2. 多条件筛选功能
我们可以基于不同维度进行筛选和排序,并支持组合筛选。
3. 多维度分组功能
表格视图中,我们可以基于用户优先级状态,对数据进行分组管理,提高表格数据的查看效率。
4. 表格字段管理功能
多维表格中不仅支持字段的管理控制,同时还支持添加自定义字段:
5. 表格行列支持自定义拖拽排序功能
表格我们不仅仅支持列的宽度拖拽,还支持拖拽调整列的排序,同时表格的行也支持拖拽,可以跨分组进行拖拽,也支持在组内进行拖拽排序,极大的提高了数据管理的效率。
6. 表格支持一键编辑
我们可以在菜单按钮中开启编辑模式,也可以双击编辑单元格一键编辑表格内容,同时大家还可以进行扩展。
7. 表格支持一键转换为可视化分析视图表
我们可以将表格数据转换为可视化分析图表,帮助管理者更好地掌握数据动向。
8. 表格支持一键导入任务数据
目前多维表格支持导出和导入json数据,并一键渲染为多维表格。

多维表格编辑器核心技术分享
多维表格的设计我采用了组件化的实现的方式, 并支持数据持久化,具体使用如下:
<div className="flex-1 bg-gray-50">    {currentView === "tasks" && <TaskManagementTablesidebarOpen={sidebarOpen}setSidebarOpen={setSidebarOpen} />}    {currentView === "statistics" && <StatisticsView />}    {currentView === "documentation" && <DocumentationView />}    {currentView === "assignment" && <AssignmentView />}    {currentView === "deployment" && <DeploymentView />}</div>
在开发多维表格的过程中其实需要考虑很多复杂逻辑,比如表格用什么方式渲染,如何优化表格性能,如何实现表格的列排序,行排序,表格编辑等。
传统表格组件大多基于div模拟行列,虽然灵活但渲染性能差。所以可以做如下优化:
  • 虚拟滚动
    当数据量超过 500 行时,启用虚拟滚动机制,仅渲染可见区域的 DOM 节点,内存占用降低 70%;
  • 行列冻结
    通过固定定位position: sticky实现表头和固定列冻结,解决大数据表格的滚动迷失问题;
  • 异步加载
    采用Intersection Observer监听表格滚动事件,动态加载可视区域外的数据,避免一次性请求全量数据。
接下来分享一下简版的虚拟滚动的实现方案:
// 虚拟滚动核心代码(简化版)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的实现方案,具体实现如下:
import { useState, useEffect } from "react"export function useLocalStorage<T>(keystringinitialValue: 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]}
其实在实现多维表格的过程中,我也调研了很多开源的方案,但是对于扩展性,灵活度和功能复杂度上,都略显简单,所以我才考虑花时间来实现这款多维表格方案。
另一个比较复杂的逻辑是表格的列拖拽和排序,我们需要对可展开折叠的表格支持排序和拖拽,并保持优秀的用户体验:
技术实现如下:
import { useState, useEffect } from "react"export function useLocalStorage<T>(keystringinitialValue: 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]}
多维表格还支持多种视图的转换,比如可以将表格视图一键转换为可视化分析图表:
对用户和团队进行多维度的数据分析。技术实现如下:
import { PieChartPieCellTooltipLegendResponsiveContainer } from "recharts"import type { Task } from "@/lib/types"interface PriorityDistributionChartProps {  tasksTask[]}export function PriorityDistributionChart({ tasks }: PriorityDistributionChartProps) {  // 计算每个优先级的任务数量  const priorityCountsRecord<stringnumber> = {}  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 (    <divclassName="w-full h-[300px]">      <ResponsiveContainerwidth="100%"height="100%">        <PieChart>          <Pie            data={chartData}            cx="50%"            cy="50%"            labelLine={true}            outerRadius={80}            fill="#8884d8"            dataKey="count"            nameKey="priority"            label={({ prioritypercent }) => `${priority}: ${(percent * 100).toFixed(0)}%`}          >            {chartData.map((entry, index) => (              <Cellkey={`cell-${index}`} fill={COLORS[index % COLORS.length]} />            ))}          </Pie>          <Tooltipformatter={(value,nameprops) => [`${value} 个任务`, props.payload.priority]} />          <Legend />        </PieChart>      </ResponsiveContainer>    </div>  )}
后续会在《架构师精选专栏 持续分享多维表格的最佳实践,如果大家想获取多维表格源码,可以在公众号加我微信了解咨询。.
体验地址:http://mute.turntip.cn
大家有好的建议也欢迎在留言区交流反馈~