跳到主要内容
版本:7.2.0

自定义筛选器插件开发指南

1. 概述

自定义筛选器插件需要在插件代码中完成编写和注册,只要插件代码中通过 openSDK 向 BI 注册了自定义筛选器,在仪表板中新建筛选器时就可以选择和配置对应的自定义筛选器。

本文将通过多选表格交互筛选器的案例来指导大家如何编写自定义筛选器插件代码。

2. 使用指导

观远提供自定义筛选器插件代码模板,您可通过插件代码模板进行拓展开发。

自定义筛选器插件开发操作步骤如下:

  1. 参照插件代码模板编写组件基础结构
  2. 基于WebComponent 和 Lit 框架,实现组件渲染
  3. 通过 BI 提供的方法和属性,进行数据加载
  4. 用户操作后,将指定格式的筛选条件提交给 BI
  5. 下次打开时,解析并处理之前保存的筛选条件
  6. 完成配置表单的 Schema 编写,以及应用用户在配置表单中填写的值
  7. 补充操作细节,完整代码

3. 开发案例:多选表格交互筛选器

本章将通过代码示例展示如何通过筛选器插件能力来开发一个多选表格交互的筛选器。

筛选器需求

需要实现的功能

  1. 将筛选器「使用字段」中的所有字段都列在表格中并按行勾选,可勾选多行,对应的所有「联动字段」的值作为筛选条件;
  2. 数据量较大时可以分页,每页的行数可以在配置筛选器时由用户自行配置。

筛选器配置

筛选器使用效果

代码示例

步骤一:编写组件基础结构

这部分内容基于插件代码模板扩展,包含了:

  1. 基于 Web Component 和 Lit 框架的筛选器组件 MultiSelectTable

  2. 加载 Lit、Shoelace 等所需资源,注册 Web Component;

    • 资源加载方式和注册时机可按需调整;
    • Shoelace 的组件有自动的按需加载功能,<sl-element> 初次渲染时会自动发送请求拉取一些代码。正常使用无需注意,但在其他 shadow root 内部是不会生效,所以插件需要手动调用一下 shoelace-autoloader 内的 discover 方法,详细信息见这里
    • discover 需要注意调用时机。如果有些 Shoelace 组件仅当有数据时才会渲染,可能会错过首次渲染的 discover 时间点;开发时可以考虑多次调用 shoelaceAutoloader.discover,或者额外添加一个 display: none 的元素供 discover 使用;
    • Shoelace 组件按需加载时会动一下,可以适当给一个 CSS 样式遮挡,参考这里
  3. 通过注册自定义筛选器GD.registerCustomSelector向 BI 注册插件,注册时请注意:

    • 系统内 id 和 tagName 不可重复,系统内 id 和 tagname 可通过获取自定义筛选器GD.getCustomSelectors获得;
    • tagName 也需要注意不要和前端页面改造中出现的元素名称重复,否则可能会实现不需要的效果;
    • 如果修改 id,仪表板中已创建的自定义筛选器卡片可能会失效。
const registerMultiSelectTable = async () => {
// 加载必要的资源
await GD.asyncLoadScript(GD.getResourceUrl('lit'), { type: 'module' })
await GD.asyncLoadScript(GD.getResourceUrl('shoelace'), { type: 'module' })
await GD.asyncLoadScript(GD.getResourceUrl('shoelace-theme-light'), { tagName: 'link' })
await GD.asyncLoadScript(GD.getResourceUrl('shoelace-theme-dark'), { tagName: 'link' })

const { LitElement, html, css } = window.lit
const { discover } = window.shoelaceAutoloader || {}

// 定义 Web Component 类
class MultiSelectTable extends LitElement {
static properties = {
// 联动字段
linkColumns: { type: Array },
// 使用字段
allColumns: { type: Array },
// 获取明细表数据
fetchData: { type: Function },
// 获取单个字段的可选值列表
fetchFieldValues: { type: Function },
// 获取多个字段的可选值列表
fetchTreeValues: { type: Function },
// 输出筛选值,类型为组合筛选器的类型,根据联动字段自己拼接
onConfirm: { type: Function },
// 初始化筛选值,类型和 onConfirm 参数一致
initialFilterValue: { type: Array },
// 分页属性
pageSize: { type: Number },
currentPage: { type: Number },
total: { type: Number },
};

constructor() {
super();
// 初始化属性
this.linkColumns = [];
this.allColumns = [];
this.fetchData = null;
this.onConfirm = null;
this.initialFilterValue = [];
this.pageSize = 10;
this.currentPage = 1;
this.total = 0;
this.data = [];
// 用于存储选中的数据
this._selectedData = new Map();
}

static styles = css`
/* 基本样式定义 */
table {
width: 100%;
border-collapse: collapse;
}
/* ... */
`;

// 生命周期方法:组件首次更新
firstUpdated() {
super.firstUpdated()
// 加载初始数据
this.refreshData()
// 激活 Shoelace 组件
discover?.(this.shadowRoot)
}

// 组件渲染方法
render() {
// 渲染实现将在后续步骤添加
return html``;
}
}

// 注册 Web Component
customElements.define('multi-select-table', MultiSelectTable);
}

