+
+ 基于 useUrlFilter 封装的简化版 Hook,使用 createUrlFilterReader 解析 filterParams[key] 格式,自动解析 label:value,支持单选和多选
+
+
+ 1. 数组形式 — 默认单选
+
+{`const [filter, setFilter] = useUrlFilterValue(['keyword', 'status']);
+
+// URL: ?filterParams[keyword]=前端开发&filterParams[status]=招聘中:active
+// → filter: [
+// { name: 'keyword', value: { label: '前端开发', value: '前端开发' } },
+// { name: 'status', value: { label: '招聘中', value: 'active' } }
+// ]`}
+
+
+
+
+ 2. 对象形式 — 多选 + 自定义转换
+
+{`// { multi: true } 表示多选,value 为数组
+// 函数接收解析后的值,返回 filter 项或 null 跳过
+const [filter, setFilter] = useUrlFilterValue({
+ keyword: true, // 单选,默认转换
+ city: { multi: true }, // 多选
+ status: (parsed) => parsed // 自定义:直接用解析值
+ ? { name: 'status', value: parsed }
+ : null
+});
+
+// URL: ?filterParams[city]=上海:010,北京:020
+// → city 的 value: [{ label: '上海', value: '010' }, { label: '北京', value: '020' }]`}
+
+
+
+
+ 对比 useUrlFilter
+
+{`// useUrlFilter(完整控制,需手动解析)
+const [filter, setFilter] = useUrlFilter({
+ readUrlParams: (searchParams) => {
+ const { takeFilterEntry, getConsumedKeys } = createUrlFilterReader(searchParams);
+ const keyword = takeFilterEntry('keyword');
+ const city = takeFilterEntry('city', { multi: true });
+ return { consumedKeys: getConsumedKeys(), keyword, city };
+ },
+ buildFilter: ({ keyword, city }) => [
+ ...(keyword ? [{ name: 'keyword', value: keyword }] : []),
+ ...(city ? [{ name: 'city', value: city }] : []),
+ ]
+});
+
+// useUrlFilterValue(等价简化写法)
+const [filter, setFilter] = useUrlFilterValue({
+ keyword: true,
+ city: { multi: true }
+});`}
+
+
);
};
@@ -1901,6 +1963,56 @@ const [filter, setFilter] = useUrlFilter({
});
```
+#### useUrlFilterValue
+
+从 URL 参数初始化 Filter 状态的 Hook(简化版)。基于 `useUrlFilter` 封装,使用 `createUrlFilterReader` 解析 `filterParams[key]` 格式的 URL 参数,自动解析 `label:value` 格式,支持单选和多选。
+
+**函数签名:**
+```javascript
+useUrlFilterValue(mapping): [array, function]
+```
+
+**参数说明:**
+
+| 参数名 | 说明 | 类型 | 必填 |
+|--------|------|------|------|
+| mapping | URL 参数映射配置,支持数组或对象格式 | string[] \| object | 是 |
+
+**mapping 格式:**
+
+- **数组形式**:`['key1', 'key2']`,默认单选,自动创建 `{ name: key, value: { label, value } }` 格式的筛选项
+- **对象形式**:`{ key1: true, key2: { multi: true }, key3: fn }`
+ - 值为 `true`:单选,使用默认转换
+ - 值为 `{ multi: true }`:多选,value 为 `[{ label, value }, ...]` 数组
+ - 值为函数:自定义转换,接收解析后的值(单选为 `{ label, value }`,多选为数组),返回 filter 项或 `null`/falsy 跳过
+
+**返回值:**
+
+| 返回值 | 说明 | 类型 |
+|--------|------|------|
+| [0] | 初始筛选值数组 | array |
+| [1] | 设置筛选值的函数 | function |
+
+**示例:**
+```javascript
+// 数组形式(默认单选)
+const [filter, setFilter] = useUrlFilterValue(['keyword', 'status']);
+// URL: ?filterParams[keyword]=前端开发&filterParams[status]=招聘中:active
+// → filter: [
+// { name: 'keyword', value: { label: '前端开发', value: '前端开发' } },
+// { name: 'status', value: { label: '招聘中', value: 'active' } }
+// ]
+
+// 对象形式(多选 + 自定义转换)
+const [filter, setFilter] = useUrlFilterValue({
+ keyword: true,
+ city: { multi: true },
+ status: (parsed) => parsed ? { name: 'status', value: parsed } : null
+});
+// URL: ?filterParams[city]=上海:010,北京:020
+// → city 的 value: [{ label: '上海', value: '010' }, { label: '北京', value: '020' }]
+```
+
#### createUrlParamsReader
创建 URL 参数读取器,自动追踪已消费的参数 key。
diff --git a/src/components/Filter/doc/api.md b/src/components/Filter/doc/api.md
index 1205f4f..55a54bd 100644
--- a/src/components/Filter/doc/api.md
+++ b/src/components/Filter/doc/api.md
@@ -573,6 +573,56 @@ const [filter, setFilter] = useUrlFilter({
});
```
+#### useUrlFilterValue
+
+从 URL 参数初始化 Filter 状态的 Hook(简化版)。基于 `useUrlFilter` 封装,使用 `createUrlFilterReader` 解析 `filterParams[key]` 格式的 URL 参数,自动解析 `label:value` 格式,支持单选和多选。
+
+**函数签名:**
+```javascript
+useUrlFilterValue(mapping): [array, function]
+```
+
+**参数说明:**
+
+| 参数名 | 说明 | 类型 | 必填 |
+|--------|------|------|------|
+| mapping | URL 参数映射配置,支持数组或对象格式 | string[] \| object | 是 |
+
+**mapping 格式:**
+
+- **数组形式**:`['key1', 'key2']`,默认单选,自动创建 `{ name: key, value: { label, value } }` 格式的筛选项
+- **对象形式**:`{ key1: true, key2: { multi: true }, key3: fn }`
+ - 值为 `true`:单选,使用默认转换
+ - 值为 `{ multi: true }`:多选,value 为 `[{ label, value }, ...]` 数组
+ - 值为函数:自定义转换,接收解析后的值(单选为 `{ label, value }`,多选为数组),返回 filter 项或 `null`/falsy 跳过
+
+**返回值:**
+
+| 返回值 | 说明 | 类型 |
+|--------|------|------|
+| [0] | 初始筛选值数组 | array |
+| [1] | 设置筛选值的函数 | function |
+
+**示例:**
+```javascript
+// 数组形式(默认单选)
+const [filter, setFilter] = useUrlFilterValue(['keyword', 'status']);
+// URL: ?filterParams[keyword]=前端开发&filterParams[status]=招聘中:active
+// → filter: [
+// { name: 'keyword', value: { label: '前端开发', value: '前端开发' } },
+// { name: 'status', value: { label: '招聘中', value: 'active' } }
+// ]
+
+// 对象形式(多选 + 自定义转换)
+const [filter, setFilter] = useUrlFilterValue({
+ keyword: true,
+ city: { multi: true },
+ status: (parsed) => parsed ? { name: 'status', value: parsed } : null
+});
+// URL: ?filterParams[city]=上海:010,北京:020
+// → city 的 value: [{ label: '上海', value: '010' }, { label: '北京', value: '020' }]
+```
+
#### createUrlParamsReader
创建 URL 参数读取器,自动追踪已消费的参数 key。
diff --git a/src/components/Filter/doc/use-url-filter.js b/src/components/Filter/doc/use-url-filter.js
index 5ca474b..63d964c 100644
--- a/src/components/Filter/doc/use-url-filter.js
+++ b/src/components/Filter/doc/use-url-filter.js
@@ -10,6 +10,7 @@ const {
getFilterValue,
createFilterValueMapper,
pickSelectValues,
+ useUrlFilterValue,
} = _Filter;
const { useState, useMemo } = React;
const { Space, Card, Divider, Typography, Button, Alert, Tag } = antd;
@@ -213,6 +214,67 @@ mapFilterValue(filterValue, getFilterValue);
{' ' + JSON.stringify(mappedSample, null, 2)}
+
+ {/* ===== useUrlFilterValue ===== */}
+
+
+ 基于 useUrlFilter 封装的简化版 Hook,使用 createUrlFilterReader 解析 filterParams[key] 格式,自动解析 label:value,支持单选和多选
+
+
+ 1. 数组形式 — 默认单选
+
+{`const [filter, setFilter] = useUrlFilterValue(['keyword', 'status']);
+
+// URL: ?filterParams[keyword]=前端开发&filterParams[status]=招聘中:active
+// → filter: [
+// { name: 'keyword', value: { label: '前端开发', value: '前端开发' } },
+// { name: 'status', value: { label: '招聘中', value: 'active' } }
+// ]`}
+
+
+
+
+ 2. 对象形式 — 多选 + 自定义转换
+
+{`// { multi: true } 表示多选,value 为数组
+// 函数接收解析后的值,返回 filter 项或 null 跳过
+const [filter, setFilter] = useUrlFilterValue({
+ keyword: true, // 单选,默认转换
+ city: { multi: true }, // 多选
+ status: (parsed) => parsed // 自定义:直接用解析值
+ ? { name: 'status', value: parsed }
+ : null
+});
+
+// URL: ?filterParams[city]=上海:010,北京:020
+// → city 的 value: [{ label: '上海', value: '010' }, { label: '北京', value: '020' }]`}
+
+
+
+
+ 对比 useUrlFilter
+
+{`// useUrlFilter(完整控制,需手动解析)
+const [filter, setFilter] = useUrlFilter({
+ readUrlParams: (searchParams) => {
+ const { takeFilterEntry, getConsumedKeys } = createUrlFilterReader(searchParams);
+ const keyword = takeFilterEntry('keyword');
+ const city = takeFilterEntry('city', { multi: true });
+ return { consumedKeys: getConsumedKeys(), keyword, city };
+ },
+ buildFilter: ({ keyword, city }) => [
+ ...(keyword ? [{ name: 'keyword', value: keyword }] : []),
+ ...(city ? [{ name: 'city', value: city }] : []),
+ ]
+});
+
+// useUrlFilterValue(等价简化写法)
+const [filter, setFilter] = useUrlFilterValue({
+ keyword: true,
+ city: { multi: true }
+});`}
+
+
);
};
diff --git a/src/components/Filter/index.js b/src/components/Filter/index.js
index e2fe066..6ee292b 100644
--- a/src/components/Filter/index.js
+++ b/src/components/Filter/index.js
@@ -8,6 +8,7 @@ import FilterProvider from './FilterProvider';
import pickSelectValues from "./pickSelectValues";
import createFilterValueMapper from "./createFilterValueMapper";
import useUrlFilter, {createUrlParamsReader, stripConsumedUrlParams} from "./useUrlFilter";
+import useUrlFilterValue from "./useUrlFilterValue";
import filterToUrlParams, {parseFilterEntry, takeFilterEntry, createUrlFilterReader} from "./filterToUrlParams";
import filterInterceptors, {singleSelectInterceptor, multiSelectInterceptor} from "./filterInterceptors";
@@ -20,6 +21,7 @@ Filter.FilterProvider = FilterProvider;
Filter.pickSelectValues = pickSelectValues;
Filter.createFilterValueMapper = createFilterValueMapper;
Filter.useUrlFilter = useUrlFilter;
+Filter.useUrlFilterValue = useUrlFilterValue;
Filter.createUrlParamsReader = createUrlParamsReader;
Filter.stripConsumedUrlParams = stripConsumedUrlParams;
Filter.filterToUrlParams = filterToUrlParams;
@@ -30,7 +32,7 @@ Filter.filterInterceptors = filterInterceptors;
Filter.singleSelectInterceptor = singleSelectInterceptor;
Filter.multiSelectInterceptor = multiSelectInterceptor;
export default Filter;
-export {fields, getFilterValue, useFilter, withFilterValue, SearchInput, FilterProvider, pickSelectValues, createFilterValueMapper, useUrlFilter, createUrlParamsReader, stripConsumedUrlParams, filterToUrlParams, parseFilterEntry, takeFilterEntry, createUrlFilterReader, filterInterceptors, singleSelectInterceptor, multiSelectInterceptor};
+export {fields, getFilterValue, useFilter, withFilterValue, SearchInput, FilterProvider, pickSelectValues, createFilterValueMapper, useUrlFilter, useUrlFilterValue, createUrlParamsReader, stripConsumedUrlParams, filterToUrlParams, parseFilterEntry, takeFilterEntry, createUrlFilterReader, filterInterceptors, singleSelectInterceptor, multiSelectInterceptor};
export {default as AdvancedFilter, advancedFields} from "./AdvancedFilter";
export {default as FilterValueDisplay} from "./FilterValueDisplay";
export {default as FilterItem} from "./FilterItem";
diff --git a/src/components/Filter/useUrlFilter.js b/src/components/Filter/useUrlFilter.js
index e6ba26e..f9421e4 100644
--- a/src/components/Filter/useUrlFilter.js
+++ b/src/components/Filter/useUrlFilter.js
@@ -1,5 +1,5 @@
-import { useState, useRef, useEffect } from 'react';
-import { useSearchParams } from 'react-router-dom';
+import {useState, useRef, useEffect} from 'react';
+import {useSearchParams} from 'react-router-dom';
/**
* 创建 URL 参数读取器,自动追踪已消费的参数 key。
@@ -14,14 +14,14 @@ import { useSearchParams } from 'react-router-dom';
* // getConsumedKeys() => ['tenantOrgId', 'orgName']
*/
export const createUrlParamsReader = (searchParams) => {
- const consumedKeys = [];
- const take = (key) => {
- if (!searchParams.has(key)) return null;
- consumedKeys.push(key);
- return searchParams.get(key);
- };
- const getConsumedKeys = () => consumedKeys;
- return { take, getConsumedKeys };
+ const consumedKeys = [];
+ const take = (key) => {
+ if (!searchParams.has(key)) return null;
+ consumedKeys.push(key);
+ return searchParams.get(key);
+ };
+ const getConsumedKeys = () => consumedKeys;
+ return {take, getConsumedKeys};
};
/**
@@ -32,16 +32,16 @@ export const createUrlParamsReader = (searchParams) => {
* @returns {URLSearchParams|null}
*/
export const stripConsumedUrlParams = (searchParams, consumedKeys) => {
- if (!consumedKeys?.length) return null;
- const next = new URLSearchParams(searchParams);
- let changed = false;
- consumedKeys.forEach(key => {
- if (next.has(key)) {
- next.delete(key);
- changed = true;
- }
- });
- return changed ? next : null;
+ if (!consumedKeys?.length) return null;
+ const next = new URLSearchParams(searchParams);
+ let changed = false;
+ consumedKeys.forEach(key => {
+ if (next.has(key)) {
+ next.delete(key);
+ changed = true;
+ }
+ });
+ return changed ? next : null;
};
/**
@@ -67,27 +67,27 @@ export const stripConsumedUrlParams = (searchParams, consumedKeys) => {
* ]
* });
*/
-const useUrlFilter = ({ readUrlParams, buildFilter }) => {
- const [searchParams, setSearchParams] = useSearchParams();
- const urlFilterSnapshotRef = useRef(null);
+const useUrlFilter = ({readUrlParams, buildFilter}) => {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const urlFilterSnapshotRef = useRef(null);
- if (urlFilterSnapshotRef.current === null) {
- urlFilterSnapshotRef.current = readUrlParams(searchParams);
- }
+ if (urlFilterSnapshotRef.current === null) {
+ urlFilterSnapshotRef.current = readUrlParams(searchParams);
+ }
- const [filter, setFilter] = useState(() => buildFilter(urlFilterSnapshotRef.current));
+ const [filter, setFilter] = useState(() => buildFilter(urlFilterSnapshotRef.current));
- const urlStrippedRef = useRef(false);
- useEffect(() => {
- if (urlStrippedRef.current) return;
- urlStrippedRef.current = true;
- const nextParams = stripConsumedUrlParams(searchParams, urlFilterSnapshotRef.current?.consumedKeys);
- if (nextParams) {
- setSearchParams(nextParams, { replace: true });
- }
- }, [searchParams, setSearchParams]);
+ const urlStrippedRef = useRef(false);
+ useEffect(() => {
+ if (urlStrippedRef.current) return;
+ urlStrippedRef.current = true;
+ const nextParams = stripConsumedUrlParams(searchParams, urlFilterSnapshotRef.current?.consumedKeys);
+ if (nextParams) {
+ setSearchParams(nextParams, {replace: true});
+ }
+ }, [searchParams, setSearchParams]);
- return [filter, setFilter];
+ return [filter, setFilter];
};
export default useUrlFilter;
diff --git a/src/components/Filter/useUrlFilterValue.js b/src/components/Filter/useUrlFilterValue.js
new file mode 100644
index 0000000..52dc852
--- /dev/null
+++ b/src/components/Filter/useUrlFilterValue.js
@@ -0,0 +1,77 @@
+import useUrlFilter from './useUrlFilter';
+import {createUrlFilterReader} from './filterToUrlParams';
+
+/**
+ * 从 URL 参数初始化 Filter 状态的 hook(简化版)。
+ *
+ * 基于 useUrlFilter 封装,使用 createUrlFilterReader 解析 filterParams[key] 格式的 URL 参数,
+ * 自动解析 label:value 格式,支持单选和多选。
+ *
+ * @param {string[]|Object} mapping - URL 参数映射
+ * - 数组: ['tenantOrgId', 'orgName'],默认单选,自动创建 { name: key, value: { label, value } }
+ * - 对象:
+ * - 值为 true: 单选,使用默认转换 { name: key, value: { label, value } }
+ * - 值为 { multi: true }: 多选,value 为 [{ label, value }, ...] 数组
+ * - 值为函数: 自定义转换,接收解析后的值(单选为 { label, value },多选为数组),返回 filter 项或 null/falsy 跳过
+ * @returns {[Array, Function]} - [filter, setFilter]
+ *
+ * @example
+ * // 数组形式(默认单选)
+ * const [filter, setFilter] = useUrlFilterValue(['keyword', 'status']);
+ * // URL: ?filterParams[keyword]=前端开发&filterParams[status]=招聘中:active
+ * // → filter: [
+ * // { name: 'keyword', value: { label: '前端开发', value: '前端开发' } },
+ * // { name: 'status', value: { label: '招聘中', value: 'active' } }
+ * // ]
+ *
+ * @example
+ * // 对象形式(多选 + 自定义)
+ * const [filter, setFilter] = useUrlFilterValue({
+ * keyword: true,
+ * city: { multi: true },
+ * status: (parsed) => parsed ? { name: 'status', value: parsed } : null
+ * });
+ * // URL: ?filterParams[keyword]=测试&filterParams[city]=上海:010,北京:020
+ * // → keyword: { label: '测试', value: '测试' }
+ * // → city: [{ label: '上海', value: '010' }, { label: '北京', value: '020' }]
+ */
+const useUrlFilterValue = (mapping) => {
+ const normalizedMapping = Array.isArray(mapping)
+ ? mapping.reduce((acc, item) => {
+ const key = typeof item === 'string' ? item : item.name;
+ acc[key] = typeof item === 'string' ? true : item;
+ return acc;
+ }, {})
+ : mapping;
+
+ return useUrlFilter({
+ readUrlParams: (searchParams) => {
+ const {takeFilterEntry, getConsumedKeys} = createUrlFilterReader(searchParams);
+ const data = {};
+ Object.entries(normalizedMapping).forEach(([key, config]) => {
+ const multi = typeof config === 'object' && config !== null && config.multi;
+ const val = takeFilterEntry(key, {multi});
+ if (val !== null) data[key] = val;
+ });
+ return {consumedKeys: getConsumedKeys(), ...data};
+ },
+ buildFilter: (data) => {
+ const {consumedKeys, ...values} = data;
+ const filters = [];
+ Object.entries(normalizedMapping).forEach(([key, config]) => {
+ if (values[key] === undefined) return;
+ const parsedValue = values[key];
+ if (typeof config === 'function') {
+ const result = config(parsedValue);
+ if (result) filters.push(result);
+ } else {
+ const label = typeof config === 'object' && config !== null ? config.label : key;
+ filters.push({name: key, label, value: parsedValue});
+ }
+ });
+ return filters;
+ }
+ });
+};
+
+export default useUrlFilterValue;
diff --git a/src/components/Table/useSelectedRow.js b/src/components/Table/useSelectedRow.js
index a89b72e..bf97dc5 100644
--- a/src/components/Table/useSelectedRow.js
+++ b/src/components/Table/useSelectedRow.js
@@ -39,7 +39,9 @@ const useSelectedRow = (options) => {
return newValue;
});
}
- }, setSelectedRows
+ }, setSelectedRows, clearSelectedRows: () => {
+ setSelectedRows([]);
+ }
};
};