Skip to content

多维表格仪表盘插件介绍

什么是多维表格仪表盘插件?

多维表仪表盘插件是多维表仪表盘所提供的用于数据可视化的插件系统。它基于标准的HTML、CSS和Javascript构建,并提供了一套用于与多维表进行数据交互的官方SDK,帮助用户针对不同的可视化需求进行定制化开发,最终实现在多维表仪表盘中帮助用户基于数据可视化进行数据的展示与分析

下面是使用仪表盘插件的示例

  1. 新建仪表盘

  2. 仪表盘展示页

  3. 插件入口

  4. 插件配置

  5. 插件展示(非全屏/全屏)

多维表格仪表盘插件可以做什么?

以目前官方提供的仪表盘组件示例

  1. 数据可视化(非全屏/全屏)

  2. 数据统计(非全屏/全屏)

  3. 文字说明(非全屏/全屏,建议搭配其他组件一起使用)

什么情况下需要开发多维表格仪表盘插件?

  1. 通过仪表盘实现可视化需求

    目前(2025/4/8)仪表盘提供如图所示可视化组件,之后仍会陆续迭代新组件。如果目前所提供的可视化组件无法满足当前需求,可以通过页面进行反馈。我们会评估反馈内容,按照反馈的需求力度进行相应的开发。 如果需求自定义程度较高,可以采用插件的方式,进行开发、测试、发布、使用。

  2. 从事相关开发服务的个人或企业

谁可以开发多维表格仪表盘插件?

基础

插件配置config

config.dataConditions

SDK提供的配置数据,包含sheetIddataRangegroupsseries

js
{
    // 数据表id
    sheetId: number
    // 数据筛选,由SDK返回,直接保存使用,无需调整
    dataRange: Object
    // 分组配置
    groups: Object[]
    // 聚合方式,通常使用"COUNTA",表示聚合非空数据
    series: string
}

config.dataConditions.sheetId

作为数据源的数据表id

js
sheetId: number

通常在插件中配合SDK获取数据表数据,以选择的形式进行配置


config.dataConditions.dataRange

针对数据源进行的数据筛选。数据筛选组件由SDK提供,可直接调用。同时配置数据也一并由SDK返回,直接保存使用,无需调整

js
{
    // 过滤条件数组(空数组表示无过滤条件)
    "filterInfo": {
        // 过滤条件
        criteria: {
            // 操作符,等于/不等于/指定值/... 
            op: string
            // 过滤的值
            values: { type: string, value: any }[]
        },
        // 字段id
        fieldId: string,
        // 过滤条件项id
        filterId: string
    }[], 
    // 过滤操作逻辑,多个过滤条件之间的逻辑关系
    // 满足所有条件或满足任一条件("And" 或 "Or")
    "filtersOp": "And" | "Or"
}

SDK提供的API调起数据筛选组件,筛选逻辑及用法同多维表数据表数据筛选


config.dataConditions.groups

针对源数据进行分组、排序

js
{
    // 根据哪个字段进行分组
    "fieldId": string, 
    // 分组单位,表明该字段数据类型,如"Text"
    "groupUnit": string, 
    "sort": {
        // 排序依据,这里按数据值排序或是按分组排序
        "sortType": "VALUE" | "GROUP",
        // 排序方式,正序或倒序
        "order": "ascend" | "descend"
    },
    // 分组模式,拆分统计或综合统计,通常为"integrated"
    "groupMode": "enumerated" | "integrated"
}

通过配置groups参数,实现图表的不同分组以及不同的排序方式


config.dataConditions.series

数据聚合方式,如统计所有非空项

js
// 通常为"COUNTA",统计所有非空数据
series: "COUNTA"

config.customConfig

除SDK支持的配置外,额外需要自定义的配置项

js
{
    [key: string]: any
}

主题色配置、图表展示配置等均可通过customConfig进行保存

插件能力

