Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pro:search): add 'cascader' searchField #1485

Merged
merged 1 commit into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
122 changes: 122 additions & 0 deletions packages/pro/search/demo/Basic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,128 @@ const searchFields: SearchField[] = [
],
},
},
{
type: 'cascader',
key: 'cascader',
label: 'Cascader',
fieldConfig: {
fullPath: true,
multiple: true,
searchable: true,
dataSource: [
{
key: 'components',
label: 'Components',
children: [
{
key: 'general',
label: 'General',
children: [
{
key: 'button',
label: 'Button',
},
{
key: 'header',
label: 'Header',
},
{
key: 'icon',
label: 'Icon',
},
],
},
{
key: 'layout',
label: 'Layout',
children: [
{
key: 'divider',
label: 'Divider',
},
{
key: 'grid',
label: 'Grid',
},
{
key: 'space',
label: 'Space',
},
],
},
{
key: 'navigation',
label: 'Navigation',
children: [
{
key: 'breadcrumb',
label: 'Breadcrumb',
},
{
key: 'dropdown',
label: 'Dropdown',
},
{
key: 'menu',
label: 'Menu',
},
{
key: 'pagination',
label: 'Pagination',
},
],
},
],
},
{
key: 'pro',
label: 'Pro',
children: [
{
key: 'pro-layout',
label: 'Layout',
},
{
key: 'pro-table',
label: 'Table',
disabled: true,
},
{
key: 'pro-transfer',
label: 'Transfer',
},
],
},
{
key: 'cdk',
label: 'CDK',
disabled: true,
children: [
{
key: 'a11y',
label: 'Accessibility',
},
{
key: 'breakpoint',
label: 'Breakpoint',
},
{
key: 'click-outside',
label: 'ClickOutside',
},
{
key: 'clipboard',
label: 'Clipboard',
},
{
key: 'forms',
label: 'Forms',
},
],
},
],
},
},
{
type: 'datePicker',
label: 'Date',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with a brief code review.

Firstly, it looks like the code is written correctly, and there are no obvious bugs or errors in the code. However, there could be some possible improvements.

Firstly, it might be better to make sure that the dataSource is defined outside of the searchFields array, so that it can be more easily reused in other places.

Also, it would be good to add some validation when entering the data, to make sure all the keys and labels are unique.

Finally, it would be a good idea to add comments to the code, so that it is easier to understand what is going on.

Overall, it looks like the code is written correctly, and all the necessary checks have been made.

Expand Down
29 changes: 29 additions & 0 deletions packages/pro/search/demo/RemoteSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ interface TreeSelectData {
label: string
children?: TreeSelectData[]
}
interface CascaderData {
key: string
label: string
children?: TreeSelectData[]
}

const labels = ['Archer', 'Berserker', 'Lancer', 'Rider', 'Saber', 'Caster', 'Assassin']
const baseSelectData: SelectData[] = Array.from(new Array(50)).map((_, idx) => {
Expand Down Expand Up @@ -66,6 +71,8 @@ const baseTreeSelectData: TreeSelectData[] = Array.from(new Array(20)).map((_, i
],
}
})
const baseCascaderData = baseTreeSelectData as CascaderData[]

const createSelectData = (searchValue: string) => {
return baseSelectData.filter(item => new RegExp(searchValue.toLowerCase()).test(item.label.toLowerCase()))
}
Expand All @@ -74,17 +81,26 @@ const createTreeSelectData = (searchValue: string) => {
new RegExp(searchValue.toLowerCase()).test(item.label.toLowerCase()),
)
}
const createCascaderData = (searchValue: string) => {
return filterTree(baseCascaderData, 'children', item =>
new RegExp(searchValue.toLowerCase()).test(item.label.toLowerCase()),
)
}

const value = ref<SearchValue[]>()
const selectData = ref<SelectData[]>(createSelectData(''))
const treeSelectData = ref<TreeSelectData[]>(createTreeSelectData(''))
const cascaderData = ref<CascaderData[]>(createCascaderData(''))