// 配置表单定义,具体实现将在后续步骤添加
const configSchema = []

// 注册插件
GD.registerCustomSelector({
id: 'multi-select-table-selector',
title: '多选表格筛选器',
tagName: 'multi-select-table',
desc: '通过表格展示数据,支持多选的筛选器',
configFormSchema: configSchema,
load: registerMultiSelectTable
})

步骤二:实现表格渲染

  • 使用 Lit 框架时,组件渲染由 render 方法负责,表格渲染的主体部分在这里实现;
  • 由于在代码开头已经引用了 Lit 和  Shoelace ,这里可以使用 html`` 模板语法、@sl-change 等 Lit 语法特性,以及 <sl-checkbox> 等 Shoelace 组件。
render() {
const totalPages = Math.ceil(this.total / this.pageSize);

return html`
<div>
<table>
<thead>
<tr>
<th>
<sl-checkbox
?checked="${this.isCurrentPageAllSelected()}"
@sl-change="${this.handleSelectAll}"
></sl-checkbox>
</th>
${this.allColumns.map((col) => html`
<th>${col.name}</th>
`)}
</tr>
</thead>
<tbody>
${this.data.map((item, index) => {
const globalIndex = (this.currentPage - 1) * this.pageSize + index;
return html`
<tr>
<td>
<sl-checkbox
?checked="${this._selectedData.has(globalIndex)}"
@sl-change="${(e) => this.handleCheckboxChange(e, item, globalIndex)}"
></sl-checkbox>
</td>
${this.allColumns.map((col, colIndex) => html`
<td>${item[colIndex]}</td>
`)}
</tr>
`;
})}
</tbody>
</table>
<div class="pagination">
<sl-button
?disabled="${this.currentPage === 1}"
@click="${() => this.handlePageChange(this.currentPage - 1)}"
>上一页</sl-button>
<span>第 ${this.currentPage} 页,共 ${totalPages} 页</span>
<sl-button
?disabled="${this.currentPage === totalPages}"
@click="${() => this.handlePageChange(this.currentPage + 1)}"
>下一页</sl-button>
</div>
<div class="actions">
<sl-button variant="primary" @click="${this.handleSubmit}">确定</sl-button>
</div>
</div>
`;
}

步骤三:实现数据加载功能

为了获取表格明细数据,这里需要使用 fetchData 方法 请求数据,再读取使用字段 allColumns  和初始化联动条件 initialFilterValue 等属性来将数据写入到组件状态中。

这些在组件中可访问的属性和方法的细节可以在这里查阅

// 在 registerMultiSelectTable 类中添加 refreshData 方法
async refreshData() {
if (!this.fetchData) return;

try {
const offset = (this.currentPage - 1) * this.pageSize;
const result = await this.fetchData({
limit: this.pageSize,
offset: offset
});

// 更新总数据量
this.total = result.rowCount || 0;

// 映射数据列
const columnIndexMap = this.allColumns.map(targetCol =>
result.columns.findIndex(col => col.fdId === targetCol.fdId)
);

// 根据映射重组数据
this.data = result.data.map(row =>
columnIndexMap.map(index => index >= 0 ? row[index] : '')
);

// 解析 initialFilterValue,处理初始值
if (...) {
this.parseInitialFilterValue()
}

this.requestUpdate();
} catch (error) {
console.error('获取数据失败:', error);
}
}

步骤四:处理筛选条件提交

由于功能需要满足多行勾选 + 多字段,最终生成的筛选条件格式也很复杂,这里需要有一段代码来处理表格选中数据和输出数据结构(组合筛选器格式)相互格式转换。

最终会使用 onConfirm 方法来提交当前组合好的筛选条件。

// 在 registerMultiSelectTable 类中添加 handleSubmit 等方法

// 处理提交按钮点击
handleSubmit() {
// 获取所有选中行的数据
const selectedData = Array.from(this._selectedData.values()).map(selectedItem => {
return this.linkColumns.map(col => {
const colIndex = this.allColumns.findIndex(allCol => allCol.fdId === col.fdId);
return colIndex >= 0 ? selectedItem[colIndex] : '';
});
});

// 对数据进行去重
const uniqueSelectedData = /* 数据去重逻辑 */;

// 转换为过滤器结构
const filterStructure = this.transformToFilterStructure(uniqueSelectedData);

// 调用外部传入的 onConfirm
if (this.onConfirm) {
this.onConfirm(filterStructure);
}
}

// 生成随机ID (用于筛选条件)
generateId(length = 6) {
// ...
}