📌 多维表格仪表盘插件的能力,主要基于多维表格API以及其中包含的多维表格仪表盘API实现,即WPSOpenAPI与DashboardAPI

获取当前多维表格下所有数据表

通过WPSOpenAPI提供的能力获取当前多维表下的所有可选数据表

代码示例

js
// 数据表类型
enum SHEET_TYPES {
  // 数据表
  dbsheet = "xlEtDataBaseSheet",
  // db仪表盘
  dashboard = "xlDbDashBoardSheet",
  // fpsheet
  fpsheet = "xlEtFlexPaperSheet",
  // 工作表
  worksheet = "xlWorksheet",
  // 数据表应用表
  appsheet = "xlEtAppSheet",
  // 工作台
  workbenchSheet = "xlEtWorkbenchSheet",
}

// 过滤需要的数据表数据
const getSheets = async () => {
    const sheets = []
    const count = await WPSOpenAPI.Sheets.Count
    
    // 通过循环,筛选出需要的数据
    for (let index = 1; index <= count; index++) {
        const sheet = await WPSOpenAPI.Sheets(index)
        const name = await sheet.Name
        const type = await sheet.Type

        if (name && type === SHEET_TYPES.dbsheet) {
            const sId = await sheet.Id
            sheets.push({ type, sId, name, index })
        }
    }    

}

实现效果


获取数据表数据

通过DashboardAPI提供的插件配置(getConfigsaveConfig)与数据获取(getDatagetDataSourceSheetSupportStaticsFields),对数据进行针对性操作,例如数据分析、数据可视化等:

代码示例

插件配置 (以排行榜插件举例)

js
{
    // 自定义配置
    "customConfig": {
        "configured": true,
        "showMaxLines": 10,
        "progressBar": true,
        "iconStyle": "MEDALS"
    },
    // 数据配置
    "dataConditions": {
        "sheetId": 1,
        "dataRange": {
            "filterInfo": [],
            "filtersOp": "And"
        },
        "groups": [
            {
                "fieldId": "F",
                "groupUnit": "Text",
                "sort": {
                    "sortType": "VALUE",
                    "order": "descend"
                },
                "groupMode": "integrated"
            }
        ],
        "series": "COUNTA"
    }
}

获取到的数据结构:

json
[
    [
        {
            "text": "星期日"
        },
        {
            "value": 300
        }
    ],
    [
        {
            "text": "星期六"
        },
        {
            "value": 200
        }
    ],
    ......
]

实现效果

根据左侧配置实现的排行榜插件效果:

  • 数据配置

  • 自定义配置

其他可以实现的效果(以柱状图举例):

  • 数据配置

  • 自定义配置


数据筛选

通过DashboardAPI提供的showFilterModal调用数据筛选弹窗,对数据进行过滤

代码示例

js
// 通过SDK提供的API打开数据筛选弹窗
dashboardAPI.showFilterModal(
    {
        sheetId, // 数据表ID
        dataRange, // 当前已配置的筛选条件
    },
    // 确认的回调函数
    (filterCondition) => {
        // filterCondition.dataRange为最终确定的所有筛选条件,可以直接保存,即:
        // config.dataConditions.dataRange = filterCondition.dataRange
        ......
    },
    // 取消的回调函数
    () => {}
)

实现效果

插件状态

仪表盘插件集成了多种状态,包括配置状态展示状态以及全屏状态。开发者必须依据各个状态的不同,进行相应的界面渲染

展示状态

添加到仪表盘的插件,在仪表盘中时即为展示状态

配置状态

点击插件的配置下拉选项,进入配置界面。插件在配置状态应该提供配置侧栏

全屏状态

点击仪表盘顶部工具栏的“全屏显示”按钮,进入全屏状态。全屏状态支持自动滚动,让数据展示更全面

开发流程

  1. 创建工程,安装脚手架或者自行创建项目
  2. 配置本地开发调试环境,将在线上文档连接到本地插件开发环境,参考调试指南
  3. 开发插件
  4. 上传并发布插件,将应用发布到线上

