From 13041e7a18754a91aa09075d4371bdefb8730cdb Mon Sep 17 00:00:00 2001 From: skie1997 Date: Thu, 18 Dec 2025 21:34:43 +0800 Subject: [PATCH 1/3] fix: delete a wrong logic --- packages/vtable-plugins/src/filter/filter-toolbar.ts | 12 ------------ 1 file changed, 12 deletions(-) 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'); }); From a0e5c5ef406b941452fe797867d677eb137f756b Mon Sep 17 00:00:00 2001 From: skie1997 Date: Thu, 18 Dec 2025 21:35:35 +0800 Subject: [PATCH 2/3] fix: toggleSelectAll only in searched item. fix#4838 --- ...-plugin-for-business_2025-12-18-13-35.json | 10 + packages/vtable-plugins/demo/filter/filter.ts | 659 ++++++++++-------- 2 files changed, 385 insertions(+), 284 deletions(-) create mode 100644 common/changes/@visactor/vtable/feat-filter-plugin-for-business_2025-12-18-13-35.json 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(() => { From 9197f688e09c77725259799215a4722b1c2fce77 Mon Sep 17 00:00:00 2001 From: skie1997 Date: Thu, 18 Dec 2025 21:36:46 +0800 Subject: [PATCH 3/3] fix: toggleSelectAll only in searched item. fix#4838 --- packages/vtable-plugins/src/filter/value-filter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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; + } }); }