// 转换为筛选器数据格式
transformToFilterStructure(selectedData) {
// 如果没有选中数据,返回空数组
if (!selectedData.length) return [];

// 创建顶层组合
const topComposition = {
type: 'composition',
value: []
};

// 处理每组选中数据
selectedData.forEach((group, groupIndex) => {
/*
1. 检查是单字段还是多字段条件
2. 构建适当的条件节点
3. 添加条件节点到组合中
4. 添加适当的操作符(and/or)
*/
});

return [topComposition];
}

步骤五:解析并处理筛选条件

onConfirm 提交的筛选条件保存在 BI 中,下次组件打开时可以从 initialFilterValue  属性中读取到,需要再回写到组件中的状态 this._selectedData。

// 这部分相对复杂,使用伪代码表示
parseInitialFilterValue() {
/*
1. 检查初始值格式是否正确
2. 解析顶层组合中的条件
3. 区分 AND 和 OR 组合
4. 将条件映射为选中状态
*/
}

checkCurrentPageMatches() {
/*
1. 计算当前页的全局索引
2. 遍历当前页数据
3. 检查每行数据是否匹配筛选条件
4. 匹配则标记为选中
*/
}

步骤六:完成配置表单定义

  • 使用方法和可视化插件的图表配置项一致;
  • 基于一个约定式的配置描述,产出一个可交互的UI,用于构建目标对象;
  • 在插件开发中用于生成插件配置,也可以用于展示一些提示信息。
// 配置表单定义
const configSchema = [
{
fieldName: 'pageSize',
label: '每页显示条数',
type: "NUMBER",
defaultValue: 10,
placeholder: '请输入每页显示的数据条数',
rules: [
{
required: true,
message: '请输入每页显示的数据条数'
},
{
type: 'number',
min: 1,
max: 100,
message: '每页显示条数需在1-100之间'
}
]
}
]

步骤七:完整代码