创建工程

脚手架安装

💡 目前仪表盘插件模板脚手架还在开发中,用户可以直接下载模板压缩包解压使用 (点击下载)

在脚手架构建完毕之后,建议采用以下指令进行依赖项的安装以及工程的运行。请注意,推荐的Node版本应大于或等于18

shell
npm i
npm run dev

调试指南

运行插件

  1. 本地运行插件,从终端取得本地服务端口PORT
  2. 进入任意多维表文档,链接示例:https://www.kdocs.cn/l/XXXXXXXXX
  3. 加入URL Params plugin_debug=PORT,例如: https://www.kdocs.cn/l/XXXXXXXXX?plugin_debug=5173https://www.kdocs.cn/l/XXXXXXXXX?R=XXXXXXXX&plugin_debug=5173 该参数将告知页面将插件调用到本地的对应端口(如 http://localhost:5173 )
  4. 刷新页面,所有插件都会被代理到本地开发环境,以供调试

使用React DevTools

参考文档:Github: react-devtools#usage-with-react-dom

  1. 命令行执行,启动一个外置的开发工具(可在任意位置执行,终端将被占用):
shell
npx react-devtools
  1. 工具打开后,将展示监听端口,将其中的配置项如<script src="``http://localhost:8097``"></script>复制到插件index.html中。记得发布前删除改行代码!记得发布前删除改行代码!记得发布前删除改行代码!

  1. 保存更改,运行插件项目
  2. 刷新页面/等待热重载HMR,运行的开发工具将载入本地插件:

注:如果页面存在多个本地插件iframe,会导致react devtools竞争占用,此时可以在F12控制面板元素选项中手动删除其余iframe。

插件上架和发布

打包插件

执行pnpm build打包插件,将dist文件夹压缩为zip包以供后续上传插件使用

创建应用

点击 创建应用

上传插件

进入详情页

打开插件开关,进入能力配置

上传文件

📌 将meta.json、main.py 两个文件压缩成.zip格式压缩包,用于文件上传

配置插件信息

插件上线

进入版本管理页面

填写版本相关信息

申请发布应用,并审核

进入企业管理后台,找到对应的应用,在应用审核页面,通过上架应用的申请

发布完成

插件下架

下架插件只需在“应用能力”页面找到“数据连接插件”卡片然后点击关闭按钮即可。但是重新上架的话需要重新走一遍插件发布审核的逻辑。

接口文档

模块说明

SDK接口融合了开放能力和插件特供能力,在初始化sdk后在同一个实例上调用

  • WPSOpenAPI:仪表盘插件可以调用开放平台中的接口能力
  • DashboardAPI:仪表盘插件定义的接口,提供了特有的插件方法调用,仅用于仪表盘插件开发

DashboardAPI

getState

获取仪表盘插件当前所处场景状态:

js
getState: () => Promise<DashboardState>

类型定义:

js
enum DashboardState {
    Config = 'Config', // 配置状态
    View = 'View', // 展示状态
    FullScreen = 'FullScreen' // 全屏状态
}

getConfig

获取仪表盘插件配置信息:

js
getConfig: () => Promise<IDBPluginConfig>

类型定义(筛选类型SourceTypeIFilterInfoFilterOpType详见类型定义):

js
interface IDBPluginConfig {
    dataConditions: IDataCondition | IDataCondition[]; // 数据源配置,当前版本仅支持一组条件
    customConfig?: Record<string, unknown>; // 自定义配置
}

interface IDataCondition {
    sheetId: number; // 数据源表id
    dataRange?: IDataRange | IDataRange[]; // 数据范围(全部)
    groups: IGroupItem[]; // 分组信息, 仅有两项,第一项用做维度,第二项用做分组,超出两项报错
    series: IStatisticSeries[] | SeriesType.COUNT; // 系列,指的是字段的计算方式
}