const selectOnSearch = (searchValue: string) => {
selectData.value = createSelectData(searchValue)
}
const treeSelectOnSearch = (searchValue: string) => {
treeSelectData.value = createTreeSelectData(searchValue)
}
const cascaderOnSearch = (searchValue: string) => {
cascaderData.value = createCascaderData(searchValue)
}

const searchFields = computed<SearchField[]>(() => [
{
Expand Down Expand Up @@ -113,6 +129,19 @@ const searchFields = computed<SearchField[]>(() => [
onSearch: treeSelectOnSearch,
},
},
{
type: 'cascader',
label: 'Cascader Data',
key: 'cascader_data',
fieldConfig: {
multiple: true,
searchable: true,
cascaderStrategy: 'all',
dataSource: cascaderData.value,
searchFn: () => true,
onSearch: cascaderOnSearch,
},
},
])

const onChange = (value: SearchValue[] | undefined, oldValue: SearchValue[] | undefined) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with a brief overview of the code patch. The patch adds an additional interface called CascaderData, and adds a new search field with cascader data as its data source. The code also contains functions to create data for select, tree select, and cascader fields, as well as an onChange function.

Now for the review, I'd suggest ensuring that the new interface is properly type-checked, since it's being used in multiple places. Additionally, it might be worth checking if the functions to create the data are optimized for performance. Finally, the onChange function should be thoroughly tested, as it's the main logic of the code.

Expand Down
54 changes: 41 additions & 13 deletions packages/pro/search/docs/Api.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,22 +127,21 @@ TreeSelectSearchFieldConfig
| `draggable` | 是否可拖拽 | `boolean` | - | - | 详情参考[Tree](/components/tree/zh) |
| `draggableIcon` | 拖拽图标 | `string` | - | - | 详情参考[Tree](/components/tree/zh) |
| `showLine` | 是否展示连线 | `boolean` | - | - | 详情参考[Tree](/components/tree/zh) |
| `searchable` | 是否支持筛选 | `boolean` | false | - | 默认不支持 |
| `searchable` | 是否支持筛选 | `boolean` | `false` | - | 默认不支持 |
| `searchFn` | 搜索函数 | `(node: TreeSelectPanelData, searchValue?: string) => boolean` | - | - | 默认模糊匹配 |
| `separator` | 多选分隔符 | `string` | `'|'` | - | - |
| `virtual` | 是否支持虚拟滚动 | `boolean` | `false` | - | 默认不支持 |

| `onCheck` | 勾选回调函数 | `(checked: boolean, node: TreeSelectPanelData) => void | ((checked: boolean, node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDragstart` | `dragstart` 触发时调用 | `(options: TreeDragDropOptions<any>) => void | ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDragend` | `dragend` 触发时调用 | `(options: TreeDragDropOptions<any>) => void | ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDragenter` | `dragenter` 触发时调用 | `(options: TreeDragDropOptions<any>) => void | ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDragleave` | `dragleave` 触发时调用 | `(options: TreeDragDropOptions<any>) => void | ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDragover` | `dragover` 触发时调用 | `(options: TreeDragDropOptions<any>) => void | ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDrop` | `drop` 触发时调用 | `(options: TreeDragDropOptions<any>) => void | ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onExpand` | 点击展开图标时触发 | `(expanded: boolean, node: TreeSelectPanelData) => void | ((expanded: boolean, node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onSelect` | 选中状态发生变化时触发 | `(selected: boolean, node: TreeSelectPanelData) => void | ((selected: boolean, node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onSearch` | 搜索回调函数 | `(searchValue: string) => void | ((searchValue: string) => void)[]` | - | - | 在触发搜索值改变时执行 |
| `onLoaded` | 子节点加载完毕时触发 | `(loadedKeys: any[], node: TreeSelectPanelData) => void | ((loadedKeys: any[], node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onCheck` | 勾选回调函数 | `((checked: boolean, node: TreeSelectPanelData) => void) \| ((checked: boolean, node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDragstart` | `dragstart` 触发时调用 | `((options: TreeDragDropOptions<any>) => void) \| ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDragend` | `dragend` 触发时调用 | `((options: TreeDragDropOptions<any>) => void) \| ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDragenter` | `dragenter` 触发时调用 | `((options: TreeDragDropOptions<any>) => void \| ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDragleave` | `dragleave` 触发时调用 | `((options: TreeDragDropOptions<any>) => void) \| ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDragover` | `dragover` 触发时调用 | `((options: TreeDragDropOptions<any>) => void) \| ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onDrop` | `drop` 触发时调用 | `((options: TreeDragDropOptions<any>) => void) \| ((options: TreeDragDropOptions<any>) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onExpand` | 点击展开图标时触发 | `((expanded: boolean, node: TreeSelectPanelData) => void) \| ((expanded: boolean, node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onSelect` | 选中状态发生变化时触发 | `((selected: boolean, node: TreeSelectPanelData) => void) \| ((selected: boolean, node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |
| `onSearch` | 搜索回调函数 | `((searchValue: string) => void) \| ((searchValue: string) => void)[]` | - | - | 在触发搜索值改变时执行 |
| `onLoaded` | 子节点加载完毕时触发 | `((loadedKeys: any[], node: TreeSelectPanelData) => void) \| ((loadedKeys: any[], node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) |

```typescript
type TreeSelectPanelData = TreeSelectNode &
Expand All @@ -151,6 +150,35 @@ type TreeSelectPanelData = TreeSelectNode &
}
```

#### CascaderSearchField

级联选择类型

| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 |
| --- | --- | --- | --- | --- | --- |
| `type` | 类型 | `'cascader'` | - | - | 固定为 `'cascader'` |
| `fieldConfig` | 配置 | `'CascaderSearchFieldConfig'` | - | - | - |

CascaderSearchFieldConfig

| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 |
| --- | --- | --- | --- | --- | --- |
| `dataSource` | 类型 | `CascaderPanelData[]` | - | - | 继承自`CascaderData`,但`key`和`label`为必填,不支持可配,且`childrenKey`固定为`'children'`,详情参考[Cascader](/components/cascader/zh) |
| `cascaderStrategy` | 级联策略 | `CascaderStrategy` | `''` | - | 详情参考[Cascader](/components/cascader/zh) |
| `multiple` | 是否为多选 | `boolean` | - | - | 默认为单选 |
| `disableData` | 动态禁用某些项 | `(data: CascaderPanelData) => boolean` | - | - | 详情参考[Cascader](/components/cascader/zh) |
| `expandIcon` | 展开图标 | `string` | - | - | 详情参考[Cascader](/components/cascader/zh) |
| `expandTrigger` | 触发展开的方式 | ``'click' \| 'hover'` | - | - | 详情参考[Cascader](/components/cascader/zh) |
| `fullPath` | 选中后的值是否包含全部路径 | `boolean` | - | `false` | 详情参考[Cascader](/components/cascader/zh) |
| `pathSeparator` | 设置分割符 | `string` | - | - | 详情参考[Cascader](/components/cascader/zh) |
| `searchable` | 是否支持筛选 | `boolean` | false | - | 默认不支持 |
| `searchFn` | 搜索函数 | `(node: TreeSelectPanelData, searchValue?: string) => boolean` | - | - | 默认模糊匹配 |
| `separator` | 多选分隔符 | `string` | `'|'` | - | -
| `virtual` | 是否支持虚拟滚动 | `boolean` | `false` | - | 默认不支持 |
| `onExpand` | 点击展开图标时触发 | `((expanded: boolean, data: CascaderPanelData) => void) \| ((expanded: boolean, data: CascaderPanelData) => void>)[]` | - | - | 详情参考[Cascader](/components/cascader/zh) |
| `onSearch` | 开启搜索功能后,输入后的回调 | `((searchValue: string) => void) \| ((searchValue: string) => void)[]` | - | - | 详情参考[Cascader](/components/cascader/zh) |
| `onLoaded` | 子节点加载完毕时触发 | `((loadedKeys: any[], node: TreeSelectPanelData) => void) \| ((loadedKeys: any[], node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Cascader](/components/cascader/zh) |

#### DatePickerSearchField

日期选择类型
Expand Down
3 changes: 3 additions & 0 deletions packages/pro/search/src/composables/useSearchItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { DateConfig } from '@idux/components/config'

import { type ComputedRef, type Slots, computed } from 'vue'

import { createCascaderSegment } from '../segments/CreateCascaderSegment'
import { createDatePickerSegment } from '../segments/CreateDatePickerSegment'
import { createDateRangePickerSegment } from '../segments/CreateDateRangePickerSegment'
import { createNameSegment } from '../segments/CreateNameSegment'
Expand Down Expand Up @@ -73,6 +74,8 @@ function createSearchItemContentSegment(
return createSelectSegment(prefixCls, searchField)
case 'treeSelect':
return createTreeSelectSegment(prefixCls, searchField)
case 'cascader':
return createCascaderSegment(prefixCls, searchField)
case 'input':
return createInputSegment(prefixCls, searchField)
case 'datePicker':
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the code review

  1. The code patch looks correct and there are no major risks of bugs.
  2. It is good practice to include comments in the code to explain its purpose. This not only helps in understanding the code but also makes it easier to debug any issues.
  3. It is also recommended that you add additional tests to ensure that the changes have not introduced any new issues into the code.
  4. You should also consider refactoring the code to make it more readable and maintainable. This could include changing variable names and using consistent coding conventions.

Expand Down
110 changes: 110 additions & 0 deletions packages/pro/search/src/panel/CascaderPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import { defineComponent, inject, onUnmounted, watch } from 'vue'

import { type VKey, callEmit } from '@idux/cdk/utils'
import { CascaderPanelProps, IxCascaderPanel } from '@idux/components/cascader'

import PanelFooter from './PanelFooter'
import { proSearchContext } from '../token'
import { proSearchCascaderPanelProps } from '../types'

export default defineComponent({
props: proSearchCascaderPanelProps,
setup(props) {
const { mergedPrefixCls, locale } = inject(proSearchContext)!

watch(
() => props.searchValue,
searchValue => {
callEmit(props.onSearch, searchValue ?? '')
},
)
onUnmounted(() => {
if (props.searchValue) {
callEmit(props.onSearch, '')
}
})

const changeSelected = (keys: VKey[] | VKey[] | VKey[][]) => {
callEmit(props.onChange, props.multiple ? keys : [keys])
}

const handleConfirm = () => {
callEmit(props.onConfirm)
}
const handleCancel = () => {
callEmit(props.onCancel)
}

const renderFooter = () => {
if (!props.multiple) {
return
}

return (
<PanelFooter
prefixCls={mergedPrefixCls.value}
locale={locale}
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
)
}

return () => {
const prefixCls = `${mergedPrefixCls.value}-cascader-panel`
const {
dataSource,
disableData,
expandIcon,
expandTrigger,
fullPath,
loadChildren,
multiple,

searchValue,
searchFn,
separator,
strategy,
virtual,

onExpand,
onLoaded,
} = props
const panelProps = {
selectedKeys: props.value,
dataSource,
disableData,
childrenKey: 'children',
getKey: 'key',
expandIcon,
expandTrigger,
fullPath,
loadChildren,
labelKey: 'label',
multiple,
searchFn,
searchValue,
separator,
strategy,
virtual,
onExpand,
onLoaded,
'onUpdate:selectedKeys': changeSelected,
} as CascaderPanelProps

return (
<div class={prefixCls} tabindex={-1} onMousedown={evt => evt.preventDefault()}>
<IxCascaderPanel {...panelProps} />
{renderFooter()}
</div>
)
}
},
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code review

First of all, the code looks well formatted and organized. There is no risk of any bugs here. However, there are some potential improvements that can be made.

  1. The use of constants for props such as mergedPrefixCls, locale, etc. should be used instead of regularly referencing them in the code. This will make the code easier to read and maintain.

  2. The use of variables such as prefixCls and panelProps can be further simplified by using destructuring in the props parameter of the setup function.

  3. The renderFooter() function has a lot of duplication with other functions. It should be refactored to use a more modular approach.

  4. The use of arrow functions could be used to simplify certain parts of the code.

Overall, the code looks well written and bug-free.