const registerMultiSelectTable = async () => {
// 加载必要的资源
await GD.asyncLoadScript(GD.getResourceUrl('lit'), { type: 'module' })
await GD.asyncLoadScript(GD.getResourceUrl('shoelace'), { type: 'module' })
await GD.asyncLoadScript(GD.getResourceUrl('shoelace-theme-light'), { tagName: 'link' })
await GD.asyncLoadScript(GD.getResourceUrl('shoelace-theme-dark'), { tagName: 'link' })

const { LitElement, html, css } = window.lit
const { discover } = window.shoelaceAutoloader || {}

// 定义 Web Component 类
class MultiSelectTable extends LitElement {
static properties = {
// 联动字段
linkColumns: { type: Array },
// 使用字段
allColumns: { type: Array },
// 获取明细表数据
fetchData: { type: Function },
// 获取单个字段的可选值列表
fetchFieldValues: { type: Function },
// 获取多个字段的可选值列表
fetchTreeValues: { type: Function },
// 输出筛选值,类型为组合筛选器的类型,根据联动字段自己拼接
onConfirm: { type: Function },
// 初始化筛选值,类型和 onConfirm 参数一致
initialFilterValue: { type: Array },
// 自定义配置
customConfig: { type: Object },
// 分页属性
pageSize: { type: Number },
currentPage: { type: Number },
total: { type: Number },
};

constructor() {
super();
// 初始化属性
this.linkColumns = [];
this.allColumns = [];
this.fetchData = null;
this.fetchFieldValues = null;
this.fetchTreeValues = null;
this.onConfirm = null;
this.initialFilterValue = [];
this.customConfig = {};
this.pageSize = 10;
this.currentPage = 1;
this.total = 0;
this.data = [];
// 用于存储选中的数据
this._selectedData = new Map();
this._initialFilterValueParsed = false;
this._filterConditions = null;
this._checkedIndices = new Set();
}

static styles = css`
:host {
display: block;
font-family: var(--sl-font-sans);
}

table {
width: 100%;
border-collapse: collapse;
margin-bottom: 16px;
}

th, td {
padding: 8px;
text-align: left;
border: 1px solid var(--sl-color-neutral-300);
}

th {
background-color: var(--sl-color-neutral-100);
font-weight: var(--sl-font-weight-semibold);
}

tr:nth-child(even) {
background-color: var(--sl-color-neutral-50);
}

.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 16px;
gap: 8px;
}

.pagination-info {
margin: 0 16px;
color: var(--sl-color-neutral-700);
}

.actions {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}

sl-checkbox {
--sl-input-border-color: var(--sl-color-neutral-400);
}

sl-button::part(base) {
cursor: pointer;
}
`;

// 生命周期方法:组件首次更新
firstUpdated() {
super.firstUpdated();

// 设置页面大小
if (this.customConfig && this.customConfig.pageSize) {
this.pageSize = this.customConfig.pageSize;
}

// 加载初始数据
this.refreshData();

// 激活 Shoelace 组件
discover?.(this.shadowRoot);
}

async refreshData() {
if (!this.fetchData) return;

try {
const offset = (this.currentPage - 1) * this.pageSize;
const result = await this.fetchData({
limit: this.pageSize,
offset: offset
});

// 更新总数据量
this.total = result.rowCount || 0;

// 映射数据列
const columnIndexMap = this.allColumns.map(targetCol =>
result.columns.findIndex(col => col.fdId === targetCol.fdId)
);

// 根据映射重组数据
this.data = result.data.map(row =>
columnIndexMap.map(index => index >= 0 ? row[index] : '')
);

// 解析初始筛选值
if (this.initialFilterValue?.length > 0 && !this._initialFilterValueParsed) {
this.parseInitialFilterValue();
this._initialFilterValueParsed = true;
}

// 检查当前页数据是否匹配筛选条件
if (this._filterConditions) {
this.checkCurrentPageMatches();
}

this.requestUpdate();
} catch (error) {
console.error('获取数据失败:', error);
}
}

// 检查当前页是否全选
isCurrentPageAllSelected() {
if (!this.data.length) return false;
const start = (this.currentPage - 1) * this.pageSize;
return this.data.every((_, index) => this._selectedData.has(start + index));
}

// 处理全选/取消全选
handleSelectAll(e) {
const start = (this.currentPage - 1) * this.pageSize;
if (e.target.checked) {
// 选中当前页所有数据
this.data.forEach((item, index) => {
this._selectedData.set(start + index, item);
});
} else {
// 取消选中当前页所有数据
this.data.forEach((_, index) => {
this._selectedData.delete(start + index);
});
}
this.requestUpdate();
}

// 处理单个选择框变化
handleCheckboxChange(e, item, globalIndex) {
if (e.target.checked) {
this._selectedData.set(globalIndex, item);
} else {
this._selectedData.delete(globalIndex);
}
this.requestUpdate();
}

// 处理页面切换
async handlePageChange(newPage) {
this.currentPage = newPage;
await this.refreshData();
}

parseInitialFilterValue() {
const topComposition = this.initialFilterValue[0];
if (topComposition.type !== 'composition') return;

// 处理顶层 OR 组合
const orGroups = [];
let currentGroup = [];

for (const item of topComposition.value) {
if (item.type === 'operator' && item.value === 'or') {
if (currentGroup.length > 0) {
orGroups.push([...currentGroup]);
currentGroup = [];
}
} else if (item.type === 'composition') {
// 处理 AND 组合
const conditions = item.value.filter(v => v.type === 'condition');
currentGroup.push(conditions);
} else if (item.type === 'condition') {
// 单个条件的情况
currentGroup.push([item]);
}
}

// 添加最后一组
if (currentGroup.length > 0) {
orGroups.push(currentGroup);
}

// 保存解析后的过滤条件
this._filterConditions = orGroups;
}

checkCurrentPageMatches() {
const offset = (this.currentPage - 1) * this.pageSize;

// 检查当前页的每一行数据
this.data.forEach((row, rowIndex) => {
const globalIndex = offset + rowIndex;

// 如果这一行已经检查过了,就跳过
if (this._checkedIndices.has(globalIndex)) {
return;
}

// 标记为已检查
this._checkedIndices.add(globalIndex);

// 检查是否匹配过滤条件
const matchesAnyOrGroup = this._filterConditions.some(andGroup => {
return andGroup.every(conditions => {
return conditions.every(condition => {
const colIndex = this.allColumns.findIndex(col =>
col.fdId === condition.value.fdId
);
if (colIndex === -1) return false;

const rowValue = row[colIndex];
return condition.value.filterValue.includes(rowValue);
});
});
});

// 只有匹配时才写入数据
if (matchesAnyOrGroup) {
this._selectedData.set(globalIndex, row);
}
});
}

// 处理提交按钮点击
handleSubmit() {
// 获取所有选中行的数据
const selectedData = Array.from(this._selectedData.values()).map(selectedItem => {
return this.linkColumns.map(col => {
const colIndex = this.allColumns.findIndex(allCol => allCol.fdId === col.fdId);
return colIndex >= 0 ? selectedItem[colIndex] : '';
});
});

// 对数据进行去重
const uniqueSelectedData = selectedData.filter((item, index) => {
const stringifiedItem = JSON.stringify(item);
return selectedData.findIndex(elem => JSON.stringify(elem) === stringifiedItem) === index;
});

// 转换为过滤器结构
const filterStructure = this.transformToFilterStructure(uniqueSelectedData);

// 调用外部传入的 onConfirm
if (this.onConfirm) {
this.onConfirm(filterStructure);
}
}

// 生成随机ID (用于筛选条件)
generateId(length = 6) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}

// 转换为筛选器数据格式
transformToFilterStructure(selectedData) {
// 如果没有选中数据,返回空数组
if (!selectedData.length) return [];

// 创建顶层组合
const topComposition = {
type: 'composition',
value: []
};

// 处理每组选中数据
selectedData.forEach((group, groupIndex) => {
// 如果只有一个字段,直接创建条件节点
if (this.linkColumns.length === 1) {
const column = this.linkColumns[0];
const condition = {
id: this.generateId(),
type: 'condition',
parentId: this.generateId(),
value: {
...column,
filterType: 'IN',
filterValue: [group[0]],
}
};

// 添加条件节点到顶层
topComposition.value.push(condition);

// 如果不是最后一组,添加 OR 操作符
if (groupIndex < selectedData.length - 1) {
topComposition.value.push({
type: 'operator',
value: 'or'
});
}
return;
}

// 多个字段时创建内层组合
const innerComposition = {
type: 'composition',
value: []
};

// 为每个字段创建条件
group.forEach((value, index) => {
const column = this.linkColumns[index];

// 创建条件节点
const condition = {
id: this.generateId(),
type: 'condition',
parentId: this.generateId(),
value: {
...column,
filterType: 'IN',
filterValue: [value],
}
};

// 添加条件节点
innerComposition.value.push(condition);

// 如果不是最后一个字段,添加 AND 操作符
if (index < group.length - 1) {
innerComposition.value.push({
type: 'operator',
value: 'and'
});
}
});

// 添加内层组合到顶层
topComposition.value.push(innerComposition);

// 如果不是最后一组,添加 OR 操作符
if (groupIndex < selectedData.length - 1) {
topComposition.value.push({
type: 'operator',
value: 'or'
});
}
});

return [topComposition];
}

// 组件渲染方法
render() {
const totalPages = Math.ceil(this.total / this.pageSize);

return html`
<div>
<table>
<thead>
<tr>
<th>
<sl-checkbox
?checked="${this.isCurrentPageAllSelected()}"
@sl-change="${this.handleSelectAll}"
></sl-checkbox>
</th>
${this.allColumns.map((col) => html`
<th>${col.name}</th>
`)}
</tr>
</thead>
<tbody>
${this.data.map((item, index) => {
const globalIndex = (this.currentPage - 1) * this.pageSize + index;
return html`
<tr>
<td>
<sl-checkbox
?checked="${this._selectedData.has(globalIndex)}"
@sl-change="${(e) => this.handleCheckboxChange(e, item, globalIndex)}"
></sl-checkbox>
</td>
${this.allColumns.map((col, colIndex) => html`
<td>${item[colIndex]}</td>
`)}
</tr>
`;
})}
</tbody>
</table>
<div class="pagination">
<sl-button
?disabled="${this.currentPage === 1}"
@click="${() => this.handlePageChange(this.currentPage - 1)}"
>上一页</sl-button>
<span class="pagination-info">第 ${this.currentPage} 页,共 ${totalPages} 页</span>
<sl-button
?disabled="${this.currentPage === totalPages || totalPages === 0}"
@click="${() => this.handlePageChange(this.currentPage + 1)}"
>下一页</sl-button>
</div>
<div class="actions">
<sl-button variant="primary" @click="${this.handleSubmit}">确定</sl-button>
</div>
</div>
`;
}
}

