diff --git a/packages/li-analysis-assets/src/widgets/FilterControl/Component/DateItem.tsx b/packages/li-analysis-assets/src/widgets/FilterControl/Component/DateItem.tsx new file mode 100644 index 00000000..06bc8000 --- /dev/null +++ b/packages/li-analysis-assets/src/widgets/FilterControl/Component/DateItem.tsx @@ -0,0 +1,39 @@ +import type { FilterDateConfigType } from '@antv/li-p2'; +import { FilterDateConfig } from '@antv/li-p2'; +import React from 'react'; + +export interface DateItemProps { + defaultValue: FilterDateConfigType; + onChange: (value: FilterDateConfigType) => void; +} + +const DateItem: React.FC = (props) => { + const { defaultValue, onChange } = props; + + const onValueChange = (val: any) => { + const { type, granularity, value } = val; + onChange({ + ...defaultValue, + value, + granularity, + params: { + ...defaultValue.params, + dateType: type, + }, + }); + }; + + return ( + + ); +}; + +export default DateItem; diff --git a/packages/li-analysis-assets/src/widgets/FilterControl/Component/NumberItem.tsx b/packages/li-analysis-assets/src/widgets/FilterControl/Component/NumberItem.tsx new file mode 100644 index 00000000..41df374a --- /dev/null +++ b/packages/li-analysis-assets/src/widgets/FilterControl/Component/NumberItem.tsx @@ -0,0 +1,64 @@ +import { DownOutlined } from '@ant-design/icons'; +import type { FilterNumberConfigType } from '@antv/li-p2'; +import { FilterNumberConfig } from '@antv/li-p2'; +import { Button, Popover } from 'antd'; +import React, { useState } from 'react'; +import useStyle from './style'; + +export interface NumberItemProps { + defaluValue: FilterNumberConfigType; + onChange: (value: FilterNumberConfigType) => void; +} + +const NumberItem: React.FC = (props) => { + const { defaluValue, onChange } = props; + const styles = useStyle(); + const [open, setOpen] = useState(false); + + const [valAndOperator, setValAndOperator] = useState>({ + value: defaluValue.value, + operator: defaluValue.operator, + }); + + const onValueChange = (val: number | [number, number] | undefined, operator: '>=' | '<=' | 'BETWEEN') => { + setValAndOperator({ value: val, operator }); + }; + + const handleOpenChange = (open: boolean) => { + setOpen(open); + }; + + const onSubmit = () => { + const numberNode = { ...defaluValue, ...valAndOperator } as FilterNumberConfigType; + onChange(numberNode); + setOpen(false); + }; + + const content = ( +
+ +
+ +
+
+ ); + + const title = !valAndOperator.value + ? '不限' + : valAndOperator.operator === 'BETWEEN' && Array.isArray(valAndOperator.value) + ? `${valAndOperator.value[0]} ~ ${valAndOperator.value[1]}` + : `${valAndOperator.operator} ${valAndOperator.value}`; + + return ( + +
+
{title}
+ +
+
+ ); +}; + +export default NumberItem; diff --git a/packages/li-analysis-assets/src/widgets/FilterControl/Component/StringItem.tsx b/packages/li-analysis-assets/src/widgets/FilterControl/Component/StringItem.tsx new file mode 100644 index 00000000..a49c8ee7 --- /dev/null +++ b/packages/li-analysis-assets/src/widgets/FilterControl/Component/StringItem.tsx @@ -0,0 +1,42 @@ +import type { FilterStringConfigType } from '@antv/li-p2'; +import { FilterStringConfig } from '@antv/li-p2'; +import { uniq } from 'lodash-es'; +import React, { useMemo } from 'react'; + +export interface StringItemProps { + defaluValue: FilterStringConfigType; + field: string; + data: Record[]; + onChange: (value: FilterStringConfigType) => void; +} + +const StringItem: React.FC = (props) => { + const { defaluValue, field, data, onChange } = props; + + const domain = useMemo(() => { + const fieldData = data.map((item) => (typeof item[field] === 'object' ? JSON.stringify(item[field]) : item[field])); + const _domain: string[] = uniq(fieldData).slice(0, 3000); + + return _domain; + }, [data, field]); + + const onValueChange = (val?: string[]) => { + onChange({ + ...defaluValue, + value: val, + }); + }; + + return ( + + ); +}; + +export default StringItem; diff --git a/packages/li-analysis-assets/src/widgets/FilterControl/Component/helper.ts b/packages/li-analysis-assets/src/widgets/FilterControl/Component/helper.ts new file mode 100644 index 00000000..ffccc06e --- /dev/null +++ b/packages/li-analysis-assets/src/widgets/FilterControl/Component/helper.ts @@ -0,0 +1,49 @@ +import type { FilterConfigType } from 'packages/li-p2'; +import type { FilterNode } from 'packages/li-sdk'; + +export const getFilterNode = (filterConfig: FilterConfigType) => { + let filterNode: FilterNode; + + switch (filterConfig.type) { + case 'string': + // 空值的情况,设置无效值; 全选情况,设置无效值; + const value = filterConfig.value === undefined || filterConfig.value.includes('all') ? [] : filterConfig.value; + filterNode = { + id: filterConfig.id, + field: filterConfig.field, + type: filterConfig.type, + operator: filterConfig.operator, + value: value, + }; + break; + case 'number': + filterNode = { + id: filterConfig.id, + field: filterConfig.field, + type: filterConfig.type, + operator: filterConfig.operator, + // 空值的情况,设置无效值 + value: filterConfig.value || ([] as any), + }; + break; + case 'date': + filterNode = { + id: filterConfig.id, + field: filterConfig.field, + type: filterConfig.type, + operator: filterConfig.operator, + // 空值的情况,设置无效值 + value: filterConfig.value || (([] as unknown) as [string, string]), + granularity: filterConfig.granularity, + }; + break; + } + + return filterNode; +}; + +export const getFilterNodes = (list: FilterConfigType[]) => { + const filterNodes = list.map((item) => getFilterNode(item)); + + return filterNodes; +}; diff --git a/packages/li-analysis-assets/src/widgets/FilterControl/Component/index.tsx b/packages/li-analysis-assets/src/widgets/FilterControl/Component/index.tsx new file mode 100644 index 00000000..d2aa7b04 --- /dev/null +++ b/packages/li-analysis-assets/src/widgets/FilterControl/Component/index.tsx @@ -0,0 +1,102 @@ +import { CustomControl } from '@antv/larkmap'; +import type { FilterConfigType } from '@antv/li-p2'; +import type { ImplementWidgetProps, LocalOrRemoteDataset } from '@antv/li-sdk'; +import { useDataset, useDatasetFilter } from '@antv/li-sdk'; +import { useMount, useUpdateEffect } from 'ahooks'; +import classNames from 'classnames'; +import React, { useMemo, useState } from 'react'; +import type { Properties } from '../registerForm'; +import DateItem from './DateItem'; +import { getFilterNode, getFilterNodes } from './helper'; +import NumberItem from './NumberItem'; +import StringItem from './StringItem'; +import useStyle from './style'; + +const CLS_PREFIX = 'li-filter-control'; +export interface LIFilterControlProps extends Properties, ImplementWidgetProps {} + +const LIFilterControl: React.FC = (props) => { + const { defaultFilters, datasetId = '', position } = props; + const styles = useStyle(); + const [filterList, setFilterList] = useState(defaultFilters); + const [filter, { addFilterNode, updateFilterNode }] = useDatasetFilter(datasetId); + // 排除自生产筛选条件 + const oimtSelfFilter = useMemo(() => { + const filterIds = defaultFilters.map((item) => item.id); + const children = filter?.children.filter((item) => !filterIds.includes(item.id)) || []; + return { relation: 'AND' as const, children }; + }, []); + const [dataset] = useDataset(datasetId, { + filter: oimtSelfFilter, + }); + const { data: tableData = [] } = dataset || {}; + + // 首次挂载 + // TODO: 添加 FilterNode 需要在「初始化」消费数据过滤条件前执行,避免「初始化」多次消费数据过滤条件 + useMount(() => { + const filterNodes = getFilterNodes(defaultFilters); + filterNodes.forEach((item) => { + addFilterNode(item); + }); + }); + + // TODO: 支持同步更新(在事件订阅之前) + // const firstMountRef = useRef(false); + // if (!firstMountRef.current) { + // const filterNodes = getFilterNodes(defaultFilters); + // filterNodes.forEach((item) => { + // addFilterNode(item); + // }); + // firstMountRef.current = true; + // } + + // 配置初始筛选条件变更,配置态运行 + useUpdateEffect(() => { + const filterIds = filterList.map((item) => item.id); + const filterNodes = getFilterNodes(defaultFilters); + + filterNodes.forEach((item) => { + // 筛选条件已经存在进行更新 updateFilterNode,不存在添加 addFilterNode + if (filterIds.includes(item.id)) { + updateFilterNode(item.id, item); + } else { + addFilterNode(item); + } + }); + setFilterList(defaultFilters); + }, [defaultFilters]); + + const onValueChange = (val: FilterConfigType) => { + const updateNode = getFilterNode(val); + updateFilterNode(val.id, updateNode); + }; + + if (!defaultFilters.length) { + return null; + } + + return ( + +
+ {filterList.map((item) => { + return ( +
+
+ {item.title}: +
+
+ {item.type === 'string' && ( + + )} + {item.type === 'number' && } + {item.type === 'date' && } +
+
+ ); + })} +
+
+ ); +}; + +export default LIFilterControl; diff --git a/packages/li-analysis-assets/src/widgets/FilterControl/Component/style.ts b/packages/li-analysis-assets/src/widgets/FilterControl/Component/style.ts new file mode 100644 index 00000000..c51224cb --- /dev/null +++ b/packages/li-analysis-assets/src/widgets/FilterControl/Component/style.ts @@ -0,0 +1,65 @@ +import { css } from '@emotion/css'; +import { theme } from 'antd'; + +const useStyle = () => { + const { useToken } = theme; + const { token } = useToken(); + + const { colorTextDescription, colorBgContainer, borderRadius } = token; + + return { + filterControl: css` + display: flex; + flex-wrap: wrap; + color: ${colorTextDescription}; + align-items: center; + background: ${colorBgContainer}; + border-radius: ${borderRadius}px; + `, + + filterItem: css` + display: flex; + align-items: center; + `, + + filterItemTitle: css` + margin: 0 10px; + font-size: 14px; + `, + + filterItemContent: css` + min-width: 150px; + max-width: 300px; + font-size: 14px; + + .ant-select-selector { + color: ${colorTextDescription}; + } + + .ant-picker-input > input { + color: ${colorTextDescription}; + } + `, + + numberItem: css` + display: flex; + justify-content: space-between; + padding: 10px; + height: 32px; + line-height: 32px; + padding: 0 5px; + cursor: pointer; + `, + + numberContent: css` + padding: 5px; + `, + + numberSubmit: css` + text-align: center; + margin-top: 20px; + `, + }; +}; + +export default useStyle; diff --git a/packages/li-analysis-assets/src/widgets/FilterControl/index.md b/packages/li-analysis-assets/src/widgets/FilterControl/index.md new file mode 100644 index 00000000..9be53dea --- /dev/null +++ b/packages/li-analysis-assets/src/widgets/FilterControl/index.md @@ -0,0 +1 @@ +## FilterControl diff --git a/packages/li-analysis-assets/src/widgets/FilterControl/index.tsx b/packages/li-analysis-assets/src/widgets/FilterControl/index.tsx new file mode 100644 index 00000000..f1a5e062 --- /dev/null +++ b/packages/li-analysis-assets/src/widgets/FilterControl/index.tsx @@ -0,0 +1,19 @@ +import { implementWidget } from '@antv/li-sdk'; +import component from './Component'; +import registerForm from './registerForm'; + +export default implementWidget({ + version: 'v0.1', + metadata: { + name: 'FilterControl', + displayName: '数据筛选控件', + description: '在地图上的实时筛选数据,支持默认值设置', + type: 'Auto', + category: 'DataAnalysis', + }, + defaultProperties: { + defaultFilters: [], + }, + component, + registerForm, +}); diff --git a/packages/li-analysis-assets/src/widgets/FilterControl/registerForm.ts b/packages/li-analysis-assets/src/widgets/FilterControl/registerForm.ts new file mode 100644 index 00000000..9baa5261 --- /dev/null +++ b/packages/li-analysis-assets/src/widgets/FilterControl/registerForm.ts @@ -0,0 +1,63 @@ +import type { PositionName } from '@antv/l7'; +import type { FilterConfigType } from '@antv/li-p2'; +import type { WidgetRegisterForm, WidgetRegisterFormProps } from '@antv/li-sdk'; +import { getDatasetSelectFormSchema } from '@antv/li-sdk'; + +/** + * 属性面板生产的数据类型定义 + */ +export type Properties = { + datasetId?: 'string'; + position?: PositionName; + defaultFilters: FilterConfigType[]; +}; + +export default (props: WidgetRegisterFormProps): WidgetRegisterForm => { + // 属性面板表单的 Schema 定义,来自表单库 formily 的 Schema + const schema = { + ...getDatasetSelectFormSchema(props, 'datasetId', '数据源'), + + defaultFilters: { + type: 'array', + default: [], + 'x-decorator': 'FormItem', + 'x-component': 'FilterConfiguration', + 'x-component-props': { + options: + '{{ $form.getFieldState("datasetId",state=> { return state.dataSource.find(item=>item.value=== state?.value)?.columns || [] })}}', + style: { + width: '100%', + marginTop: 10, + }, + }, + 'x-reactions': [ + { + dependencies: ['datasetId'], + fulfill: { + state: { + visible: '{{ $deps[0] !== undefined }}', + }, + }, + }, + { + dependencies: ['datasetId'], + fulfill: { + run: `$form.setFieldState('defaultFilters',state=>{ + state.value = []; + })`, + }, + }, + ], + }, + + position: { + title: '放置方位', + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'ControlPositionSelect', + default: 'lefttop', + }, + }; + + return { schema }; +}; diff --git a/packages/li-analysis-assets/src/widgets/FilterWidget/index.tsx b/packages/li-analysis-assets/src/widgets/FilterWidget/index.tsx index cad61ce6..66627699 100644 --- a/packages/li-analysis-assets/src/widgets/FilterWidget/index.tsx +++ b/packages/li-analysis-assets/src/widgets/FilterWidget/index.tsx @@ -6,8 +6,8 @@ export default implementWidget({ version: 'v0.1', metadata: { name: 'FilterWidget', - displayName: '数据筛选器', - description: '用于添加筛选条件过滤数据', + displayName: '条件筛选器', + description: '用于动态添加筛选条件来过滤数据', type: 'Atom', category: 'DataAnalysis', }, diff --git a/packages/li-analysis-assets/src/widgets/index.ts b/packages/li-analysis-assets/src/widgets/index.ts index 699cbc9d..493c4758 100644 --- a/packages/li-analysis-assets/src/widgets/index.ts +++ b/packages/li-analysis-assets/src/widgets/index.ts @@ -14,3 +14,4 @@ export { default as TimeLine } from './TimeLine'; export { default as SpreadSheetTable } from './SpreadSheetTable'; export { default as AdministrativeSelectControl } from './AdministrativeSelectControl'; export { default as SwipeControl } from './SwipeControl'; +export { default as FilterControl } from './FilterControl'; diff --git a/packages/li-core-assets/src/services/fetch-dataset/helper.ts b/packages/li-core-assets/src/services/fetch-dataset/helper.ts index b7a27d21..6da684db 100644 --- a/packages/li-core-assets/src/services/fetch-dataset/helper.ts +++ b/packages/li-core-assets/src/services/fetch-dataset/helper.ts @@ -59,6 +59,8 @@ export const getFetchData = (params: Params) => { : properties.requestOptions.body, }); + console.log('getFetchData filter: ', filter); + if (Chache.has(requestkey)) { const data = Chache.get(requestkey); return datasetFilterService({ data, filter }, signal); diff --git a/packages/li-editor/src/widgets/WidgetsPanel/WidgetAttribute/WidgetForm/SchemaField.tsx b/packages/li-editor/src/widgets/WidgetsPanel/WidgetAttribute/WidgetForm/SchemaField.tsx index bcef3b8f..d351d62e 100644 --- a/packages/li-editor/src/widgets/WidgetsPanel/WidgetAttribute/WidgetForm/SchemaField.tsx +++ b/packages/li-editor/src/widgets/WidgetsPanel/WidgetAttribute/WidgetForm/SchemaField.tsx @@ -1,4 +1,10 @@ -import { ControlPositionSelect, FieldSelect, FormCollapse, TimeGranularitySelect } from '@antv/li-p2'; +import { + ControlPositionSelect, + FieldSelect, + FormCollapse, + TimeGranularitySelect, + FilterConfiguration, +} from '@antv/li-p2'; import { ArrayItems, Checkbox, @@ -33,6 +39,7 @@ const SchemaField = createSchemaField({ FormGrid, ControlPositionSelect, TimeGranularitySelect, + FilterConfiguration, }, }); diff --git a/packages/li-editor/src/widgets/WidgetsPanel/WidgetAttribute/index.tsx b/packages/li-editor/src/widgets/WidgetsPanel/WidgetAttribute/index.tsx index 5d7f7ca0..5390fd1b 100644 --- a/packages/li-editor/src/widgets/WidgetsPanel/WidgetAttribute/index.tsx +++ b/packages/li-editor/src/widgets/WidgetsPanel/WidgetAttribute/index.tsx @@ -38,7 +38,24 @@ const WidgetAttribute: React.FC = (props) => { // 数据集列表 const { editorDatasets } = useEditorDatasets(); - const datasets: Dataset[] = editorDatasets.map((item) => ({ ...item.schema, columns: item.columns, data: [] })); + const datasets: Dataset[] = editorDatasets.map((item) => { + const columns = item.columns.map((cloumn) => { + // TODO: 从 editorDataset 获取 domain 数据 + let domain: string[] | [number, number] = []; + if (cloumn.type === 'string') { + const itemValue = item.data.map((_item: any) => _item[cloumn.name]); + domain = cloumn.type === 'string' ? [...new Set(itemValue)] : []; + } + + return { ...cloumn, domain }; + }); + + return { + ...item.schema, + columns: columns, + data: [], + }; + }); // 服务资产列表 const services = useMemo(() => appService.getImplementServices(), [appService]); diff --git a/packages/li-p2/src/components/Formily/FieldSelect/Select/index.tsx b/packages/li-p2/src/components/Formily/FieldSelect/Select/index.tsx index 2d77341f..024d206f 100644 --- a/packages/li-p2/src/components/Formily/FieldSelect/Select/index.tsx +++ b/packages/li-p2/src/components/Formily/FieldSelect/Select/index.tsx @@ -1,4 +1,5 @@ import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import { useUpdateEffect } from 'ahooks'; import type { SelectProps } from 'antd'; import { Empty, Select, Tag } from 'antd'; import cls from 'classnames'; @@ -8,15 +9,19 @@ import useStyle from './style'; import type { FieldSelectOptionType } from './types'; const InternalSelect: React.FC> = (props) => { - const { options, ...prop } = props; + const { options, open: outerOpen = false, ...prop } = props; const prefixCls = usePrefixCls('formily-field-select'); const [wrapSSR, hashId] = useStyle(prefixCls); - const [open, setOpen] = useState(false); + const [internalOpen, setInternalOpen] = useState(outerOpen); + + useUpdateEffect(() => { + setInternalOpen(outerOpen); + }, [outerOpen]); const onOptionClick = (val: string) => { if (props.onChange) { props.onChange(val, options ?? []); - setOpen(false); + setInternalOpen(false); } }; @@ -24,8 +29,8 @@ const InternalSelect: React.FC> = (pr + + +
+
默认值
+ +
+ , + ); +}; + +export default DateItem; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/DateItem/style.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/DateItem/style.ts new file mode 100644 index 00000000..6735e97f --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/DateItem/style.ts @@ -0,0 +1,22 @@ +import { genStyleHook } from '@formily/antd-v5/esm/__builtins__'; + +export default genStyleHook('filter-setting-modal-date-item', (token) => { + const { componentCls, colorText } = token; + + return { + [componentCls]: { + '&__filter': { + marginBottom: '8px', + }, + + '&__field': { + fontSize: '12px', + fontWeight: 700, + color: colorText, + height: '20px', + lineHeight: '20px', + marginBottom: '8px', + }, + }, + }; +}); diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/NumberItem/index.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/NumberItem/index.tsx new file mode 100644 index 00000000..5e5861b5 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/NumberItem/index.tsx @@ -0,0 +1,39 @@ +import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import cls from 'classnames'; +import React from 'react'; +import { FilterNumberConfig } from '../../../components'; +import type { FilterNumberConfigType } from '../../../type'; +import useStyle from './style'; + +export interface NumberItemProps { + value: FilterNumberConfigType; + /** + * 选择发生改变时 + */ + onChange: (value: FilterNumberConfigType) => void; +} + +const NumberItem: React.FC = (props) => { + const prefixCls = usePrefixCls('formily-filter-setting-modal-number-item'); + const [wrapSSR, hashId] = useStyle(prefixCls); + const { value: outterValue, onChange } = props; + const { value, operator } = outterValue; + + const onValueChange = (val: number | [number, number] | undefined, operator: '>=' | '<=' | 'BETWEEN') => { + const _value = { + ...outterValue, + value: val, + operator, + } as FilterNumberConfigType; + onChange(_value); + }; + + return wrapSSR( +
+
设定默认值
+ +
, + ); +}; + +export default NumberItem; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/NumberItem/style.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/NumberItem/style.ts new file mode 100644 index 00000000..1b60fdbc --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/NumberItem/style.ts @@ -0,0 +1,22 @@ +import { genStyleHook } from '@formily/antd-v5/esm/__builtins__'; + +export default genStyleHook('filter-setting-modal-number-item', (token) => { + const { componentCls, colorText } = token; + + return { + [componentCls]: { + '&__filter': { + marginBottom: '8px', + }, + + '&__field': { + fontSize: '12px', + fontWeight: 700, + color: colorText, + height: '20px', + lineHeight: '20px', + marginBottom: '8px', + }, + }, + }; +}); diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/StringItem/index.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/StringItem/index.tsx new file mode 100644 index 00000000..cdc14bd6 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/StringItem/index.tsx @@ -0,0 +1,61 @@ +import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import { Radio } from 'antd'; +import cls from 'classnames'; +import React from 'react'; +import { FilterStringConfig } from '../../../components'; +import type { FilterStringConfigType } from '../../../type'; +import useStyle from './style'; +export interface StringItemProps { + value: FilterStringConfigType; + options: string[]; + onChange: (value: FilterStringConfigType) => void; +} + +const StringItem: React.FC = (props) => { + const prefixCls = usePrefixCls('formily-filter-setting-modal-string-item'); + const [wrapSSR, hashId] = useStyle(prefixCls); + const { value: outterValue, options = [], onChange } = props; + + // 类型变化 + const onTypeChange = (type: 'single' | 'multiple') => { + onChange({ + ...outterValue, + params: { + ...outterValue.params, + filterType: type, + }, + value: undefined, + }); + }; + + const onValueChange = (val: string[] | undefined) => { + onChange({ + ...outterValue, + value: val, + }); + }; + + return wrapSSR( + <> +
+
筛选方式
+ onTypeChange(e.target.value)}> + 单选 + 多选 + +
+ +
+
设定默认值
+ +
+ , + ); +}; + +export default StringItem; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/StringItem/style.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/StringItem/style.ts new file mode 100644 index 00000000..44d47f15 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/StringItem/style.ts @@ -0,0 +1,22 @@ +import { genStyleHook } from '@formily/antd-v5/esm/__builtins__'; + +export default genStyleHook('filter-setting-modal-string-item', (token) => { + const { componentCls, colorText } = token; + + return { + [componentCls]: { + '&__filter': { + marginBottom: '8px', + }, + + '&__field': { + fontSize: '12px', + fontWeight: 700, + color: colorText, + height: '20px', + lineHeight: '20px', + marginBottom: '8px', + }, + }, + }; +}); diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/index.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/index.tsx new file mode 100644 index 00000000..5dad4c34 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/index.tsx @@ -0,0 +1,91 @@ +import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import cls from 'classnames'; +import React, { useEffect, useState } from 'react'; +import FieldSelect from '../../../FieldSelect/Select'; +import type { FilterConfigType, OptionType } from '../../type'; +import { getDefaultValue } from '../helper'; +import DateItem from './DateItem'; +import NumberItem from './NumberItem'; +import StringItem from './StringItem'; +import useStyle from './style'; + +export interface FilterContentProps { + value: FilterConfigType; + /** + * 筛选字段 + */ + options: OptionType[]; + /** + * 选择发生改变时 + */ + onChange: (value: FilterConfigType) => void; +} + +const FilterContent: React.FC = (props) => { + const prefixCls = usePrefixCls('formily-filter-setting-content'); + const [wrapSSR, hashId] = useStyle(prefixCls); + const { value: outterValue, options, onChange } = props; + const [filter, setFilter] = useState(outterValue); + const [format, setFormat] = useState('YYYY'); + const [domain, setDomain] = useState([]); + + const openFieldSelect = outterValue.field ? false : true; + + // 筛选字段变更 + const onFieldChange = (field: string) => { + const _field = options.find((item) => item.value === field); + if (_field) { + setDomain(_field?.domain ?? []); + setFormat(_field?.format ?? 'YYYY'); + const _filter = { ...getDefaultValue(_field, filter.id) }; + setFilter(_filter); + onChange(_filter); + } + }; + + // 配置项变更 + const onFilterValueChange = (value: FilterConfigType) => { + setFilter(value); + onChange(value); + }; + + useEffect(() => { + if (outterValue.field && options) { + const _field = options.find((item) => item.value === outterValue.field); + setDomain(_field?.domain ?? []); + if (_field?.type === 'date') { + setFormat(_field?.format || 'YYYY'); + } + } + setFilter(outterValue); + }, [outterValue, options]); + + if (!filter) { + return null; + } + + return wrapSSR( +
+
+
选择筛选字段
+ +
+ + {filter.type === 'string' && ( + + )} + + {filter.type === 'date' && } + + {filter.type === 'number' && } +
, + ); +}; + +export default FilterContent; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/style.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/style.ts new file mode 100644 index 00000000..1ba1ab85 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterContent/style.ts @@ -0,0 +1,25 @@ +import { genStyleHook } from '@formily/antd-v5/esm/__builtins__'; + +export default genStyleHook('filter-setting-content', (token) => { + const { componentCls, colorText } = token; + + return { + [componentCls]: { + width: '100%', + padding: '12px 12px 0', + + '&__filter': { + marginBottom: '8px', + }, + + '&__field': { + fontSize: '12px', + fontWeight: 700, + color: colorText, + height: '20px', + lineHeight: '20px', + marginBottom: '8px', + }, + }, + }; +}); diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterItem/EditName.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterItem/EditName.tsx new file mode 100644 index 00000000..77de30c4 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterItem/EditName.tsx @@ -0,0 +1,56 @@ +import { EnterOutlined } from '@ant-design/icons'; +import { Input, Tooltip } from 'antd'; +import classnames from 'classnames'; +import React, { useCallback, useEffect, useState } from 'react'; +import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import useStyle from './style'; + +type EditNameProps = { + name: string; + isEdit?: boolean; + onChange: (newName: string) => void; + onCancel: () => void; +}; + +export default ({ name, isEdit, onCancel, onChange }: EditNameProps) => { + const [cacheName, setCacheName] = useState(''); + const prefixCls = usePrefixCls('formily-filter-setting-filter-item'); + const [wrapSSR, hashId] = useStyle(prefixCls); + + const onSubmit = useCallback(() => { + if (!cacheName) { + return; + } + if (cacheName === name) { + onCancel(); + } else { + onChange(cacheName); + } + }, [cacheName, name, onChange]); + + useEffect(() => { + if (isEdit) { + setCacheName(name); + } + }, [isEdit, name]); + + return wrapSSR( +
+ {isEdit ? ( + } + onPressEnter={onSubmit} + onChange={(e) => setCacheName(e.target.value)} + onBlur={onCancel} + /> + ) : ( + {name} + )} +
, + ); +}; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterItem/index.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterItem/index.tsx new file mode 100644 index 00000000..1e6e6d5a --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterItem/index.tsx @@ -0,0 +1,81 @@ +import { DeleteOutlined, EditOutlined, HolderOutlined } from '@ant-design/icons'; +import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import classnames from 'classnames'; +import React, { useRef, useState } from 'react'; +import { useDrag, useDrop } from 'react-dnd'; +import { Tooltip } from 'antd'; +import useStyle from './style'; +import EditName from './EditName'; + +type FilterItemProps = { + index: string | number; + id: string; + field: string; + /** + * 选择发生改变时 + */ + onChange: (title: string) => void; + onDelete: () => void; + onClickItem: () => void; + onChangeSort: (dragIndex: string | number, hoverIndex: string | number) => void; +}; + +const FilterItem = ({ index, id, field, onChange, onDelete, onClickItem, onChangeSort }: FilterItemProps) => { + const prefixCls = usePrefixCls('formily-filter-setting-filter-item'); + const [wrapSSR, hashId] = useStyle(prefixCls); + const ref = useRef(null); + const [isEditName, setIsEditName] = useState(false); + + const [, drop] = useDrop({ + accept: 'DragDropBox', + hover: (item: Record) => { + if (!ref.current) return; + const dragIndex = item.index; + const hoverIndex = index; + if (dragIndex === hoverIndex) return; // 如果回到自己的坑,那就什么都不做 + onChangeSort(dragIndex, hoverIndex); // 调用传入的方法完成交换 + item.index = hoverIndex; // 将当前当前移动到Box的index赋值给当前拖动的box,不然会出现两个盒子疯狂抖动! + }, + }); + + const [, drag] = useDrag({ + type: 'DragDropBox', + item: { id, index }, + isDragging: (monitor) => { + return index === monitor.getItem().index; + }, + }); + + const onChangeName = (title: string) => { + onChange(title); + setIsEditName(false); + }; + + drag(drop(ref)); + + return wrapSSR( +
+
+ +
+
+
+ setIsEditName(false)} isEdit={isEditName} /> +
+ + +
setIsEditName(true)}> + +
+
+ +
+ +
+
+
+
, + ); +}; + +export default FilterItem; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterItem/style.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterItem/style.ts new file mode 100644 index 00000000..dba90af1 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/FilterItem/style.ts @@ -0,0 +1,51 @@ +import { genStyleHook } from '@formily/antd-v5/esm/__builtins__'; + +export default genStyleHook('filter-setting-filter-item', (token) => { + const { componentCls, colorPrimary } = token; + + return { + [componentCls]: { + display: 'flex', + alignItems: 'center', + margin: '5px 10px 5px 0', + height: '32px', + + [`${componentCls}__drag-icon`]: { + cursor: 'move', + opacity: 0, + }, + + [`${componentCls}__infor`]: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + width: '100%', + cursor: 'pointer', + + '&__field': { + width: '100%', + }, + + '&__delete-icon': { + fontSize: '12px', + marginLeft: '5px', + opacity: 0, + }, + + '&__delete-icon:hover': { + color: colorPrimary, + }, + }, + }, + + [`${componentCls}:hover`]: { + [`${componentCls}__drag-icon`]: { + opacity: 1, + }, + + [`${componentCls}__infor__delete-icon`]: { + opacity: 1, + }, + }, + }; +}); diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/helper.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/helper.ts new file mode 100644 index 00000000..fc57497e --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/helper.ts @@ -0,0 +1,44 @@ +import type { FilterDateConfigType, FilterNumberConfigType, FilterStringConfigType, OptionType } from '../type'; + +export const getDefaultValue = (field: OptionType, id: string) => { + if (field.type === 'string') { + const _filter: FilterStringConfigType = { + id, + title: field.value, + field: field.value, + type: 'string', + operator: 'IN', + value: undefined, + params: { + filterType: 'single', + }, + }; + return _filter; + } + + if (field.type === 'date') { + const _filter: FilterDateConfigType = { + id, + title: field.value, + field: field.value, + type: 'date', + operator: 'BETWEEN', + granularity: 'day', + params: { + format: field.format ?? 'YYYY-MM-DD', + dateType: 'date', + }, + }; + return _filter; + } + + const _filter: FilterNumberConfigType = { + id, + title: field.value, + field: field.value, + type: 'number', + operator: '>=', + value: undefined, + }; + return _filter; +}; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/index.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/index.tsx new file mode 100644 index 00000000..2e23d449 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/index.tsx @@ -0,0 +1,184 @@ +import { PlusOutlined } from '@ant-design/icons'; +import { getUniqueId } from '@antv/li-sdk'; +import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import { Modal } from 'antd'; +import cls from 'classnames'; +import React, { useEffect, useMemo, useState } from 'react'; +import { DndProvider } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import type { FilterConfigType, OptionType } from '../type'; +import FilterContent from './FilterContent'; +import FilterItem from './FilterItem'; +import useStyle from './style'; + +export interface FilterModalProps { + /** + * 是否打开 + */ + open?: boolean; + value: FilterConfigType[]; + /** + * 筛选字段 + */ + options: OptionType[]; + /** + * 取消 + */ + onCancel: () => void; + /** + * 选择发生改变时 + */ + onChange: (value: FilterConfigType[]) => void; +} + +const FilterModal: React.FC = (props) => { + const prefixCls = usePrefixCls('formily-filter-setting-modal'); + const [wrapSSR, hashId] = useStyle(prefixCls); + const { open = false, value = [], options = [], onCancel, onChange } = props; + const [filterList, setFilterList] = useState([]); + const [selectedFilterNode, setSelectedFilterNode] = useState(); + + const selectedOptions = useMemo(() => { + if (filterList.length) { + return filterList.map((item) => item.field); + } + return []; + }, [filterList]); + + const delFilterItem = (id: string) => { + const _filter = filterList.filter((item) => item.id !== id); + setSelectedFilterNode(_filter[0]); + setFilterList(_filter); + }; + + const onFilterChange = (val: FilterConfigType) => { + setSelectedFilterNode(val); + const list = filterList.map((item) => { + if (item.id === val.id) { + return val; + } + return item; + }); + + setFilterList(list); + }; + + const onChangeSort = (dragIndex: string | number, hoverIndex: string | number) => { + const data = filterList.slice(); + const temp = data[Number(dragIndex)]; + data[Number(dragIndex)] = data[Number(hoverIndex)]; + data[Number(hoverIndex)] = temp; + setFilterList(data); + }; + + const addFilterItem = () => { + const DEFAULTITEM = ({ + id: getUniqueId(), + field: undefined, + } as unknown) as FilterConfigType; + + const _filterList = [...filterList, DEFAULTITEM]; + setFilterList(_filterList); + setSelectedFilterNode(DEFAULTITEM); + }; + + // 提交到外部 + const onSubmit = () => { + onChange(filterList); + }; + + const validOptions = useMemo(() => { + if (!selectedFilterNode) return []; + const selected = selectedOptions.filter((item) => item !== selectedFilterNode.field); + return options.filter((item) => !selected.includes(item.value)); + }, [selectedFilterNode]); + + useEffect(() => { + if (open) { + if (value.length) { + setFilterList(value); + setSelectedFilterNode(value?.[0] || []); + } else { + const DEFAULTITEM = ({ + id: getUniqueId(), + field: undefined, + } as unknown) as FilterConfigType; + + setFilterList([DEFAULTITEM]); + setSelectedFilterNode(DEFAULTITEM); + } + } + }, [open]); + + return wrapSSR( + +
+
+
+
筛选和参数项
+
+ +
+
+
+ + {filterList.map((item: FilterConfigType, index: string | number) => ( +
+ onFilterChange({ ...item, title })} + onChangeSort={onChangeSort} + onClickItem={() => setSelectedFilterNode(item)} + onDelete={() => delFilterItem(item.id)} + /> +
+ ))} +
+
+
+ +
+
+
筛选配置
+
+ + {selectedFilterNode && ( + + )} +
+
+
, + ); +}; + +export default FilterModal; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/style.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/style.ts new file mode 100644 index 00000000..2565320c --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/FilterModal/style.ts @@ -0,0 +1,55 @@ +import { genStyleHook } from '@formily/antd-v5/esm/__builtins__'; + +export default genStyleHook('filter-setting-modal', (token) => { + const { componentCls, colorBorder, colorText, colorBgTextHover, colorPrimary } = token; + + return { + [`${componentCls}__content`]: { + display: 'flex', + minHeight: '400px', + margin: '0 -24px', + + '&__left': { + width: '200px', + + '&-item': { + '&:hover': { + background: colorBgTextHover, + }, + }, + + '&-selected': { + background: colorBgTextHover, + color: colorText, + }, + + '&__add-filter': { + borderBottom: `1px solid ${colorBorder}`, + display: 'flex', + height: '34px', + lineHeight: '34px', + justifyContent: 'space-between', + paddingLeft: 15, + paddingRight: 10, + fontWeight: 700, + color: colorText, + '&-btn': { + cursor: 'pointer', + '&:hover': { + color: colorPrimary, + }, + + '&_disabled': { + display: 'none', + }, + }, + }, + }, + + '&__right': { + flex: 1, + borderLeft: `1px solid ${colorBorder}`, + }, + }, + }; +}); diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/Preview/index.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/Preview/index.tsx new file mode 100644 index 00000000..5a2563d4 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/Preview/index.tsx @@ -0,0 +1,97 @@ +import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import type { DescriptionsProps } from 'antd'; +import { Descriptions } from 'antd'; +import cls from 'classnames'; +import dayjs from 'dayjs'; +import { isEmpty } from 'lodash-es'; +import React, { useMemo } from 'react'; +import type { FilterConfigType } from '../type'; +import useStyle from './style'; + +export interface PreviewProps { + /** + * 筛选数组 + */ + filters: FilterConfigType[]; +} + +const Preview: React.FC = (props) => { + const prefixCls = usePrefixCls('formily-filter-setting-preview'); + const [wrapSSR, hashId] = useStyle(prefixCls); + const { filters = [] } = props; + + const items: DescriptionsProps['items'] = useMemo(() => { + if (!filters.length) { + return []; + } + + const _options = filters.map((item, index) => { + // 时间类型 + if (item.type === 'date') { + if (isEmpty(item.value)) { + return { + key: index, + label: item.title, + children: '不限', + }; + } + + const time = + item.params.dateType === 'date' + ? dayjs(item.value?.[0]).format(item.params.format) + : `${dayjs(item.value?.[0]).format(item.params.format)} 至 ${dayjs(item.value?.[1]).format( + item.params.format, + )}`; + + return { + key: index, + label: item.title, + children: time as string, + }; + } + + // 数值类型 + if (item.type === 'number') { + if (item.operator === 'BETWEEN') { + return { + key: index, + label: item.title, + children: `${item.value?.[0]} ~ ${item.value?.[1]}`, + }; + } + + return { + key: index, + label: item.title, + children: !item.value ? '不限' : `${item.operator}${item.value}`, + }; + } + + // 文本类型 + return { + key: index, + label: item.title, + children: !item.value + ? '不限' + : item.value && item.value.includes('all') + ? '全部' + : `包含:${item.value.toString()}`, + }; + }); + + return _options; + }, [filters]); + + if (!filters.length) { + return null; + } + + return wrapSSR( +
+

默认筛选:

+ +
, + ); +}; + +export default Preview; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/Preview/style.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/Preview/style.ts new file mode 100644 index 00000000..410ca3f3 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/Preview/style.ts @@ -0,0 +1,16 @@ +import { genStyleHook } from '@formily/antd-v5/esm/__builtins__'; + +export default genStyleHook('filter-setting-preview', (token) => { + const { componentCls, antCls } = token; + + return { + [componentCls]: { + width: '100%', + + [`${antCls}-descriptions-item-content`]: { + maxHeight: '100px', + overflowY: 'auto', + }, + }, + }; +}); diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/constants.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/constants.ts new file mode 100644 index 00000000..5601d8e4 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/constants.ts @@ -0,0 +1,64 @@ +import type { OpUnitType } from 'dayjs'; +import type { GranularityItem } from './type'; + +export const DEFAULT_OPTIONS: (GranularityItem & { other: string; otherLabel: string })[] = [ + { + label: '秒', + value: 'YYYY-MM-DD HH:mm:ss', + other: 'YYYY/MM/DD HH:mm:ss', + otherLabel: '秒', + granularity: 'second', + }, + { + label: '分钟', + value: 'YYYY-MM-DD HH:mm', + other: 'YYYY/MM/DD HH:mm', + otherLabel: '分钟', + granularity: 'minute', + }, + { + label: '小时', + value: 'YYYY-MM-DD HH', + other: 'YYYY/MM/DD HH', + otherLabel: '小时', + granularity: 'hour', + }, + { + label: '日', + value: 'YYYY-MM-DD', + other: 'YYYY/MM/DD', + otherLabel: '日', + granularity: 'day', + picker: 'date', + }, + { + label: '月', + value: 'YYYY-MM', + other: 'YYYY/MM', + otherLabel: '月', + granularity: 'month', + picker: 'month', + }, + { + label: '年', + value: 'YYYY', + other: 'YYYY', + otherLabel: '年', + granularity: 'year', + picker: 'year', + }, +]; + +export const DateGranularity: Record = { + 'YYYY/MM/DD HH:mm:ss': { format: 'YYYY/MM/DD HH:mm:ss', granularity: 'second' }, + 'YYYY-MM-DD HH:mm:ss': { format: 'YYYY-MM-DD HH:mm:ss', granularity: 'second' }, + 'YYYY/MM/DD HH:mm': { format: 'YYYY/MM/DD HH:mm:ss', granularity: 'minute' }, + 'YYYY-MM-DD HH:mm': { format: 'YYYY-MM-DD HH:mm:ss', granularity: 'minute' }, + 'YYYY/MM/DD HH': { format: 'YYYY/MM/DD HH:mm:ss', granularity: 'hour' }, + 'YYYY-MM-DD HH': { format: 'YYYY-MM-DD HH:mm:ss', granularity: 'hour' }, + 'YYYY/MM/DD': { format: 'YYYY/MM/DD HH:mm:ss', granularity: 'day' }, + 'YYYY-MM-DD': { format: 'YYYY-MM-DD HH:mm:ss', granularity: 'day' }, + 'YYYY/MM': { format: 'YYYY/MM/DD HH:mm:ss', granularity: 'month' }, + 'YYYY-MM': { format: 'YYYY-MM-DD HH:mm:ss', granularity: 'month' }, + YYYY: { format: 'YYYY-MM-DD HH:mm:ss', granularity: 'year' }, +}; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/helper.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/helper.ts new file mode 100644 index 00000000..746cd85e --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/helper.ts @@ -0,0 +1,46 @@ +import dayjs from 'dayjs'; +import { DateGranularity, DEFAULT_OPTIONS } from './constants'; + +export const getTimeFormat = ( + times: [string, string] | string, + dateGranularity: string, +): [string, string] | undefined => { + const _format = DateGranularity[dateGranularity]; + const _times = + typeof times === 'string' + ? dayjs(times).format(dateGranularity) + : [dayjs(times[0]).format(dateGranularity), dayjs(times[1]).format(dateGranularity)]; + + if (!_format) { + return undefined; + } + + if (typeof _times === 'string') { + const _timer: [string, string] = [ + dayjs(_times).format(_format.format), + dayjs(_times).endOf(_format.granularity).format(_format.format), + ]; + + return _timer; + } + + const _item: [string, string] = [ + dayjs(_times[0]).format(_format.format), + dayjs(_times[1]).endOf(_format.granularity).format(_format.format), + ]; + + return _item; +}; + +export const getGranularityOptions = (format: string) => { + const isDiagonalLineSplit = format.indexOf('/') === -1; + const options = DEFAULT_OPTIONS.map((item) => ({ + picker: item.picker, + granularity: item.granularity, + label: isDiagonalLineSplit ? item.label : item.otherLabel, + value: isDiagonalLineSplit ? item.value : item.other, + key: isDiagonalLineSplit ? item.value : item.other, + })); + + return options; +}; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/index.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/index.tsx new file mode 100644 index 00000000..5aa9234f --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/index.tsx @@ -0,0 +1,243 @@ +import { DownOutlined } from '@ant-design/icons'; +import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import { useUpdateEffect } from 'ahooks'; +import { DatePicker, Dropdown, Space } from 'antd'; +import cls from 'classnames'; +import dayjs from 'dayjs'; +import React, { useMemo, useRef, useState } from 'react'; +import { getGranularityOptions, getTimeFormat } from './helper'; +import useStyle from './style'; +import type { Granularity } from './type'; + +const { RangePicker } = DatePicker; +export interface FilterDateConfigProps { + // 是否自定义footer + isRenderExtraFooter?: boolean; + // 时间格式,eg: ""YYYY/MM/DD HH:mm:ss"" + format: string; + // 时间粒度, eg: "day" + granularity: Granularity; + // 时间类型 单日期|区间 + type: 'date' | 'range'; + // 默认时间 + value?: string | [string, string]; + bordered?: boolean; + size?: 'small' | 'middle' | 'large'; + onChange: (value: { + format: string; + granularity: Granularity; + type: 'date' | 'range'; + value?: string | [string, string]; + }) => void; +} + +const FilterDateConfig: React.FC = (props) => { + const { + isRenderExtraFooter = false, + format: outterFormat, + granularity: outterGranularity, + type: outterType = 'date', + value: outterValue, + size = 'middle', + bordered = true, + onChange, + } = props; + const [open, setOpen] = useState(false); + const prefixCls = usePrefixCls('formily-filter-setting-date'); + const [wrapSSR, hashId] = useStyle(prefixCls); + const dataRef = useRef(0); + const [state, setState] = useState<{ + granularity: Granularity; + format: string; + value?: string | [string, string]; + type: 'date' | 'range'; + }>({ + granularity: outterGranularity, + format: outterFormat, + value: outterValue, + type: outterType, + }); + + // RangePicker 开关变化 + const onRangePickerOpenChange = (open: boolean) => { + setOpen(open); + dataRef.current = 0; + }; + + // 时间区间变化 + const onRangePickerChange = (_: any, dateString: [string, string] | string) => { + let value: string | [string, string] | undefined; + + // 点击清空的情况 + // dateString is '' Or ['',''] + if (dateString === '' || (Array.isArray(dateString) && dateString.every((item) => item === ''))) { + value = undefined; + } else if (state.type === 'date') { + value = typeof dateString === 'string' ? getTimeFormat(dateString, state.format) : undefined; + } else { + value = Array.isArray(dateString) ? getTimeFormat(dateString, state.format) : undefined; + } + + setState((pre) => ({ ...pre, value })); + onChange({ value, format: state.format, type: state.type, granularity: state.granularity }); + }; + + // 粒度变化 + const onGranularityChange = (format: string, granularity: Granularity) => { + const value = state.value ? getTimeFormat(state.value, format) : undefined; + setState((pre) => ({ ...pre, format, granularity })); + onChange({ value, format, type: state.type, granularity }); + setOpen(true); + }; + + // 区间变化 + const onDateOrRange = (type: 'date' | 'range') => { + setState((pre) => ({ ...pre, type })); + const value = state.value ? getTimeFormat(state.value[0], state.format) : undefined; + onChange({ value, format: state.format, type, granularity: state.granularity }); + setOpen(true); + }; + + // 外部属性更新,同步状态 + useUpdateEffect(() => { + setState({ + granularity: outterGranularity, + format: outterFormat, + value: outterValue, + type: outterType, + }); + }, [outterGranularity, outterFormat, outterValue, outterType]); + + const granularityOptions = useMemo(() => { + return state.format + ? getGranularityOptions(state.format).map((item) => ({ + ...item, + label:
onGranularityChange(item.value, item.granularity)}>{item.label}
, + })) + : []; + }, [state.format]); + + const dateRangeTtpe = [ + { key: 'date', value: 'date', label:
onDateOrRange('date')}>单日期
}, + { key: 'range', value: 'range', label:
onDateOrRange('range')}>日期区间
}, + ]; + + const renderExtraFooter = ( + + ); + + return wrapSSR( +
+ {state.type === 'date' && ( + <> + {['year', 'month', 'day'].includes(state.granularity) ? ( + setOpen(open)} + value={ + state.value + ? dayjs(typeof state.value === 'string' ? state.value : state.value[0], state.format) + : undefined + } + picker={(state.granularity === 'day' ? 'date' : state.granularity) as 'year' | 'month' | 'date'} + format={state.format} + onChange={onRangePickerChange} + renderExtraFooter={() => (isRenderExtraFooter ? renderExtraFooter : null)} + /> + ) : ( + setOpen(open)} + value={ + state.value + ? dayjs(typeof state.value === 'string' ? state.value : state.value[0], state.format) + : undefined + } + showTime={{ format: state.format }} + format={state.format} + onChange={onRangePickerChange} + renderExtraFooter={() => (isRenderExtraFooter ? renderExtraFooter : null)} + /> + )} + + )} + + {state.type === 'range' && state.granularity && ( + <> + {['year', 'month', 'day'].includes(state.granularity) ? ( + { + if (dataRef.current !== 1) { + dataRef.current += 1; + } else { + setOpen(false); + } + }} + renderExtraFooter={() => (isRenderExtraFooter ? renderExtraFooter : null)} + /> + ) : ( + { + if (dataRef.current !== 3) { + dataRef.current += 1; + } else { + setOpen(false); + } + }} + format={state.format} + renderExtraFooter={() => (isRenderExtraFooter ? renderExtraFooter : null)} + /> + )} + + )} +
, + ); +}; + +export default FilterDateConfig; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/style.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/style.ts new file mode 100644 index 00000000..c06a0be2 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/style.ts @@ -0,0 +1,23 @@ +import { genStyleHook } from '@formily/antd-v5/esm/__builtins__'; + +export default genStyleHook('filter-setting-date', (token) => { + const { componentCls, antCls, colorPrimary, colorPrimaryHover, colorTextDescription } = token; + + return { + [componentCls]: { + display: 'flex', + }, + + [`${componentCls}__item`]: { + marginRight: '10px', + + '&__info': { + color: colorPrimary, + + '&:hover': { + color: colorPrimaryHover, + }, + }, + }, + }; +}); diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/type.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/type.ts new file mode 100644 index 00000000..a0799cb6 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterDateConfig/type.ts @@ -0,0 +1,10 @@ +export type Granularity = 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year'; + +type PickerType = 'year' | 'month' | 'date'; + +export type GranularityItem = { + label: string; + value: string; + granularity: Granularity; + picker?: PickerType; +}; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterNumberConfig/index.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterNumberConfig/index.tsx new file mode 100644 index 00000000..7e2ccdb9 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterNumberConfig/index.tsx @@ -0,0 +1,76 @@ +import { InputNumber } from 'antd'; +import React, { useEffect, useState } from 'react'; + +export interface FilterNumberConfigProps { + operator: '>=' | '<=' | 'BETWEEN'; + size?: 'small' | 'middle' | 'large'; + bordered?: boolean; + value?: number | [number, number]; + onChange: (value: number | [number, number] | undefined, operator: '>=' | '<=' | 'BETWEEN') => void; +} + +const FilterNumberConfig: React.FC = (props) => { + const { value: outterValue, operator, size = 'middle', bordered = true, onChange } = props; + const [ranges, setRanges] = useState<[number | null, number | null]>([null, null]); + + useEffect(() => { + if (!outterValue) { + return; + } + + if (typeof outterValue === 'number') { + if (operator === '>=') { + setRanges([outterValue, null]); + } else if (operator === '<=') { + setRanges([null, outterValue]); + } + } else { + setRanges(outterValue); + } + }, [outterValue]); + + const onValueChange = (val: [number | null, number | null]) => { + const [minVal, maxVal] = val; + setRanges(val); + // 处理数据结构 + if (minVal) { + if (maxVal) { + if (minVal < maxVal) { + onChange([minVal, maxVal], 'BETWEEN'); + } else { + onChange([maxVal, minVal], 'BETWEEN'); + } + } else { + onChange(minVal, '>='); + } + } else { + if (maxVal) { + onChange(maxVal, '<='); + } else { + onChange(undefined, '<='); + } + } + }; + + return ( +
+ onValueChange([value, ranges[1]])} + /> + - + onValueChange([ranges[0], value])} + /> +
+ ); +}; + +export default FilterNumberConfig; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterStringConfig/helper.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterStringConfig/helper.ts new file mode 100644 index 00000000..7840a898 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterStringConfig/helper.ts @@ -0,0 +1,5 @@ +export const getOptions = (domain: string[], disabled: boolean) => { + const _options = domain.map((item) => ({ label: item, value: item, disabled })); + + return [{ label: '全部', value: 'all' }, ..._options]; +}; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterStringConfig/index.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterStringConfig/index.tsx new file mode 100644 index 00000000..278e76cb --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterStringConfig/index.tsx @@ -0,0 +1,92 @@ +import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import { useUpdateEffect } from 'ahooks'; +import { Select } from 'antd'; +import React, { useEffect, useState } from 'react'; +import { getOptions } from './helper'; +import useStyle from './style'; + +export interface FilterStringConfigProps { + filterType: 'single' | 'multiple'; + domain: string[]; + value?: string[]; + onChange: (value?: string[]) => void; + maxTagCount?: number; + size?: 'small' | 'middle' | 'large'; + bordered?: boolean; +} + +const FilterStringConfig: React.FC = (props) => { + const { value: outterValue, domain, filterType, size = 'middle', bordered = true, onChange } = props; + const [selectedOptions, setSelectedOptions] = useState(outterValue); + const [options, setOptions] = useState<{ label: string; value: string; disabled?: boolean }[]>(); + const prefixCls = usePrefixCls('formily-filter-string-config'); + const [wrapSSR, hashId] = useStyle(prefixCls); + + const onValueChange = (val: string | string[]) => { + if (filterType === 'single') { + const _val = typeof val === 'string' ? [val] : undefined; + onChange(_val); + setSelectedOptions(_val); + } else { + if (typeof val === 'string') return; + if (val.includes('all')) { + const _options = getOptions(domain, true); + setOptions(_options); + onChange(['all']); + setSelectedOptions(['all']); + } else { + const _options = getOptions(domain, false); + setOptions(_options); + onChange(val); + setSelectedOptions(val); + } + } + }; + + useUpdateEffect(() => { + setSelectedOptions(outterValue); + }, [outterValue]); + + useEffect(() => { + if (domain && domain.length) { + const isUsable = filterType === 'multiple' && outterValue && outterValue[0] === 'all' ? true : false; + const _options = getOptions(domain, isUsable); + setOptions(_options); + } + }, [domain, filterType]); + + return wrapSSR( + <> + {filterType === 'single' && ( + <>{_props.label} } + style={{ width: '100%', textAlign: 'left' }} + placeholder="请选择" + value={selectedOptions} + options={options} + onChange={onValueChange} + /> + )} + , + ); +}; + +export default FilterStringConfig; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterStringConfig/style.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterStringConfig/style.ts new file mode 100644 index 00000000..eaf8ed5c --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/components/FilterStringConfig/style.ts @@ -0,0 +1,9 @@ +import { genStyleHook } from '@formily/antd-v5/esm/__builtins__'; + +export default genStyleHook('filter-string-config', (token) => { + const { componentCls } = token; + + return { + [componentCls]: {}, + }; +}); diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/components/index.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/components/index.ts new file mode 100644 index 00000000..39fd5fea --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/components/index.ts @@ -0,0 +1,3 @@ +export { default as FilterDateSetting } from './FilterDateConfig'; +export { default as FilterStringConfig } from './FilterStringConfig'; +export { default as FilterNumberConfig } from './FilterNumberConfig'; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/demos/default.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/demos/default.tsx new file mode 100644 index 00000000..dc90596d --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/demos/default.tsx @@ -0,0 +1,113 @@ +import { FilterConfiguration } from '@antv/li-p2'; +import { Form, FormItem } from '@formily/antd-v5'; +import type { Form as FormInstance } from '@formily/core'; +import { createForm, onFormValuesChange } from '@formily/core'; +import { createSchemaField, FormConsumer } from '@formily/react'; +import React from 'react'; + +const form = createForm({ + initialValues: { filterConfiguration: [] }, + effects() { + onFormValuesChange((formIns: FormInstance) => { + console.log('formIns.values: ', formIns.values); + }); + }, +}); + +const SchemaField = createSchemaField({ + components: { + FormItem, + FilterConfiguration, + }, +}); + +const fieldList = [ + { + type: 'number', + name: 'depth', + label: 'depth', + value: 'depth', + typeName: '数值', + domain: [3, 34.8], + typeColor: 'green', + }, + { + type: 'number', + name: 'mag', + label: 'mag', + value: 'mag', + typeName: '数值', + domain: [5, 7.9], + typeColor: 'green', + }, + { + type: 'string', + name: 'title', + label: 'title', + value: '名称', + typeName: '文本', + typeColor: 'gold', + domain: ['001', '002', '003', '004', '005', '006'], + }, + { + domain: [ + '2022-01-01', + '2020-10-16', + '2019-05-01', + '2022-09-16', + '2021-03-06', + '2023-03-08', + '2020-07-15', + '2020-06-28', + '2020-04-26', + '2021-08-30', + '2022-10-01', + '2021-10-24', + '2020-07-01', + '2020-04-01', + ], + format: 'YYYY-MM-DD', + label: '开盘日期', + name: '开盘日期', + type: 'date', + typeColor: 'geekblue', + typeName: '日期', + value: '开盘日期', + }, +]; + +const schema = { + type: 'object', + properties: { + filterConfiguration: { + type: 'array', + title: '筛选器', + default: [], + 'x-decorator': 'FormItem', + 'x-component': 'FilterConfiguration', + 'x-component-props': { + dots: false, + range: true, + slider: false, + options: [...fieldList], + }, + 'x-decorator-props': {}, + }, + }, +}; + +export default () => { + return ( +
+ + + + {() => ( + +
{JSON.stringify(form.values, null, 2)}
+
+ )} +
+ + ); +}; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/index.md b/packages/li-p2/src/components/Formily/FilterConfiguration/index.md new file mode 100644 index 00000000..968f2158 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/index.md @@ -0,0 +1,24 @@ +--- +toc: content +order: 6 +group: + title: formily 组件 + order: 1 +nav: + title: 组件 + path: /components +--- + +# 筛选条件配置 - FilterConfiguration + +## 介绍 + +运行时筛选器 + +## 代码演示 + +### 默认示例 + + + + diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/index.tsx b/packages/li-p2/src/components/Formily/FilterConfiguration/index.tsx new file mode 100644 index 00000000..6f40a078 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/index.tsx @@ -0,0 +1,74 @@ +import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; +import { connect } from '@formily/react'; +import { useUpdateEffect } from 'ahooks'; +import { Button } from 'antd'; +import cls from 'classnames'; +import React, { useState } from 'react'; +import FilterModal from './FilterModal'; +import Preview from './Preview'; +import useStyle from './style'; +import type { FilterConfigType, OptionType } from './type'; + +export interface FilterConfigurationProps { + /** + * 筛选字段 + */ + options: OptionType[]; + /** + * value + */ + value?: FilterConfigType[]; + /** + * 选择发生改变时 + */ + onChange?: (value: FilterConfigType[]) => void; +} + +const Internal: React.FC = (props) => { + const prefixCls = usePrefixCls('formily-filter-setting'); + const [wrapSSR, hashId] = useStyle(prefixCls); + const { options, value = [], onChange } = props; + const [isModalOpen, setIsModalOpen] = useState(false); + const [filterList, setFilterList] = useState(value); + + useUpdateEffect(() => { + setFilterList(value); + }, [value]); + + const handleCancel = () => { + setIsModalOpen(false); + }; + + // 获取所有数据变更信息 + const onfiltersChange = (val: FilterConfigType[]) => { + if (onChange) { + onChange(val); + } + + setFilterList(val); + setIsModalOpen(false); + }; + + return wrapSSR( +
+
+ + +
+ + +
, + ); +}; + +const FilterConfiguration = connect(Internal); + +export default FilterConfiguration; diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/style.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/style.ts new file mode 100644 index 00000000..67efa137 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/style.ts @@ -0,0 +1,18 @@ +import { genStyleHook } from '@formily/antd-v5/esm/__builtins__'; + +export default genStyleHook('filter-setting', (token) => { + const { componentCls } = token; + + return { + [componentCls]: { + display: 'inline-block', + width: '100%', + }, + + [`${componentCls}__list`]: { + '&__btn': { + width: '100%', + }, + }, + }; +}); diff --git a/packages/li-p2/src/components/Formily/FilterConfiguration/type.ts b/packages/li-p2/src/components/Formily/FilterConfiguration/type.ts new file mode 100644 index 00000000..81f9c402 --- /dev/null +++ b/packages/li-p2/src/components/Formily/FilterConfiguration/type.ts @@ -0,0 +1,83 @@ +export type Granularity = 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year'; + +/** + * 数据列表字段项 + */ +export type OptionType = { + /** + * 值 + */ + value: string; + /** + * label + */ + label: string; + /** + * 数据类型 + */ + type?: string; + /** + * 类型名 + */ + typeName?: string; + /** + * 类型颜色 + */ + typeColor?: string; + /** + * 日期格式 + */ + format?: string; + /** + * 字段数值范围 + */ + domain?: string[] | [number, number]; +}; + +// FilterConfiguration + +type FilterConfigBase = { + id: string; + title: string; + field: string; +}; + +export type FilterStringConfigType = FilterConfigBase & { + type: 'string'; + operator: 'IN'; + value?: string[]; + params: { + // 筛选方式 + filterType: 'multiple' | 'single'; + }; +}; + +export type FilterDateConfigType = FilterConfigBase & { + type: 'date'; + /** 日期粒度 */ + granularity: Granularity; + operator: 'BETWEEN'; + value?: [string, string]; + params: { + // 日期格式 + format: string; + // 筛选方式 + dateType: 'date' | 'range'; + }; +}; + +export type FilterNumberConfigType = FilterConfigBase & + ( + | { + type: 'number'; + operator: '>=' | '<='; + value?: number; + } + | { + type: 'number'; + operator: 'BETWEEN'; + value?: [number, number]; + } + ); + +export type FilterConfigType = FilterDateConfigType | FilterNumberConfigType | FilterStringConfigType; diff --git a/packages/li-p2/src/components/Formily/index.ts b/packages/li-p2/src/components/Formily/index.ts index a0e9992d..448fe9d3 100644 --- a/packages/li-p2/src/components/Formily/index.ts +++ b/packages/li-p2/src/components/Formily/index.ts @@ -4,6 +4,16 @@ export type { ColorRange } from './ColorRangeSelector/Internal/types'; export { default as ControlPositionSelect } from './ControlPositionSelect'; export { default as FieldSelect } from './FieldSelect'; export type { FieldSelectOptionType } from './FieldSelect/Select/types'; +export { default as FilterConfiguration } from './FilterConfiguration'; +export { default as FilterDateConfig } from './FilterConfiguration/components/FilterDateConfig'; +export { default as FilterNumberConfig } from './FilterConfiguration/components/FilterNumberConfig'; +export { default as FilterStringConfig } from './FilterConfiguration/components/FilterStringConfig'; +export type { + FilterConfigType, + FilterDateConfigType, + FilterNumberConfigType, + FilterStringConfigType, +} from './FilterConfiguration/type'; export { default as FormCollapse } from './FormCollapse'; export { default as Offset } from './Offset'; export { default as RibbonSelect } from './RibbonSelect'; diff --git a/packages/li-sdk/src/hooks/useDatasetFilter.ts b/packages/li-sdk/src/hooks/useDatasetFilter.ts index b03f214c..79170432 100644 --- a/packages/li-sdk/src/hooks/useDatasetFilter.ts +++ b/packages/li-sdk/src/hooks/useDatasetFilter.ts @@ -1,19 +1,56 @@ +import { useUpdate, useUpdateEffect } from 'ahooks'; import { produce } from 'immer'; +import { useEffect } from 'react'; import type { DatasetFilter, FilterNode } from '../specs'; +import { DatasetsStoreEvent } from '../state/constants'; +import type { Dataset } from '../types'; import { isLocalOrRemoteDataset } from '../utils'; -import { useDatasetBase, useStateManager } from './internal'; +import { useStateManager } from './internal'; + +function useFilter(datasetId: string) { + const { datasetStore } = useStateManager(); + const update = useUpdate(); + + useUpdateEffect(() => { + update(); + }, [datasetStore.getDatasets()]); + + useUpdateEffect(() => { + update(); + }, [datasetId]); + + useEffect(() => { + const onClearFilter = (id: string) => { + if (id === datasetId) update(); + }; + const onUpdateDataset = (data: Dataset) => { + if (data.id === datasetId) update(); + }; + datasetStore.on(DatasetsStoreEvent.ADD_DATASET, onUpdateDataset); + datasetStore.on(DatasetsStoreEvent.CLEAR_FILTER, onClearFilter); + datasetStore.on(DatasetsStoreEvent.UPDATE_FILTER, onUpdateDataset); + return () => { + datasetStore.off(DatasetsStoreEvent.ADD_DATASET, onUpdateDataset); + datasetStore.off(DatasetsStoreEvent.CLEAR_FILTER, onClearFilter); + datasetStore.off(DatasetsStoreEvent.UPDATE_FILTER, onUpdateDataset); + }; + }, [datasetId, datasetStore]); + + return datasetStore.getFilter(datasetId); +} export function useDatasetFilter(datasetId: string) { const { datasetStore } = useStateManager(); - const dataset = useDatasetBase(datasetId); + const filter = useFilter(datasetId); + const dataset = datasetStore.getDatasetById(datasetId); const isLocalOrRemote = dataset && isLocalOrRemoteDataset(dataset); - const filter = (isLocalOrRemote && dataset.filter) || undefined; const addFilterNode = (filterNode: FilterNode, filterLogicalOperator?: DatasetFilter['relation']) => { if (isLocalOrRemote) { let _filter: DatasetFilter; - if (dataset.filter) { - _filter = { ...dataset.filter, children: dataset.filter.children.concat(filterNode) }; + const lastFilter = datasetStore.getFilter(datasetId); + if (lastFilter) { + _filter = { ...lastFilter, children: lastFilter.children.concat(filterNode) }; } else { _filter = { relation: filterLogicalOperator || 'AND', children: [filterNode] }; } @@ -22,8 +59,9 @@ export function useDatasetFilter(datasetId: string) { }; const updateFilterNode = (filterId: string, filterNode: Partial>) => { - if (isLocalOrRemote && dataset.filter && dataset.filter.children.find((item) => item.id === filterId)) { - const _filter = produce(dataset.filter, (draftState) => { + const lastFilter = datasetStore.getFilter(datasetId); + if (isLocalOrRemote && lastFilter && lastFilter.children.find((item) => item.id === filterId)) { + const _filter = produce(lastFilter, (draftState) => { const index = draftState.children.findIndex((item) => item.id === filterId); if (index !== -1) draftState.children[index] = { ...draftState.children[index], ...filterNode } as FilterNode; }); @@ -32,8 +70,9 @@ export function useDatasetFilter(datasetId: string) { }; const removeFilterNode = (filterId: string) => { - if (isLocalOrRemote && dataset.filter && dataset.filter.children.find((item) => item.id === filterId)) { - const _filter = produce(dataset.filter, (draftState) => { + const lastFilter = datasetStore.getFilter(datasetId); + if (isLocalOrRemote && lastFilter && lastFilter.children.find((item) => item.id === filterId)) { + const _filter = produce(lastFilter, (draftState) => { const index = draftState.children.findIndex((item) => item.id === filterId); if (index !== -1) draftState.children.splice(index, 1); }); diff --git a/packages/li-sdk/src/state/datasets.ts b/packages/li-sdk/src/state/datasets.ts index 37ef64bf..bea12d09 100644 --- a/packages/li-sdk/src/state/datasets.ts +++ b/packages/li-sdk/src/state/datasets.ts @@ -90,7 +90,7 @@ class DatasetStore extends BaseStore { const dataset = this.getDatasetById(datasetId); if (dataset && isLocalOrRemoteDataset(dataset)) { this.state.datasets.set(datasetId, { ...dataset, filter: filter }); - this.emit(DatasetsStoreEvent.UPDATE_FILTER, dataset.filter); + this.emit(DatasetsStoreEvent.UPDATE_FILTER, this.getDatasetById(datasetId)); this.emit(DatasetsStoreEvent.UPDATE_DATASET, this.getDatasetById(datasetId)); } } @@ -100,7 +100,7 @@ class DatasetStore extends BaseStore { if (dataset && isLocalOrRemoteDataset(dataset) && dataset.filter) { dataset.filter = undefined; this.state.datasets.set(datasetId, { ...dataset }); - this.emit(DatasetsStoreEvent.CLEAR_FILTER, dataset.filter); + this.emit(DatasetsStoreEvent.CLEAR_FILTER, datasetId); this.emit(DatasetsStoreEvent.UPDATE_DATASET, this.getDatasetById(datasetId)); } }