diff --git a/common/changes/@visactor/vtable/feat-filter-plugin-for-business_2025-12-18-13-35.json b/common/changes/@visactor/vtable/feat-filter-plugin-for-business_2025-12-18-13-35.json
new file mode 100644
index 000000000..3379b76db
--- /dev/null
+++ b/common/changes/@visactor/vtable/feat-filter-plugin-for-business_2025-12-18-13-35.json
@@ -0,0 +1,10 @@
+{
+ "changes": [
+ {
+ "packageName": "@visactor/vtable",
+ "comment": "fix: toggleSelectAll only in searched item. fix#4838",
+ "type": "none"
+ }
+ ],
+ "packageName": "@visactor/vtable"
+}
\ No newline at end of file
diff --git a/packages/vtable-plugins/demo/filter/filter.ts b/packages/vtable-plugins/demo/filter/filter.ts
index e3dea746d..7998f421a 100644
--- a/packages/vtable-plugins/demo/filter/filter.ts
+++ b/packages/vtable-plugins/demo/filter/filter.ts
@@ -40,312 +40,403 @@ const generateDemoData = (count: number, prefix: string) => {
};
export function createTable() {
- const records = generateDemoData(50, '第一次');
- const columns: VTable.ColumnsDefine = [
- {
- field: 'id',
- title: 'ID',
- width: 60,
- sort: true
+ const columnStyle = {
+ textAlign: 'center',
+ borderColor: ['rgba(63,63,86,0)', null, null, null],
+ borderLineWidth: [1, 0, 0, 0],
+ borderLineDash: [null, null, null, null],
+ padding: [0, 0, 0, 0],
+ hover: {
+ cellBgColor: 'rgba(186, 215, 255, 0.7)',
+ inlineRowBgColor: 'rgba(186, 215, 255, 0.3)',
+ inlineColumnBgColor: 'rgba(186, 215, 255, 0.3)'
},
- {
- field: 'name',
- title: '姓名',
- width: 120,
- sort: true,
- headerIcon: {
- type: 'svg',
- svg: '',
- width: 20,
- height: 20,
- name: 'name-icon',
- positionType: VTable.TYPES.IconPosition.absoluteRight,
- marginRight: 30
- }
- },
- {
- field: 'gender',
- title: '性别',
- width: 100
- },
- // {
- // field: 'avatar',
- // title: '头像',
- // width: 100,
- // cellType: 'image',
- // keepAspectRatio: true
- // },
- {
- field: 'salary',
- title: '薪资',
- width: 120,
- sort: true,
- fieldFormat(record) {
- return '¥' + record.salary;
- }
- },
- {
- field: 'sales',
- title: '销售额',
- width: 150,
- sort: true,
- fieldFormat(record) {
- return '¥' + record.sales;
- }
- },
- {
- field: 'seniority',
- title: '工龄',
- width: 100,
- sort: true
- },
- {
- field: 'department',
- title: '部门',
- width: 100
- },
- // {
- // field: 'favoriteColor',
- // title: '喜好',
- // width: 120,
- // style: {
- // bgColor: (args: any) => args.value
- // }
- // },
- {
- field: 'status',
- title: '状态',
- width: 120,
- style: {
- textAlign: 'center'
- // color: (args: any) => {
- // const { value } = args;
- // if (value === '在职') {
- // return '#7ed321';
- // }
- // if (value === '请假') {
- // return '#f5a623';
- // }
- // return '#ff5a5f'; // 离职
- // }
- }
- },
- {
- field: 'isSelected',
- title: '选择',
- width: 100,
- cellType: 'checkbox'
- },
- {
- field: 'option',
- title: '选项',
- width: 100,
- cellType: 'radio'
- }
- ];
-
- const filterPlugin = new FilterPlugin({
- filterIcon: {
- name: 'filter-icon',
- type: 'svg',
- width: 20,
- height: 20,
- positionType: VTable.TYPES.IconPosition.right,
- cursor: 'pointer',
- marginRight: 4,
- svg: ``
+ fontFamily: 'D-DIN',
+ fontSize: 12,
+ fontStyle: 'normal',
+ fontWeight: 'normal',
+ fontVariant: 'normal',
+ color: 'rgba(255,255,255,1)',
+ lineHeight: 18,
+ underline: false
+ };
+ const headerStyle = {
+ textAlign: 'center',
+ borderColor: [null, null, null, null],
+ borderLineWidth: [null, 0, 0, 0],
+ borderLineDash: [null, null, null, null],
+ padding: [0, 0, 0, 0],
+ hover: {
+ cellBgColor: 'rgba(0, 100, 250, 0.16)',
+ inlineRowBgColor: 'rgba(255, 255, 255, 0)',
+ inlineColumnBgColor: 'rgba(255, 255, 255, 0)'
},
- filteringIcon: {
- name: 'filtering-icon',
- type: 'svg',
- width: 20,
- height: 20,
- positionType: VTable.TYPES.IconPosition.right,
- cursor: 'pointer',
- marginRight: 4,
- svg: ``
+ frameStyle: {
+ borderColor: [null, null, null, null],
+ borderLineWidth: 2
},
- styles: {
- filterMenu: {
- display: 'none',
- position: 'absolute',
- backgroundColor: '#0E1119',
- border: '0px solid #272A30',
- boxShadow: '0 4px 8px rgba(0,0,0,0.15)',
- zIndex: '99999',
- borderRadius: '4px',
+ fontFamily: 'SourceHanSansCN-Normal',
+ fontSize: 12,
+ fontVariant: 'normal',
+ fontStyle: 'normal',
+ fontWeight: 'bold',
+ color: '#FFFFFF',
+ bgColor: '#0e305c',
+ lineHeight: 18,
+ underline: false
+ };
+ // filter特殊配置:
+ // 1. syncFilterItemsState:
+ // - 配置为false, 表示:
+ // - 筛选面板不同步数据, 用户配置什么条件筛选器就回显什么条件, 筛选结果为多个筛选器共同作用的结果
+ // - 对于值筛选而言, 配置筛选且生效后, 更新数据, 则新数据不会被自动加到已勾选配置中, 即不被选中
+ // 2. styles: 自定义样式
+ // 3. conditionCategories: 条件筛选的筛选类型
+ // 4. checkboxItemFormat: 通过回调保证筛选项展示为原始值
+ const getTableFilterPluginAttrFromProps = () => {
+ const filterProps = {
+ visible: false,
+ fillColor: '#0E1119',
+ strokeColor: '#272A30',
+ strokeWidth: 0,
+ borderRadius: 4,
+ highlightColor: '#006EFF',
+ defaultColor: '#FFF',
+ textStyle: {
color: '#FFF',
fontFamily: 'SourceHanSansCN-Normal',
- fontSize: '12px'
- },
- filterPanel: {
- padding: '10px',
- display: 'block'
- },
- searchContainer: {
- padding: '5px'
- },
- searchInput: {
- width: '100%',
- padding: '8px 10px',
- border: '1px solid #272a30',
- borderRadius: '4px',
- fontSize: '14px',
- boxSizing: 'border-box',
- backgroundColor: '#0e1119',
- color: 'red'
- },
- optionsContainer: {
- maxHeight: '200px',
- overflowY: 'auto',
- marginTop: '10px'
- },
- optionItem: {
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: '8px 5px',
- color: 'red'
- },
- optionLabel: {
- display: 'flex',
- alignItems: 'center',
+ fontSize: 12
+ }
+ };
+ const {
+ fillColor,
+ strokeColor,
+ strokeWidth,
+ borderRadius,
+ highlightColor,
+ defaultColor,
+ textStyle: { color: textColor, fontFamily: textFontFamily, fontSize: textFontSize, fontWeight: fontFontWeight }
+ } = filterProps;
+ return {
+ syncFilterItemsState: false,
+ filterModes: ['byValue', 'byCondition'],
+ filterIcon: {
+ name: 'filter-icon',
+ type: 'svg',
+ width: 12,
+ height: 12,
+ positionType: 'right',
cursor: 'pointer',
- flexGrow: '1',
- fontWeight: 'normal'
- },
- checkbox: {
- marginRight: '10px'
+ marginRight: 4,
+ svg: ``
},
- countSpan: {
- color: '#888',
- fontSize: '12px'
- },
- tabsContainer: {
- display: 'flex',
- justifyContent: 'space-around',
- borderBottom: '0px solid #e0e0e0'
+ filteringIcon: {
+ name: 'filtering-icon',
+ type: 'svg',
+ width: 12,
+ height: 12,
+ positionType: 'right',
+ cursor: 'pointer',
+ marginRight: 4,
+ svg: ``
},
- footerContainer: {
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: '10px 15px',
- borderTop: '0px solid #e0e0e0',
- backgroundColor: 'transparent'
+ styles: {
+ // 筛选菜单
+ filterMenu: {
+ position: 'absolute',
+ backgroundColor: fillColor,
+ border: `${strokeWidth}px solid ${strokeColor}`,
+ boxShadow: '0 4px 8px rgba(0,0,0,0.15)',
+ zIndex: 99999,
+ borderRadius: `${borderRadius}px`,
+ color: textColor,
+ fontFamily: textFontFamily,
+ fontSize: `${textFontSize}px`
+ },
+
+ // 搜索输入框
+ searchInput: {
+ width: '100%',
+ padding: '8px 10px',
+ border: '1px solid #272a30',
+ borderRadius: '4px',
+ backgroundColor: '#0e1119',
+ boxSizing: 'border-box',
+ // vtable内部没有做国际化, 所以这里也不做国际化
+ placeholder: '请输入关键字搜索',
+ color: textColor
+ },
+ tabsContainer: {
+ borderBottom: '0px solid #e0e0e0'
+ },
+
+ // 标签样式
+ tabStyle: isActive => ({
+ backgroundColor: 'transparent',
+ border: 'none',
+ flex: '1',
+ padding: '10px 15px',
+ cursor: 'pointer',
+ fontWeight: isActive ? 'bold' : 'normal',
+ color: isActive ? highlightColor : defaultColor,
+ borderBottom: isActive ? `3px solid ${highlightColor}` : '2px solid transparent'
+ }),
+
+ countSpan: {
+ color: 'transparent'
+ },
+
+ // 页脚容器
+ footerContainer: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: '10px 15px',
+ borderTop: '0px solid #e0e0e0',
+ backgroundColor: 'transparent'
+ },
+
+ footerButton: isPrimary => ({
+ padding: '6px 12px',
+ border: '1px solid #ccc',
+ borderRadius: '4px',
+ cursor: 'pointer',
+ marginLeft: '5px',
+ backgroundColor: isPrimary ? highlightColor : 'transparent',
+ color: isPrimary ? defaultColor : highlightColor,
+ borderColor: isPrimary ? highlightColor : 'transparent'
+ }),
+
+ // 清除链接
+ clearLink: {
+ color: highlightColor,
+ textDecoration: 'none'
+ },
+
+ // 按钮样式
+ buttonStyle: (isPrimary = false) => ({
+ padding: '6px 12px',
+ border: '1px solid #ccc',
+ borderRadius: `${borderRadius}px`,
+ cursor: 'pointer',
+ marginLeft: '5px',
+ backgroundColor: isPrimary ? highlightColor : 'white',
+ color: isPrimary ? 'white' : textColor,
+ borderColor: isPrimary ? highlightColor : '#ccc'
+ }),
+
+ // === 条件筛选相关样式 ===
+
+ // 条件筛选容器
+ conditionContainer: {
+ marginBottom: '15px',
+ padding: '10px',
+ backgroundColor: fillColor,
+ color: textColor
+ },
+
+ // 表单标签样式
+ formLabel: {
+ display: 'block',
+ marginBottom: '8px',
+ fontWeight: 'normal',
+ backgroundColor: 'transparent',
+ color: textColor
+ },
+
+ // 操作符选择框样式
+ operatorSelect: {
+ width: '100%',
+ padding: '8px',
+ marginBottom: '15px',
+ border: '1px solid #272a30',
+ borderRadius: '4px',
+ backgroundColor: '#0e1119',
+ color: textColor
+ },
+
+ rangeInputContainer: {
+ display: 'flex',
+ alignItems: 'center',
+ gap: '8px',
+ backgroundColor: fillColor,
+ color: textColor
+ }
},
- clearLink: {
- color: '#006EFF',
- textDecoration: 'none'
+ conditionCategories: [
+ { value: 'all', label: '全部' },
+ { value: 'text', label: '文本' },
+ { value: 'number', label: '数值' }
+ // { value: FilterOperatorCategory.COLOR, label: '颜色' },
+ // { value: FilterOperatorCategory.CHECKBOX, label: '复选框' },
+ // { value: FilterOperatorCategory.RADIO, label: '单选框' }
+ ],
+ checkboxItemFormat: (rawValue, formatValue) => rawValue,
+ onFilterRecordsEnd: records => {
+ console.log('records', records);
+ records.push(null);
+ // return records;
+ }
+ };
+ };
+ const filterPlugin = new FilterPlugin(getTableFilterPluginAttrFromProps());
+ const option = {
+ container: document.getElementById(CONTAINER_ID),
+ columns: [
+ {
+ field: '0#LINE_NUMBER_DIM_ID_STR',
+ title: '序号',
+ width: '12%',
+ style: columnStyle
},
- conditionContainer: {
- marginBottom: '15px',
- padding: '10px',
- backgroundColor: '#0E1119'
+ {
+ field: 'PZwFghHcsvwt',
+ title: 'From Province',
+ showSort: false,
+ style: columnStyle,
+ headerStyle,
+ width: '29.333333333333332%'
},
- formLabel: {
- display: 'block',
- marginBottom: '8px',
- fontWeight: 'normal',
- backgroundColor: 'transparent'
+ {
+ field: 'CKQPX3dQXKYk',
+ title: 'To Province',
+ showSort: false,
+ style: columnStyle,
+ headerStyle,
+ width: '29.333333333333332%'
},
- operatorSelect: {
- width: '100%',
- padding: '8px',
- marginBottom: '15px',
- border: '1px solid #272a30',
- borderRadius: '4px',
- boxSizing: 'border-box',
- backgroundColor: '#0e1119',
- color: 'red'
+ {
+ firstRow: 0.8,
+ field: 'RMLcpglcOTHo',
+ title: 'Profit',
+ showSort: false,
+ style: columnStyle,
+ headerStyle,
+ width: '29.333333333333332%',
+ fieldFormat: datum => {
+ return '¥' + datum.RMLcpglcOTHo;
+ }
+ }
+ ],
+ records: [
+ {
+ PZwFghHcsvwt: '河北',
+ CKQPX3dQXKYk: '河南',
+ RMLcpglcOTHo: 0.8,
+ '0#LINE_NUMBER_DIM_ID_STR': 1,
+ '0#LINE_NUMBER_ICON':
+ ''
},
- rangeInputContainer: {
- display: 'flex',
- alignItems: 'center',
- gap: '8px',
- backgroundColor: '#0E1119'
+ {
+ PZwFghHcsvwt: '山西',
+ CKQPX3dQXKYk: '湖北',
+ RMLcpglcOTHo: 15,
+ '0#LINE_NUMBER_DIM_ID_STR': 2,
+ '0#LINE_NUMBER_ICON':
+ ''
},
- addLabel: {
- display: 'none',
- padding: '0 5px'
+ {
+ PZwFghHcsvwt: '内蒙古',
+ CKQPX3dQXKYk: '湖南',
+ RMLcpglcOTHo: 50,
+ '0#LINE_NUMBER_DIM_ID_STR': 3,
+ '0#LINE_NUMBER_ICON':
+ ''
}
- },
- conditionCategories: [
- { value: FilterOperatorCategory.ALL, label: '全部' },
- { value: FilterOperatorCategory.TEXT, label: '文本' },
- { value: FilterOperatorCategory.NUMBER, label: '数值' }
- // { value: FilterOperatorCategory.COLOR, label: '颜色' },
- // { value: FilterOperatorCategory.CHECKBOX, label: '复选框' },
- // { value: FilterOperatorCategory.RADIO, label: '单选框' }
],
- checkboxItemFormat: (formatValue, rawValue) => {
- return formatValue;
+ theme: {
+ underlayBackgroundColor: 'rgba(255,255,255,0)',
+ headerStyle,
+ bodyStyle: {
+ bgColor: ({ row, col }) => {
+ return row % 2 === 0 ? 'rgba(16,34,58,1)' : 'rgba(12,9,41,1)';
+ }
+ }
},
- syncFilterItemsState: false,
- onFilterRecordsEnd: records => {
- console.log('onFilterRecordsEnd');
- return [...records, null, undefined];
- }
- });
- (window as any).filterPlugin = filterPlugin;
-
- const option: VTable.ListTableConstructorOptions = {
- container: document.getElementById(CONTAINER_ID),
- records: [...records, null, undefined],
- columns,
- padding: 10,
- plugins: [filterPlugin],
- emptyTip: {
- text: 'no data'
- }
+ transpose: false,
+ widthMode: 'adaptive',
+ columnResizeMode: 'all',
+ heightMode: 'standard',
+ heightAdaptiveMode: 'all',
+ autoFillHeight: true,
+ autoWrapText: true,
+ maxCharactersNumber: 256,
+ defaultHeaderColWidth: 'auto',
+ keyboardOptions: {
+ selectAllOnCtrlA: true,
+ copySelected: false
+ },
+ menu: {
+ renderMode: 'html'
+ },
+ disableScroll: true,
+ customConfig: {
+ _disableColumnAndRowSizeRound: true,
+ imageMargin: 4,
+ multilinesForXTable: true,
+ shrinkSparklineFirst: true,
+ limitContentHeight: false
+ },
+ frozenColCount: 0,
+ showHeader: true,
+ hover: {
+ disableHover: true
+ },
+ select: {
+ highlightMode: 'row',
+ headerSelectMode: 'cell',
+ blankAreaClickDeselect: true,
+ disableSelect: true
+ },
+ autoHeightInAdaptiveMode: false,
+ defaultRowHeight: 61.25,
+ // animationAppear: {
+ // duration: 100,
+ // delay: 0,
+ // type: 'one-by-one',
+ // direction: 'row'
+ // },
+ hash: '0fa7dcedd7d638eff65f3a5bd3906361',
+ width: 400,
+ height: 245,
+ plugins: [filterPlugin]
};
+
const tableInstance = new VTable.ListTable(option);
- (window as any).tableInstance = tableInstance;
- tableInstance.on(ListTable.EVENT_TYPE.FILTER_MENU_SHOW, (...args) => {
- console.log('filter_menu_show', args);
- tableInstance.arrangeCustomCellStyle({ col: 1, row: 0 }, 'header_highlight');
- });
- tableInstance.on(ListTable.EVENT_TYPE.FILTER_MENU_HIDE, (...args) => {
- console.log('filter_menu_hide', args);
- tableInstance.arrangeCustomCellStyle({ col: 1, row: 0 }, 'header_highlight');
- });
-
- tableInstance.registerCustomCellStyle('header_highlight', {
- bgColor: 'red'
- });
+ window.tableInstance = tableInstance;
// 数据更新
- setTimeout(() => {
- filterPlugin.updatePluginOptions({
- styles: {
- searchInput: {
- placeholder: 'xxx',
- color: 'blue'
- },
- optionItem: {
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: '8px 5px',
- color: 'blue'
- },
- filterMenu: {
- color: 'red'
- }
- },
- onFilterRecordsEnd: records => {
- console.log('onFilterRecordsEnd-2');
- records.push(null, undefined);
- }
- });
- console.log('update');
- tableInstance.updateOption({
- ...option,
- plugins: [filterPlugin],
- records: [...generateDemoData(50, '第二次'), null, undefined]
- });
- }, 2000);
+ // setTimeout(() => {
+ // filterPlugin.updatePluginOptions({
+ // styles: {
+ // searchInput: {
+ // placeholder: 'xxx',
+ // color: 'blue'
+ // },
+ // optionItem: {
+ // display: 'flex',
+ // justifyContent: 'space-between',
+ // alignItems: 'center',
+ // padding: '8px 5px',
+ // color: 'blue'
+ // },
+ // filterMenu: {
+ // color: 'red'
+ // }
+ // },
+ // onFilterRecordsEnd: records => {
+ // console.log('onFilterRecordsEnd-2');
+ // records.push(null, undefined);
+ // }
+ // });
+ // console.log('update');
+ // tableInstance.updateOption({
+ // ...option,
+ // plugins: [filterPlugin],
+ // records: [...generateDemoData(50, '第二次'), null, undefined]
+ // });
+ // }, 2000);
// 插件更新
// setTimeout(() => {
diff --git a/packages/vtable-plugins/src/filter/filter-toolbar.ts b/packages/vtable-plugins/src/filter/filter-toolbar.ts
index 2fb8d486c..32fbc5f2e 100644
--- a/packages/vtable-plugins/src/filter/filter-toolbar.ts
+++ b/packages/vtable-plugins/src/filter/filter-toolbar.ts
@@ -31,8 +31,6 @@ export class FilterToolbar {
private cancelFilterButton: HTMLButtonElement;
private applyFilterButton: HTMLButtonElement;
- private activeType: 'byValue' | 'byCondition' = 'byValue';
-
constructor(table: ListTable | PivotTable, filterStateManager: FilterStateManager, pluginOptions: FilterOptions) {
this.table = table;
this.filterStateManager = filterStateManager;
@@ -176,25 +174,15 @@ export class FilterToolbar {
applyStyles(this.applyFilterButton, styles.footerButton(true));
this.valueFilter.updateStyles(styles);
this.conditionFilter.updateStyles(styles);
-
- // 面板处于显示状态, 更新了样式, 则需要手动控制tab显隐
- // 面板显示按值筛选或按条件筛选
- if (this.activeType === 'byCondition') {
- this.onTabSwitch('byCondition');
- } else {
- this.onTabSwitch('byValue');
- }
}
attachEventListeners() {
// 按值筛选/按条件筛选的事件监听
this.filterTabByValue.addEventListener('click', () => {
- this.activeType = 'byValue';
this.onTabSwitch('byValue');
});
this.filterTabByCondition.addEventListener('click', () => {
- this.activeType = 'byCondition';
this.onTabSwitch('byCondition');
});
diff --git a/packages/vtable-plugins/src/filter/value-filter.ts b/packages/vtable-plugins/src/filter/value-filter.ts
index 270ebf1af..7a7edaf76 100644
--- a/packages/vtable-plugins/src/filter/value-filter.ts
+++ b/packages/vtable-plugins/src/filter/value-filter.ts
@@ -175,7 +175,9 @@ export class ValueFilter {
private toggleSelectAll(fieldId: string | number, selected: boolean): void {
const options = this.valueFilterOptionList.get(fieldId);
options.forEach(option => {
- option.checkbox.checked = selected;
+ if (option.itemContainer.style.display !== 'none') {
+ option.checkbox.checked = selected;
+ }
});
}