// 注册 Web Component
customElements.define('multi-select-table', MultiSelectTable);
}

// 配置表单定义
const configSchema = [
{
fieldName: 'pageSize',
label: '每页显示条数',
type: "NUMBER",
defaultValue: 10,
placeholder: '请输入每页显示的数据条数',
rules: [
{
required: true,
message: '请输入每页显示的数据条数'
},
{
type: 'number',
min: 1,
max: 100,
message: '每页显示条数需在1-100之间'
}
]
}
]

// 注册插件
GD.registerCustomSelector({
id: 'multi-select-table-selector',
title: '多选表格筛选器',
tagName: 'multi-select-table',
desc: '通过表格展示数据,支持多选的筛选器,可配置页面大小',
configFormSchema: configSchema,
load: registerMultiSelectTable
})

4. 参考材料

插件代码模板

constregisterCustomSelector=async()=>{
//加载BI平台提供的资源
awaitGD.asyncLoadScript(GD.getResourceUrl('lit'),{type:'module'})
awaitGD.asyncLoadScript(GD.getResourceUrl('shoelace'),{type:'module'})
awaitGD.asyncLoadScript(GD.getResourceUrl('shoelace-theme-light'),{tagName:'link'})
awaitGD.asyncLoadScript(GD.getResourceUrl('shoelace-theme-dark'),{tagName:'link'})

const{LitElement,html,css}=window.lit
const{discover}=window.shoelaceAutoloader||{}

//定义WebComponent类SelectableTable
classSelectableTableextendsLitElement{
staticproperties={
//联动字段
linkColumns:{type:Array},
//使用字段
allColumns:{type:Array},
//获取明细表数据
fetchData:{type:Function},
//获取单个字段的可选值列表
fetchFieldValues:{type:Function},
//获取多个字段的可选值列表
fetchTreeValues:{type:Function},
//输出筛选值,类型为组合筛选器的类型,根据联动字段自己拼接
onConfirm:{type:Function},
//初始化筛选值,类型和onConfirm参数一致
initialFilterValue:{type:Array},
};

constructor(){
super();
this.linkColumns=[];
this.allColumns=[];
this.fetchData=null;
this.onConfirm=null;
this.initialFilterValue=[];
}

staticstyles=css`
.custom-selector-container{
width:100%;
height:100%;
}
`;

firstUpdated(){
super.firstUpdated()
//如果使用shoelace组件,需要调用autoloader.discover方法触发shoelace组件的自动加载
discover?.(this.shadowRoot)

}

render(){
returnhtml`
<divclass="custom-selector-container">
自定义筛选器
</div>
`;
}
}

//将WebComponent注册到当前页面
customElements.define('define-your-tagName',SelectableTable);
}