interface IDataRange {
    type?: SourceType.ALL; // 单表的全部数据
    filterInfo?: IFilterInfo[]; // 筛选条件
    filtersOp?: FilterOpType; // 筛选条件的逻辑关系
}

// 分组
interface IGroupItem {
    fieldId: string; // 分组依据的字段
    groupUnit: DbGroupUnit // 拆分统计,如日期字段
    sort?: ISort; // 每个分组的排序规则
    groupMode?: GroupMode; // 分组模式,分组字段为多选时可进行配置
}

interface ISort {
    sortType: DataSourceSortType; // 排序依据 
    order: ORDER; // 排序方式(正序倒序)
}

enum DataSourceSortType {
    VALUE = 'VALUE', // 按计算结果的值进行排序(如统计的是记录总数,则按照记录总数进行排序)
    GROUP = 'GROUP', // 按分组字段类别进行排序(如用数字字段进行分组,则用数字大小进行排序)
}

// 字段计算方式
interface IStatisticSeries {
    fieldId: string;
    rollup: Rollup; // 求和、平均、最大、最小、计数
}

export enum DbGroupUnit {
    Text = 'Text',
    Week = 'Week',
    Month = 'Month',
    Day = 'Day',
    Year = 'Year',
    Hour = 'Hour',
    Minute = 'Minute',
    Second = 'Second',
}

// 分组模式
enum GroupMode {
    ENUMERATED = 'enumerated', // 拆分统计,“A,B,C” -> A | B | C
    INTEGRATED = 'integrated', // 不拆分统计,“A,B,C” -> “A,B,C”
}

// 计算方式
enum Rollup {
    SUM = 'sum',
    COUNT = 'count',
    MAX = 'max',
    MIN = 'min',
    AVG = 'avg',
    UNIQUE_COUNT ='uniqueCount', // 去重计数
}

// 排序方式
enum ORDER {
    ASCEND = 'ascend', // 正序
    DESCEND = 'descend', // 倒序
}

saveConfig

保存仪表盘插件配置(用户更改配置后调用,图表立即响应更改):

js
saveConfig: (config: IDBPluginConfig) => Promise<IError>

类型定义(其余相关类型详见getConfig):

js
interface IError {
    code: number; // code 0 - 表示保存成功
    message: string;
}

getData

获取数据表数据:

js
getData: () => Promise<IDBPluginData>

类型定义:

js
type IDBPluginData = [IDBPluginHeader, ...IDBPluginDataRow]

/**
 * 数组的第一项,固定形式,[{类别字段}, {系列1}, {系列2}, {系列3}]
 */
type IDBPluginHeader = [
    {
        /** 类别字段 */
        text: string;
    },
    {
        /** 某系列名称 */
        text: string;
        /**  numberFormat统计字段的数字格式 */
        numberFormat: string;
    },
    ...
]

/**
 * 数组的第二项开始,为具体类别下的每个系列的数据(计算结果)
 */
type IDBPluginDataRow = [
    {
        /** 类别 */
        text: string;
    },
    {
        /** 数据 */
        value: number;
    },
    ...
][]

setRendered

📌 请务必在插件渲染完成后调用该方法,否则影响定时发送等功能

渲染完成时请调用该方法通知宿主页面:

js
setRendered: () => void

showFilterModal

打开数据筛选器弹窗:

js
showFilterModal: (
    filterCondition: IFilterCondition,
    onOk: (filterCondition: IFilterCondition) => void,
    onCancel: (filterCondition: IFilterCondition) => void,
) => void

参数类型(其余相关类型定义详见getConfig):

js
type IFilterCondition = Pick<IDataCondition, 'sheetId' | 'dataRange'>

配置状态下打开字段筛选器弹窗,在onOk方法中需要调用saveConfig方法保存用户设置的dataRangeIFilterCondition包含的IDataRange属性类型定义如下,其中筛选类型SourceTypeIFilterInfoFilterOpType参考类型定义

