提交 627d3609 编写于 作者: A ailululu

feat: 级联组件

上级 778d36ff
......@@ -392,6 +392,16 @@
"sort": 1,
"show": true,
"author": "swag~jun"
},
{
"version": "0.1.0",
"name": "Cascader",
"type": "component",
"cName": "级联选择器",
"desc": "级联选择,用于多层级数据的选择,典型场景为省市区选择。",
"sort": 1,
"show": true,
"author": "ailululu"
}
]
},
......
@import '../popup/popup.scss';
.nut-cascader {
width: 100%;
font-size: 14px;
line-height: 22px;
.nut-popup {
padding: 0;
&.popup-bottom.round {
padding: 0 !important;
}
}
&__title {
display: flex;
justify-content: center;
align-items: center;
padding: 24px 20px 17px;
text-align: center;
font-weight: bold;
line-height: 20px;
color: #1a1a1a;
font-size: 18px;
}
.nut-tabs__titles {
padding: 0 10px;
background: #fff;
}
.nut-tabs__titles-item {
flex: initial;
min-width: auto;
width: auto;
padding: 0 10px;
white-space: nowrap;
}
.nut-tabpane {
padding: 0;
}
&-pane {
display: block;
padding: 0;
margin: 0;
width: 100%;
padding-top: 10px;
height: 342px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
&-item {
display: flex;
align-items: center;
padding: 10px 20px;
margin: 0;
cursor: pointer;
font-size: 14px;
color: #1a1a1a;
&.active {
&:not(.disabled) {
color: #fa2c19;
}
.nut-cascader-item__icon-check {
visibility: visible;
color: #fa2c19;
}
}
}
&-item__title {
flex: 1;
}
.nut-icon-checklist {
margin-left: 10px;
visibility: hidden;
}
}
import React, { FunctionComponent, useState, useEffect } from 'react'
import Popup from '@/packages/popup'
import { Tabs } from '@/packages/tabs/tabs'
import { TabPane } from '@/packages/tabpane/tabpane'
import { Icon } from '@/packages/icon/icon'
import { CascaderItem } from './cascaderItem'
import classNames from 'classnames'
import bem from '@/utils/bem'
interface OptiosData {
nodes: OptiosInfo[]
selectedNode: OptiosInfo | null
paneKey: string
}
interface OptiosInfo {
text: string
value: string
paneKey: string
disabled?: boolean
loading?: boolean
children: OptiosInfo[]
}
export interface CascaderProps {
visible: boolean
options: OptiosInfo[]
title: string
value: []
onClose?: () => void
onChange: (params: any) => void
}
const defaultProps = {
visible: false,
options: [],
title: '',
value: [],
onClose: () => {},
onChange: (params) => {},
} as CascaderProps
export const Cascader: FunctionComponent<
Partial<CascaderProps> & React.HTMLAttributes<HTMLDivElement>
> = (props) => {
const [tabvalue, setTabvalue] = useState('c1')
const [optiosData, setOptiosData] = useState<OptiosData[]>([])
const [checkedIndexList, setCheckedIndexList] = useState<Array<number>>([])
const [selectValue, setSelectValue] = useState([])
const {
visible,
options,
title,
value, // 选中值
onClose,
onChange,
} = { ...defaultProps, ...props }
const b = bem('cascader')
const classes = classNames(b(''))
const classesPane = classNames({
[`${b('')}-pane`]: true,
})
useEffect(() => {
initData()
}, [])
const initData = () => {
console.log('options', options)
let newOptions: any[] = []
let newOptionsChild: any[] = []
options.forEach((item: any, index: number) => {
console.log('item', item)
item.paneKey = 'c' + (index + 1)
// item.checked = false
newOptionsChild.push(item)
})
newOptions = [
{
nodes: newOptionsChild,
selectedNode: null,
paneKey: 'c1',
},
]
setOptiosData([...newOptions])
console.log('newOptions', newOptions)
console.log('optiosData 2', optiosData)
}
const close = () => {
onClose && onClose()
}
// const choose = (param: string) => {
// close()
// onChoose && onChoose(param)
// }
const closePopup = () => {
close()
}
const chooseItem = (item: any, checked: boolean, index: number, tabIndex: number) => {
console.log('item:', item)
console.log('checked', checked, 'index', index, tabIndex)
let newOptiosData = [...optiosData]
let newCheckedIndexList = [...checkedIndexList]
// 当前状态为true,被选中,滑到下一个
// 当前状态为false,没被选中
newCheckedIndexList[tabIndex] = index + 1
newOptiosData[tabIndex].selectedNode = item
// newOptiosData = newOptiosData.slice(0, level);
if (item.children && item.children.length > 0) {
newOptiosData.push({
nodes: item.children || [],
selectedNode: null,
paneKey: 'c' + (tabIndex + 2),
})
}
// console.log('newOptiosData 3', newOptiosData)
// console.log('tabIndex', tabIndex)
setCheckedIndexList(newCheckedIndexList)
setOptiosData([...newOptiosData])
// 滑到下一个
if (item.children && item.children.length > 0) {
setTabvalue('c' + (tabIndex + 2))
} else {
const pathNodes = optiosData.map((item) => item.selectedNode)
const optionParams = pathNodes.map((item) => item.value)
console.log('optionParams', optionParams)
onChange(optionParams)
close()
}
}
return (
<>
<div className={classes}>
{console.log('dom optiosData 2', optiosData)}
<Popup
visible={visible}
position="bottom"
round
closeable
onClickOverlay={closePopup}
onClickCloseIcon={closePopup}
style={{ padding: '30px 50px' }}
>
<div className="nut-cascader__title">{title}</div>
<Tabs
value={tabvalue}
titleNode={() => {
return optiosData.map((pane) => (
<div
onClick={() => setTabvalue(pane.paneKey)}
className={`nut-tabs__titles-item ${tabvalue == pane.paneKey ? 'active' : ''}`}
key={pane.paneKey}
>
<span className="nut-tabs__titles-item__text">
{pane?.selectedNode?.text ? pane.selectedNode.text : '请选择'}
</span>
<span className="nut-tabs__titles-item__line" />
</div>
))
}}
>
{optiosData.map((pane, tabIndex) => (
<TabPane key={pane.paneKey} paneKey={pane.paneKey}>
<div className={classesPane}>
{pane.nodes &&
pane.nodes.map((item: any, index: number) => (
<CascaderItem
key={index}
{...props}
data={item}
checked={checkedIndexList[tabIndex] == index + 1}
index={index}
tabIndex={tabIndex}
chooseItem={(
item: any,
checked: boolean,
index: number,
tabIndex: number
) => chooseItem(item, checked, index, tabIndex)}
/>
))}
</div>
</TabPane>
))}
</Tabs>
</Popup>
</div>
</>
)
}
Cascader.defaultProps = defaultProps
Cascader.displayName = 'NutCascader'
import React, { FunctionComponent, useState, useEffect } from 'react'
import Popup from '@/packages/popup'
import { Tabs } from '@/packages/tabs/tabs'
import { TabPane } from '@/packages/tabpane/tabpane'
import { Icon } from '@/packages/icon/icon'
import classNames from 'classnames'
import bem from '@/utils/bem'
export interface CascaderItemProps {
data: Object
index: number
tabIndex: number
checked: boolean
options: []
chooseItem: (data: any, checked: boolean, index: number, tabIndex: number) => void
}
const defaultProps = {
data: {},
index: 0,
tabIndex: 0,
checked: false,
options: [],
// chooseItem: () => {},
} as CascaderItemProps
export const CascaderItem: FunctionComponent<
Partial<CascaderItemProps> & React.HTMLAttributes<HTMLDivElement>
> = (props) => {
const [showBasic, setShowBasic] = useState(false)
const [tabvalue, setTabvalue] = useState('c1')
const [optiosData, setOptiosData] = useState([])
const { pane, data, index, tabIndex, checked, options, chooseItem } = {
...defaultProps,
...props,
}
const b = bem('cascader-item')
const classes = classNames(
{
active: checked,
},
b('')
)
const classesTitle = classNames({
[`${b('')}__title`]: true,
})
useEffect(() => {
initData()
}, [])
const initData = () => {
// options.forEach((item, index: number) => {
// item.title = item.text
// item.paneKey = 'c'+ (index + 1)
// optiosData.push(item)
// })
}
return (
<>
<div
className={classes}
onClick={() => {
chooseItem(data, checked, index, tabIndex)
}}
>
<div className={classesTitle}>{data.text}</div>
{
checked ? <Icon className={`${checked ? b('icon-check') : ''}`} name="checklist" /> : ''
// <Icon v-if="node.loading" className="nut-cascader-item__icon-loading" name="loading" />
}
</div>
</>
)
}
// Cascader.defaultProps = defaultProps
CascaderItem.displayName = 'NutCascaderItem'
/*
* @Author: your name
* @Date: 2021-12-23 11:15:50
* @LastEditTime: 2021-12-27 10:16:31
* @LastEditors: Please set LastEditors
* @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
* @FilePath: /nutui-react/src/packages/calendar/demo.tsx
*/
import React, { useState } from 'react'
import { Cascader } from './cascader'
import { Cell } from '@/packages/cell/cell'
import { Input } from '@/packages/input/input'
const CascaderDemo = () => {
const [date, setDate] = useState('')
const [dateWeek, setDateWeek] = useState('')
const [isVisible, setIsVisible] = useState(false)
const [value, setValue] = useState([])
const [options, setOptions] = useState([
{
value: '浙江',
text: '浙江',
children: [
{
value: '杭州',
text: '杭州',
disabled: true,
children: [
{ value: '西湖区', text: '西湖区' },
{ value: '余杭区', text: '余杭区' },
],
},
{
value: '温州',
text: '温州',
children: [
{ value: '鹿城区', text: '鹿城区' },
{ value: '瓯海区', text: '瓯海区' },
],
},
],
},
{
value: '湖南',
text: '湖南',
disabled: true,
children: [
{
value: '长沙',
text: '长沙',
disabled: true,
children: [
{ value: '西湖区', text: '西湖区' },
{ value: '余杭区', text: '余杭区' },
],
},
{
value: '温州',
text: '温州',
children: [
{ value: '鹿城区', text: '鹿城区' },
{ value: '瓯海区', text: '瓯海区' },
],
},
],
},
{
value: '福建',
text: '福建',
children: [
{
value: '福州',
text: '福州',
children: [
{ value: '鼓楼区', text: '鼓楼区' },
{ value: '台江区', text: '台江区' },
],
},
],
},
])
const openSwitch = () => {
setIsVisible(true)
}
const closeSwitch = () => {
setIsVisible(false)
}
const change = (value) => {
// console.log('value1', ...value)
// console.log('value2', value)
setValue(value)
}
return (
<>
<div className="demo">
<h2>基础用法</h2>
<Cell title="选择地址" desc={value ? value : '请选择'} onClick={openSwitch}></Cell>
<Cascader
visible={isVisible}
value={value}
onClose={closeSwitch}
title="地址选择"
options={options}
onChange={change}
/>
</div>
</>
)
}
export default CascaderDemo
# Cascader 级联选择
### 介绍
级联选择器,用于多层级数据的选择,典型场景为省市区选择。
### 安装
```js
import { Cascader, Tabs, TabPane } from '@nutui/nutui-react';
const app = createApp();
app.use(Cascader)
.use(Tabs)
.use(TabPane);
```
### 基础用法
传入`options`列表。
:::demo
```jsx
import React from "react";
import { Collapse,CollapseItem } from '@nutui/nutui-react';
const App = () => {
return (
<>
<Cell
title="选择地址"
desc={value ? value : '请选择'}
onClick={openSwitch}
>
</Cell>
<Cascader
visible={isVisible}
value={value}
onClose={closeSwitch}
title="地址选择"
options={options}
onChange={change}
/>
</>
);
};
export default App;
```
:::
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| ------------- | ----------------------------------------------------- | -------- | ------ |
| v-model | 选中值,双向绑定 | Array | - |
| options | 级联数据 | Array | - |
| lazy | 是否开启动态加载 | Boolean | - |
| lazy-load | 动态加载回调,开启动态加载时生效 | Function | - |
| value-key | 自定义`options`结构中`value`的字段 | String | - |
| text-key | 自定义`options`结构中`text`的字段 | String | - |
| children-key | 自定义`options`结构中`children`的字段 | String | - |
| convert-config | 当options为可转换为树形结构的扁平结构时,配置转换规则 | Object | - |
| title | 标题 | String | '' |
| close-icon-position | 取消按钮位置,继承 Popup 组件 | String | "top-right" |
| close-icon | 自定义关闭按钮,继承 Popup 组件 | String | "close" |
| closeable | 是否显示关闭按钮,继承 Popup 组件 | Boolean | true |
### Events
| 事件名 | 说明 | 回调参数 |
| ---------- | ---------------- | ------------------ |
| change | 选中值改变时触发 | (value, pathNodes) |
| pathChange | 选中项改变时触发 | (pathNodes) |
import { Cascader } from './cascader'
export default Cascader
......@@ -51,11 +51,14 @@ export const Collapse: FunctionComponent<Partial<CollapseProps>> = memo((props)
const colBem = bem('collapse')
useEffect(() => {
const activeArr = handleActiveName()
console.log('activeArr', activeArr)
setDefaultOpenIndex(activeArr)
}, [activeName])
const onToggle = (isOpen: boolean, name: string) => {
let newOpenIndex = [...defaultOpenIndex]
console.log('newOpenIndex', newOpenIndex)
console.log('isOpen', isOpen, name)
if (isOpen) {
// 当前状态为true,则变为false,闭合
const removeIndex = newOpenIndex.findIndex((value) => {
......@@ -71,7 +74,7 @@ export const Collapse: FunctionComponent<Partial<CollapseProps>> = memo((props)
}
}
setDefaultOpenIndex(newOpenIndex)
change && change(!isOpen, name)
// change && change(!isOpen, name)
}
return (
<div className={colBem()}>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册