//用于展示文本说明和填充自定义配置
constlegoDefs=[
//配置格式参考https://guandata.yuque.com/guandataweb/dsebtl/iefgq3
]

//将WebComponent和自定义配置表单注册到BI平台
GD.registerCustomSelector({
id:'unique-id',
title:'插件标题展示',
tagName:'define-your-tagName',
desc:'插件功能描述',
configFormSchema:legoDefs,
load:registerCustomSelector
})

WebComponent

  • 使用自定义元素-MDN文档
  • 自定义筛选器的实现原理为,先在插件代码中通过customElements.define定义WebComponet,再由BI平台渲染到对应筛选器卡片中。

Lit

  • Lit框架官方文档
  • WebComponent开发框架,大幅提升开发体验;
  • BI平台提供v3.2.1版本,进行了一些修改和适配,需要通过GD.asyncLoadScriptGD.getResourceUrl('lit')引入;
  • 需注意BI平台插件代码中不支持Decorator语法,原因是该语法需要Typescript或Babel生效;其余语法没有限制。
await GD.asyncLoadScript(GD.getResourceUrl('lit'),{type:'module'})

Shoelace

//即便不使用autoloader.discover也要以type:module引用
await GD.asyncLoadScript(GD.getResourceUrl('shoelace'),{type:'module'})
//light和dark样式需要同时引入
await GD.asyncLoadScript(GD.getResourceUrl('shoelace-theme-light'),{tagName:'link'})
await GD.asyncLoadScript(GD.getResourceUrl('shoelace-theme-dark'),{tagName:'link'})

支持访问的属性和方法

classSelectableTableextendsLitElement{
staticproperties={
//联动字段
linkColumns:{type:Array},
//使用字段
allColumns:{type:Array},
//获取明细表数据
fetchData:{type:Function},
//获取单个字段的可选值列表
fetchFieldValues:{type:Function},
//获取多个字段的可选值列表
fetchTreeValues:{type:Function},
//输出筛选值,类型为组合筛选器的类型,根据联动字段自己拼接
onConfirm:{type:Function},
//初始化筛选值,类型和onConfirm参数一致
initialFilterValue:{type:Array},
};

//...
}

属性

这些属性会作为HTML元素的属性,以JSON字符串的形式传入元素,可以直接在HTML里看到;

如果自定义元素使用了Lit框架,组件内部就不需要处理JSON字符串的问题,直接访问this.customConfig就是正常的JSON结构。

自定义设置内容customConfig

用户填写的自定义设置内容,格式为JSON;

组件注册时的configFormSchema内容会生成表单供用户填写,填写的结果就从这里得到。

使用字段allColumns

筛选器新建和编辑时填写的【使用字段】,可能是一个或多个;

使用字段allColumns仅提供给插件消费,不影响BI功能如联动、被联动等。

示例

{
allColumns:[
{
"fdId":"cbe5217f06de44f0f908232a",
"name":"省份",
"fdType":"STRING",
"metaType":"DIM",
"isAggregated":false,
"calculationType":"normal",
"level":"dataset",
"annotation":"",
"dsId":"k5fa308879b364e5bb628f56"
},
{/*...*/}
]
}
联动字段linkColumns

筛选器新建和编辑时填写的【联动字段】,可能是一个或多个;

联动字段linkColumns一定是使用字段allColumns的子集;

联动字段和其他筛选器的联动字段作用基本一致。自定义筛选器联动其他卡片时,需要给每个联动字段设置关联;

自定义筛选器提交联动条件时,也需要和联动字段linkColumns相匹配。

示例

{
linkColumns:[
{
"fdId":"cbe5217f06de44f0f908232a",
"name":"省份",
"fdType":"STRING",
"metaType":"DIM",
"isAggregated":false,
"calculationType":"normal",
"level":"dataset",
"annotation":"",
"dsId":"k5fa308879b364e5bb628f56"
},
{/*...*/}
]
}
初始化联动条件initialFilterValue