js
interface IDataRange {
    type?: SourceType.ALL; // 单表的全部数据
    filterInfo?: IFilterInfo[]; // 筛选条件
    filtersOp?: FilterOpType; // 筛选条件的逻辑关系
}

closeConfigView

关闭配置面板:

js
closeConfigView: () => void

getDataSourceSheetSupportStaticsFields

获取该sheet所有支持统计的字段:

js
getDataSourceSheetSupportStaticsFields: (sheetId: number) => Promise<IStaticField[]>

类型定义(Rollup类型详见getConfig):

js
interface IStaticField {
    id: string;
    name: string;
    type: FieldType;
    /** 支持的op类型 */
    supportRollup: Rollup[];
}

valuesFormatter

对某一列的数字进行格式化,如百分比、小数、货币,千分位标志等,拿到和单元格一样的数字展示格式:

js
// 参数说明:
// values:从getData统计结果取数据,一列数据的数组。
// format:从getData统计结果取第一行数据,也就是从表头的数据中,取出某一列数据的格式化规则numberFormat,如'0.00_ '
// 调用示例
// valuesFormatter([1234], '0.00_ ')  => ['1234.00']
valuesFormatter: (values: number[], numberFormat: string) => string[]

Dashboard事件

📌 调用事件函数将返回一个清除函数,调用清除函数停止监听对应更改。

事件处理函数的参数类型,如果出错则会在结果中包含error属性:

js
interface IEventCbCtx<T> {
    data: T;
    error?: {
        code: number;
        message: string;
    }
}

onDataChange

📌 为了维持与WebOffice图表相近的协同体验,插件图表在整个生命周期均需持有该监听方法

监听数据源的变化:

js
onDataChange: (handler: IDataChangeEventCallback) => IOffCallback

类型定义(其余类型定义详见getData):

js
type IDataChangeEventCallback = (data: IEventCbCtx<IDBPluginData>) => void
type IOffCallback = () => void

onConfigChange

📌 为了维持与WebOffice图表相近的协同体验,插件图表在整个生命周期均需持有该监听方法

监听配置的变化:

js
onConfigChange: (handler: IConfigChangeEventCallback) => IOffCallback

类型定义(其余类型定义详见getConfig):

js
type IConfigChangeEventCallback = (data: IEventCbCtx<IDBPluginConfig>) => void
type IOffCallback = () => void

类型定义

字段类型定义

js
enum FieldType {
    Invalid = 'Invalid',                    // 无效类型
    Date = 'Date',                          // 日期
    Time = 'Time',                          // 时间
    Number = 'Number',                      // 数值
    Currency = 'Currency',                  // 货币
    SingleLineText = 'SingleLineText',      // 单行文本
    MultiLineText = 'MultiLineText',        // 多行文本
    Percentage = 'Percentage',              // 百分比
    ID = 'ID',                              // 身份证
    Phone = 'Phone',                        // 电话
    Email = 'Email',                        // 电子邮箱
    Url = 'Url',                            // 超链接
    Button = 'Button',                      // 按鈕
    Checkbox = 'Checkbox',                  // 复选框
    SingleSelect = 'SingleSelect',          // 单选项
    MultipleSelect = 'MultipleSelect',      // 多选项
    Cascade = 'Cascade',                    // 级联选择
    Rating = 'Rating',                      // 等级
    Complete = 'Complete',                  // 进度条
    CellPicture = 'CellPicture',            // 单元格图片
    AutoNumber = 'AutoNumber',              // 编号
    Attachment = 'Attachment',              // 图片和附件
    CreatedBy = 'CreatedBy',                // 创建者
    CreatedTime = 'CreatedTime',            // 创建时间
    Contact = 'Contact',                    // 联系人
    Automations = 'Automations',            // 触发器
    Note = 'Note',                          // 备注
    Link = 'Link',                          // 双向关联
    OneWayLink = 'OneWayLink',              // 单向关联
    Formula = 'Formula',                    // 公式
    FormulaResult = 'FormulaResult',        // 公式结果
    Lookup = 'Lookup',                      // 引用
    Address = 'Address',                    // 地址
    LastModifiedBy = 'LastModifiedBy',      // 最后修改者
    LastModifiedTime = 'LastModifiedTime',  // 最后修改时间
    SearchLookup = 'SearchLookup',          // 查找引用
    Statistic = 'Statistic',                // 统计
    ParentRecord = 'ParentRecord',          // 父记录
    Department = 'Department',              // 部门
}