筛选器组件初始化时的联动条件,来自于筛选器组件上一次onConfirm时的输出,和onConfirm项对应;

联动条件的格式基于组合筛选器的格式,类型为ICombinationFilter;

为确保使用体验,筛选器组件初始化时,需要读取上次设置的筛选条件并展示出来;很可能需要实现联动条件格式ICombinationFilter和筛选器组件内部状态的双向转换,可以把以下内容喂给AI,用AI实现。

  • ICombinationFilter具体如下:
enumIConditionType{
CONDITION='condition',
OPERATOR='operator',
COMPOSITION='composition',//嵌套组合
}
typeIConditionSimple={
not:boolean,
type:IConditionType.CONDITION,
value:any
}
typeIConditionOperator={
type:IConditionType.OPERATOR
value:'OR'|'AND'
}
typeIConditionComposition={
type:IConditionType.COMPOSITION
value:(IConditionSimple|IConditionOperator|IConditionComposition)[]
}
typeICondition=(IConditionSimple|IConditionOperator|IConditionComposition)[]

interfaceICombinationFilter{
name?:string
condition:ICondition
sourceCdId?:string
}

//实际筛选条件示例
constfilterValue:ICombinationFilter={
"condition":[
{
"type":"composition",
"value":[
{
"type":"composition",
"value":[
{
"id":"YetDdI",
"type":"condition",
"parentId":"EjXPJT",
"value":{
"name":"省份",
"fdType":"STRING",
"dsId":"k5fa308879b364e5bb628f56",
"fdId":"cbe5217f06de44f0f908232a",
"metaType":"DIM",
"seqNo":1,
"isAggregated":false,
"calculationType":"normal",
"level":"dataset",
"annotation":"",
"isDetectionSensitive":false,
"isSensitive":false,
"filterType":"IN",
"filterValue":[
"四川省"
],
"cdId":"tbb94c8b9bfbd43f49ce58b2"
}
},
{
"type":"operator",
"value":"and"
},
{
"id":"ywfkWx",
"type":"condition",
"parentId":"EjXPJT",
"value":{
"name":"城市",
"fdType":"STRING",
"dsId":"k5fa308879b364e5bb628f56",
"fdId":"k31522e65c9ba4fac9e223d7",
"metaType":"DIM",
"seqNo":3,
"isAggregated":false,
"calculationType":"normal",
"level":"dataset",
"annotation":"",
"isDetectionSensitive":false,
"isSensitive":false,
"filterType":"IN",
"filterValue":[
"宜宾市"
],
"cdId":"tbb94c8b9bfbd43f49ce58b2"
}
}
]
},
{
"type":"operator",
"value":"or"
},
{
"type":"composition",
"value":[
{
"id":"kjmjnK",
"type":"condition",
"parentId":"PFnsKP",
"value":{
"name":"省份",
"fdType":"STRING",
"dsId":"k5fa308879b364e5bb628f56",
"fdId":"cbe5217f06de44f0f908232a",
"metaType":"DIM",
"seqNo":1,
"isAggregated":false,
"calculationType":"normal",
"level":"dataset",
"annotation":"",
"isDetectionSensitive":false,
"isSensitive":false,
"filterType":"IN",
"filterValue":[
"浙江省"
],
"cdId":"tbb94c8b9bfbd43f49ce58b2"
}
},
{
"type":"operator",
"value":"and"
},
{
"id":"guTZIF",
"type":"condition",
"parentId":"PFnsKP",
"value":{
"name":"城市",
"fdType":"STRING",
"dsId":"k5fa308879b364e5bb628f56",
"fdId":"k31522e65c9ba4fac9e223d7",
"metaType":"DIM",
"seqNo":3,
"isAggregated":false,
"calculationType":"normal",
"level":"dataset",
"annotation":"",
"isDetectionSensitive":false,
"isSensitive":false,
"filterType":"IN",
"filterValue":[
"杭州市"
],
"cdId":"tbb94c8b9bfbd43f49ce58b2"
}
}
]
}
]
}
],
"sourceCdId":"u8de5094302994ab8a251c18",
"name":"组合筛选器-1"
}
移动端渲染isMobile

是否来自移动端,bool类型。

方法

这些方法是通过JavaScript写入到元素属性,可以直接调用,如this.fetchData()

获取明细数据fetchData

功能描述

用于获取数据集明细数据。

使用说明

//参数示例
//filters和treeFilters格式同BIweb,
//基本上是column信息+filterType+filterValue
constparams={
offset:0,
limit:20,
filters:[
{
fdId:'dafd2c41b99b44068901b19c',
name:'产品名称',
fdType:'STRING',
dsId:'s39796a0f96b145e0ab48dc2',
filterType:'IN',
filterValue:['伊利酸奶']
}
],
treeFilters:[
{
cdId:'w5e7e168010b64315a052118',
filterType:'IN',
fields:[
{
fdId:'bf4121c487a164c02bd81ee8',
name:'门店',
fdType:'STRING',
dsId:'s39796a0f96b145e0ab48dc2'
},
{
fdId:'dafd2c41b99b44068901b19c',
name:'产品名称',
fdType:'STRING',
dsId:'s39796a0f96b145e0ab48dc2'
}
],
values:[
[
'一号店',
'乐事薯片'
]
],
displayValue:[
[
'一号店',
'乐事薯片'
]
]
}
]
}

constresponse=awaitthis.fetchData(params)

//返回值示例
response={
"columns":[
{
"name":"编号ID",
"fdType":"LONG",
"dsId":"k5fa308879b364e5bb628f56",
"fdId":"s946dbdd2bd644fff93c709c",
"metaType":"METRIC",

},
{
"name":"省份",
"fdType":"STRING",
"dsId":"k5fa308879b364e5bb628f56",
"fdId":"cbe5217f06de44f0f908232a",
"metaType":"DIM",
},
/*...*/
],
"data":[
//data每个元素代表明细表的一行,数组长度与columns长度相同,空值以null填充
[null,"四川省","510000","成都市","50g日本樱花盈润活肤霜"],
[null,"浙江省","330000","嘉兴市","50ml欧莱雅深层美白乳霜*日霜"],
/*...*/
],
"rowCount":4395,//总行数,并非data长度
}

注意事项

  1. 返回数据为明细数据,不支持聚合;

  2. 接口会分页,需要自行维护分页状态,单页上限不超过20000条;

  3. 筛选器被外部其他筛选器联动时,fetchData传入的参数会与外部筛选条件合并,共同生效。

  4. filters会和外部筛选条件中的filters合并;

  5. treeFilters会覆盖外部筛选条件中的treeFilters。

获取指定字段的可选值fetchFieldValues

功能描述

获取指定字段的值列表,可用于在自定义筛选器内部实现单个字段的筛选器。

使用说明

//参数示例
constparams={
"fieldQuery":{
"name":"省份",
"fdType":"STRING",
"dsId":"k5fa308879b364e5bb628f56",
"fdId":"cbe5217f06de44f0f908232a",
//...
//以上即单个column的字段
"limit":50,
"offset":0,
"search":""//用于文本搜索,同BI的选择筛选器

}
}

constresponse=awaitthis.fetchFieldValues(params)

//返回值示例
response={
"offset":0,
"limit":50,
"count":4340,
"result":[
{"value":"四川省"},
{"value":"湖北省"},
{"value":"浙江省"},
/*...*/
]
}
获取多个字段的可选值fetchTreeValues

功能描述

获取多个指定字段的值列表,可用于在自定义筛选器内部实现多个字段的树状筛选器。

使用说明

//参数示例
constparams={
"limit":50,
"offest":0,
"fieldQuerySeq":[
{
"name":"省份",
"fdType":"STRING",
"fdId":"cbe5217f06de44f0f908232a",
"dsId":"k5fa308879b364e5bb628f56",
//...
//以上即单个column的字段
},
{
"name":"城市",
"fdType":"STRING",
"fdId":"k31522e65c9ba4fac9e223d7",
"dsId":"k5fa308879b364e5bb628f56"
//...
//以上即单个column的字段
}
],
"filters":[],//同fetchData的filters
"search":"",//用于文本搜索,同BI的树状筛选器
}

constresponse=awaitthis.fetchTreeValues(params)

//返回值示例
response={
"offset":0,
"limit":50,
"count":4340,
"result":[
{
"key":"上海市_0",
"children":[
{
"key":"市辖区_1",
"displayValue":"市辖区"
}
],
"displayValue":"上海市"
},
{
"key":"四川省_0",
"children":[
{
"key":"乐山市_1",
"displayValue":"乐山市"
},
{
"key":"凉山彝族自治州_1",
"displayValue":"凉山彝族自治州"
},
{
"key":"宜宾市_1",
"displayValue":"宜宾市"
},
{
"key":"甘孜藏族自治州_1",
"displayValue":"甘孜藏族自治州"
},
//...
],
"displayValue":"四川省"
},
//...
]
}
发起联动onConfirm

功能描述

提交筛选条件,类型要求为ICombinationFilter,和组合筛选器相同(详情可见initialFilterValue的部分);

如果自定义筛选器在Popover中渲染,提交操作会关闭Popover。

使用说明

constfilterValue:ICombinationFilter=[/*...*/]

this.onConfirm(filterValue)
//无返回值

注意事项

  1. 如果提交按钮使用,提交按钮需要在插件中自行绘制;
  2. 外部操作如点击蒙层关闭Popover不会触发onConfirm提交。
文档AI助手
观远AI助手关闭