筛选器类型定义

js
// 筛选面板头部的条件类型
enum FilterOpType {
    And = 'And',
    Or = 'Or'
}

// 筛选条件类型
enum FilterCriteriaOpType {
    Null = 'Null',

    // 数字,日期,文本,集合
    Equals = 'Equals',
    NotEqu = 'NotEqu',

    // 数字,日期
    Greater = 'Greater',
    GreaterEqu = 'GreaterEqu',
    Less = 'Less',
    LessEqu = 'LessEqu',
    GreaterEquAndLessEqu = 'GreaterEquAndLessEqu',
    LessOrGreater = 'LessOrGreater',

    // 文本
    BeginWith = 'BeginWith',
    NotBeginWith = 'NotBeginWith',
    EndWith = 'EndWith',
    NotEndWith = 'NotEndWith',
    Contains = 'Contains',
    NotContains = 'NotContains',

    // 集合
    Intersected = 'Intersected', // 有交集,当字段不是多选类型时,相当于 has any of,单元格集合只有一个元素
    NotIntersected = 'NotIntersected', // 没有交集,当字段不是多选类型时,相当于 has none of,单元格集合只有一个元素
    SubsetOf = 'SubsetOf',
    SupersetOf = 'SupersetOf',

    // 数字,日期,文本,集合
    Empty = 'Empty',
    NotEmpty = 'NotEmpty',

    // 数字,日期,文本,单选项
    Defined = 'Defined',
}

enum FcValueType {
    Null = 'Null',
    Text = 'Text',
    Num = 'Num',
    Date = 'Date',
    Time = 'Time',
    Any = 'Any',
    Link = 'Link',
    Contact = 'Contact',
    SelectItem = 'SelectItem',

    // 动态时间
    DynamicNone = 'DynamicNone',
    DynamicSimple = 'DynamicSimple',
    DynamicThreshold = 'DynamicThreshold',
}

enum DbFcDynamicType {
    CurUser = "CurUser",
    FixedDate = 'FixedDate',
    Today = 'Today',
    Yesterday = 'Yesterday',
    Tomorrow = 'Tomorrow',
    ThisWeek = 'ThisWeek',
    LastWeek = 'LastWeek',
    NextWeek = 'NextWeek',
    ThisMonth = 'ThisMonth',
    LastMonth = 'LastMonth',
    NextMonth = 'NextMonth',
    Last7Days = 'Last7Days',
    Last30Days = 'Last30Days',
    CurDept = "CurDept", // 企业账号下部门筛选显示当前部门
}

enum SourceType {
    ALL = "ALL"
}

interface IFilterInfo {
    fieldId: string; // 字段id
    filterId?: string; // 多条件筛选需要
    criteria?: ICriteria;
}

interface ICriteria {
    op: FilterCriteriaOpType; // 操作符
    values: IValue[];
}

interface IBaseValue<T extends FcValueType = FcValueType> {
    type: T,
}

interface IValue extends IBaseValue {
    value?: string | number,
    dynamicType?: DbFcDynamicType,
    content?: string,
    text?: string,
}

暗黑模式适配&多主题适配(基于DashboardAPI)

暗黑模式适配

获取仪表盘当前主题:

js
getThemeMode: () => Promise<Theme>

监听仪表盘主题变化:

js
onThemeChange: (listenerTListener) => void

类型定义:

js
enum Theme {
  dark = "dark",
  light = "light",
}

type TListener = (theme: Theme) => void

明亮模式:

暗黑模式:

多主题适配

开发中......