ant官网示例
import { Select } from 'antd';
const { Option } = Select;
function handleChange(value) {
console.log(value);
}
//单选
<Select defaultValue="lucy" style={{ width: 120 }} onChange={handleChange}>
<Option value="jack">Jack</Option>
<Option value="lucy">Lucy</Option>
<Option value="disabled" disabled>Disabled</Option>
<Option value="Yiminghe">yiminghe</Option>
</Select>
//多选
<Select mode="multiple" defaultValue={["a10","b11"]} clearable onChange={(val:string)=>valueChange(val)}>
<Option value="a10" label="a10">a10</Option>
<Option value="b11">b11</Option>
<Option value="c12">c12</Option>
<Option value="d13">d13</Option>
<Option value="e14">e14</Option>
</Select>
单选功能
需求分析
组件可传入的属性有:
和
为了实现预览中的效果,我们需要管理三个状态,先给他们起个名字:列表展开收起 isShowOptions,输入框展示的值 selectedText, 回调给onChange的值 selectedValue。*这个地方可以优化后面mode = multiple时再谈
它的结构分为两部分:展示选中项 & 下拉选项框。
展示选中项点击只需要设置 isShowOptions = true 即可。
下拉选项框中点击某个选项有以下变化:
selectedValue = value
selectedText = label
isShowOptions = false
动手制作
option组件
option组件什么都不需要做,后面再Select组件中会使用props.children获取里面所有属性。
import React from 'react';
interface IOption{
value:any;
label?:any;
children:any;
}
const Option:React.FC<IOption>=(props:IOption)=>{
return (
""
)
}
Select组件
基础功能:点击切换展示值,触发onChange回调
遍历一下children属性,渲染列表项。点击某一个列表项的时候修改一些状态,并触发onchange事件
import React,{useState, useEffect} from 'react';
interface ISelect{
defaultValue?:any;
children:any;
onChange?:any;
}
interface IOption{
value:any;
label?:any;
children:any;
}
const Select:React.FC<ISelect>=(props:ISelect)=>{
const { children, onChange } = props;
const [isShowOption,setShowStatus] = useState(false)
const [selectedValue,setSelectedValue] = useState(null)
const [selectedText,setselectedText] = useState(null)
return (
<div className="select">
<div className="selectedText">
展示值:{selectedText}
</div>
<div className="selectOptions">
{children.map((item: any, index: number)=>{
const optionComponentProps:IOption = item.props
return (
<div key={index} onClick={()=>{
setSelectedValue(optionComponentProps.value || "")
setselectedText(optionComponentProps.label || optionComponentProps.children || '')
onChange(optionComponentProps.value)
}}>
{optionComponentProps.label || optionComponentProps.children || ''}
</div>
)
})}
</div>
</div>
)
}
export {Select}
增加功能: 收起展开状态
const OptionList = ()=>{
// 仅当isShowOptions === true且select有子元素时渲染,没有则显示空状态
if(isShowOption){
if(children&&children.length>0){
return (
<div className="selectOptions">
{children.map((item:any,index:number)=>{
const optionComponentProps:IOption = item.props
return (
<div className="optionsClasses" key={index} onClick={
()=>{
setSelectedValue(optionComponentProps.value || "")
setselectedText(optionComponentProps.label || optionComponentProps.children || '')
onChange(optionComponentProps.value)
setShowStatus(false)
}
}>
{optionComponentProps.label || optionComponentProps.children || ''}
</div>
)
})}
</div>
)
}else{
return (
<div className="selectOptions">
<div>no data</div>
</div>
)
}
}
return ""
}
return (
<div className="select">
<div className="selectedText" onClick={()=>setShowStatus(!isShowOption)}>
展示值:{selectedText}
</div>
{OptionList()}
</div>
)
新增功能:在 select 组件外任何地方点击时,一律收起 select 组件。
列表展开时无论再次点击哪里都隐藏列表,元素内点击只需要setShowStatus(false)即可,那节点外的地方呢?
我们引入useRef,给select绑定ref。在点击展示框的时候添加监听事件,判断e.target和ref元素之间的关系确定是否隐藏列表,并在收起列表时取消监听事件
import React,{useState, useEffect, useRef} from 'react';
const selectRef:any = useRef();
const fn = (e:any)=>{
if(selectRef.current !== e.target && !selectRef.current.contains(e.target)){
setShowStatus(false)
}
}
const addListener = ()=>{
document.addEventListener('click', fn, true);
}
const removeListener = ()=>{
document.removeEventListener('click', fn, true);
}
useEffect(()=>{
if(!isShowOption){
removeListener()
}
},[isShowOption])
//输出
<div className="select" ref={selectRef}>
<div className="selectedText" onClick={()=>{
addListener();
setShowStatus(!isShowOption)}}>
展示值:{selectedText}
</div>
{OptionList()}
</div>
下拉列表直接展示每个option的children, 展示区域则需要做一些判断。
增加Option组件的 label属性,如果没有传入label属性,就使用option的children,考虑到option的children不一定是纯文本,可能像是这样《span》123《/span》。所以要再做一层判断,如果是纯文本使用纯文本,如果不是,使用option的value属性。
interface IOption{
value?:any;
label?:any;
children:any;
}
const getInnerText = (child:any)=>{
if(typeof child.props.children==='string'){
return child.props.children
}else{
return child.props.value
}
}
const OptionList = ()=>{
if(isShowOption){
if(children&&children.length>0){
return (
<div className="selectOptions">
{children.map((item:any,index:number)=>{
let label = item.props.label || getInnerText(item)
return (
<div className="optionsClasses" key={index} onClick={
()=>{
onChange(item.props.value)
setSelectedValue(item.props.value||"")
setselectedText(label || "")
setShowStatus(false)
}
}>
{item.props.children}
</div>
)
})}
</div>
)
}else{
return (
<div className="selectOptions">
<div>no data</div>
</div>
)
}
}
return ""
}
基础的功能已经完成了,接下来增加CSS样式,以及完善:初始值,清空,禁用项等功能
完整代码
import React,{useState, useEffect, useRef} from 'react';
import classNames from 'classnames';
import {ThemeContext} from '../../App';
import Icon from '../Icon'
interface ISelect{
defaultValue?:any;
children?:any;
onChange?:any;
}
interface IOption{
children:any;
value?:any;
label?:any;
}
const Option:React.FC<IOption>=(props:IOption)=>{
return (
""
)
}
const Select:React.FC<ISelect> = (props)=>{
const { children, onChange, defaultValue } = props;
const getInnerText = (child:any)=>{
if(typeof child.props.children==='string'){
return child.props.children
}else{
return child.props.value
}
}
const getDefaultText = (dv:any)=>{
if(!children || children.length<1){
return null
}
for(let i=0;i<children.length;i++){
if(children[i].props.value === dv){
return children[i].props.label || getInnerText(children[i])
}
}
}
//usestate
const [isShowOption,setShowStatus] = useState(false)
const [iconRotate,setIconRotate] = useState({ transform:"rotate(0deg)" })
const [selectedValue,setSelectedValue] = useState( defaultValue )
const [selectedText,setselectedText] = useState( getDefaultText(defaultValue) )
//eventlistener
const selectRef:any = useRef();
const fn = (e:any)=>{
if(selectRef.current!==e.target && !selectRef.current.contains(e.target)){
setShowStatus(false)
}
}
const addListener = ()=>{
document.addEventListener('click', fn, true);
}
const removeListener = ()=>{
document.removeEventListener('click', fn, true);
}
//useeffect
useEffect(()=>{
if(!isShowOption){
setIconRotate({transform:"rotate(0deg)"})
removeListener()
}else{
setIconRotate({transform:"rotate(-180deg)"})
}
},[isShowOption])
//列表
const OptionList = ()=>{
if(isShowOption){
if(children && children.length>0){
return (
<div className="selectOptions">
{children.map((item:any)=>{
const optionsClasses = classNames("optionItem",{"optionItem-selected":selectedValue===item.props.value})
let label = item.props.label || getInnerText(item)
return (
<div className={optionsClasses} key={item.props.value} onClick={
()=>{
onChange(item.props.value)
setSelectedValue(item.props.value||"")
setselectedText(label || "")
setShowStatus(false)
}
}>
<div style={{flex:"auto"}}>
{item.props.children}
</div>
<div>
<Icon style={{color:"#1890ff"}} icon="icon-select"/>
</div>
</div>
)
})}
</div>
)
}else{
return (
<div className="selectOptions">
<div>no data</div>
</div>
)
}
}
return ""
}
//展示
const displayArea = ()=>{
const selectedClass=classNames("selectedText",{"selectedText-focus":isShowOption})
return (
<div className={selectedClass} onClick={()=>{
addListener();
setShowStatus(!isShowOption)}}>
<div style={{paddingRight:"12px"}}>
{selectedText}
</div>
<span className="suffixIcon" style={iconRotate}>
<Icon style={{fontSize:"16px"}} icon="icon-arrow-down-bold"></Icon>
</span>
</div>
)
}
return (
<div className="select" ref={selectRef}>
{displayArea()}
{OptionList()}
</div>
)
}
export {Select,Option}
.select{
position: relative;
width:200px;
height: 32px;
line-height: 32px;
}
.suffixIcon{
display: inline-block;
font-size:16px;
position: absolute;
height: 100%;
right: 10px;
top: 0;
text-align: center;
color: #c0c4cc;
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
transition: all .3s cubic-bezier(.645,.045,.355,1);
transform-origin: center;
cursor:pointer;
}
.selectedText{
position: relative;
height: 100%;
line-height: inherit;
padding: 0 8px;
width:inherit;
border: 1px solid #d9d9d9;
box-sizing: content-box;
border-radius: 2px;
background-color:#fff;
display: inline-block;
cursor: pointer;
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
transition: all .3s cubic-bezier(.645,.045,.355,1);
.multipleBox{
padding-right:12px;
display: flex;
align-items: center;
height: inherit;
.multipleItem{
background: #f5f5f5;
border: 1px solid #f0f0f0;
border-radius: 2px;
margin-right:8px;
display:inline-block;
height: calc(100% - 4px);
display: flex;
align-items: center;
padding:0 4px 0 8px;
//line-height: 28px;
.item-label{
color:rgba(0,0,0,.85);
margin-right: 4px;
}
.item-icon{
font-size: 12px;
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
transition: all .3s cubic-bezier(.645,.045,.355,1);
color:rgba(0,0,0,.45);
}
.item-icon:hover{
color:rgba(0,0,0,.85);
}
}
}
}
.selectedText:hover{
border-color: #40a9ff;
}
.selectedText-focus{
border-color: #40a9ff;
border-right-width: 1px!important;
outline: 0;
-webkit-box-shadow: 0 0 0 2px rgba(24,144,255,.2);
box-shadow: 0 0 0 2px rgba(24,144,255,.2)
}
.selectOptions{
position: absolute;
z-index:9;
top:45px;
left:0;
//overflow-y: scroll;
width:inherit;
max-height: 274px;
border: 1px solid #e4e7ed;
border-radius: 4px;
background-color: #fff;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
box-sizing: content-box;
margin: 5px 0;
height:auto;
.optionItem{
display: flex;
cursor: pointer;
font-size: 14px;
padding:0 20px;
height:34px;
line-height:34px;
color:black;
}
.optionItem-selected{
font-weight: bold;
}
.optionItem:hover{
background-color: #f5f7fa;
}
.optionItem-selected, .optionItem-selected:hover{
background-color:#e6f7ff
}
}
多选功能
需求分析
多选功能 select传入mode=”multiple” 列出与单选的区别点:
(1)defaultValue是数组的形式[‘a10’, ‘c12’];
(2)onchange返回值也是数组形式value=[‘a10’, ‘c12’];
(3)输入区域 每点击一个项都会展示该项的小标签,可删除
(4)展示列表
1。只有点击列表外区域才会收起,点击列表内不收起。
2。点击列表内某个项目时更改背景色,屁股后面加个钩。
3。输入框清除标签时,也要清除列表项的样式和钩
思路分析
输出值和展示值都为数组的情况下 那么selectedValue和selectedText也一定是一个数组。selectedText:我们点击列表项“a10”的时候,涉及到一次遍历:如果selectedText没有“a10”元素:添加一个元素,如果有:filter过滤掉该元素,selectedValue 也是一样。
每点击一次列表项的时候,我都要遍历两次数组,麻烦,而且代码有点冗余,我需要一个优化的方案,把他们统一放在一起管理。把selectedText和selectedValue的每一项放进一个对象里,这时候再加一个isSelect字段来管理是否被选中,
这样一来,每次点击列表项只需要遍历一次修改isSelect取反即可,是不是一下子方便了很多。
新增option的key属性,如果没有使用option的value属性。一起存放在对象里,遍历的时候设置key,避免重复。
我们看一下数据结构,在开始的时候需要遍历children处理,输出initData。
const data=[
{
key:"1",
isSelect:false,
label:"Jack",
value:"value",
},
{
key:"2",
isSelect:false,
label:"label2",
value:"value2",
},
]
开始动手
展示框区域
const getInnerText = (child:any)=>{
if(typeof child.props.children==='string'){
return child.props.children
}else{
return child.props.value
}
}
const setInitData = ()=>{
if(!children || children.length<1){
return []
}
let initData=[]
for(let i=0;i<children.length;i++){
initData.push({
key:children[i].props.key || children[i].props.value,
//通过defaultValue默认值初始化选中状态
isSelect:defaultValue.indexOf(children[i].props.value) > -1 ? true : false,
label:children[i].props.label || getInnerText(children[i]) || "",
value:children[i].props.value,
})
}
return initData
}
const [selectedInfo,setselectedInfo] = useState<any[]>( setInitData() )
//点击列表项的事件
const multipleClick = (item:any)=>{
setselectedInfo(selectedInfo.map((info:any)=>{
if(info.key===(item.key || item.value)){
return {...info,isSelect:!info.isSelect}
}
return info
}))
}
//展示框区域 只要遍历出isSelect为true的对象渲染即可
//每一个项目是一个小标签,有删除按钮可以删除
{selectedInfo.map((item:any)=>{
if(item.isSelect){
return (
<div className="multipleItem" key={item.key}>
<span className="item-label">
{item.label}
</span>
<span onClick={()=>{
multipleClick(item)
}}>
<Icon className="item-icon" icon="icon-close-bold"/>
</span>
</div>
)
}
return ""
})}
此处有一个问题,点击小标签的删除,会触发外部输入框的下拉事件,我们不想点击删除的时候还顺便展示或者收起菜单,这是典型的事件冒泡,只要在小标签的事件上取消冒泡即可event.stopPropagation()
const multipleClick = (item:any,e:any)=>{
e.stopPropagation()
setselectedInfo(selectedInfo.map((info:any)=>{
if(info.key===(item.key || item.value)){
return {...info,isSelect:!info.isSelect}
}
return info
}))
}
//元素上加上参数别忘了
onClick={(e)=>multipleClick(item,e)}
标签长度超出了输入框长度时我们希望他能自动换行,我们使用css实现即可
.autoBreak{
min-height: 32px;
-webkit-box-flex: 1;
-ms-flex: auto;
flex: auto;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
max-width: 100%;
}
由于列表使用绝对定位,当输入框高度发生变化时,列表的top值也要发生变化。新增一个状态管理列表top值,绑定在列表的style属性上。使用useEffect每次selectinfo状态发生变化时,获得输入框的高度,如果变高增加相应的top值。
const [top,setTop] = useState(40) //初始高度40
useEffect(()=>{
let height = document.getElementsByClassName("multipleBox")[0].clientHeight //30
setTop(10+height)
},[selectedInfo])
//绑定在绝对定位的元素上 style={{top:top+'px'}}
//顺便把其top属性删除
下拉列表区域
输入框搞定了,再来看看展示列表吧,之前我们直接循环select.children,很不优雅,现在我们循环selectedInfo即可,isSelect为true增加打钩的icon
{selectedInfo.map((info:any)=>{
const optionsClasses = classNames("optionItem",{"optionItem-selected":info.isSelect})
const iconArea = info.isSelect?(<div><Icon style={{color:"#1890ff"}} icon="icon-select"/></div>):""
return (
<div className={optionsClasses} key={info.key} onClick={()=>multipleClick(info)}>
<div style={{flex:"auto"}}>
{info.label}
</div>
{iconArea}
</div>
)
})}
当子元素太多,展示列表长度会把遮住页面上的信息很不友好,所以我们给他一个最大高度,超出时用滚动条。用 overflow-y:scroll 加上设置 max-height。
onchange事件:
触发select上的onchange事件上的参数,单选返回字符串格式,mode=”multiple” 返回数组形式,默认值也是如此
一开始我想在列表元素点击的事件中触发onchange事件的,后来发现在事件绑定中修改state是异步的。
//错误示例
const multipleClick = (item:any,e:any)=>{
e.stopPropagation()
if(mode==="multiple"){
//此处更新为异步
setselectedInfo(selectedInfo.map((info:ISelectInfo)=>{
if(info.key===(item.key || item.value)){
return {...info,isSelect:!info.isSelect}
}
return info
}))
//把选中元素回调到onChange
//这里并不能获取最新的selectInfo
onChange(selectedInfo.filter((item:any)=>item.isSelect))
}
}
只能根据selectinfo的变化在effect中触发onchange了。
let flag=useRef(true)
useEffect(()=>{
//为了让他第一次进来不调用
if(flag.current){
flag.current=false
return
}
if(mode==="multiple"){
let emitVal:any[]=[]
selectedInfo.map((item:any)=>{
if(item.isSelect) emitVal.push(item.value)
})
onChange(emitVal)
}
},[selectedInfo])
在页面中使用多个select组件对应会生成多个className为”multipleBox”,document.getElementsByClassName(“multipleBox”)[0]会有错误
在”multipleBox”上再加个id根据isshowoption判断。true的话id为calcHeight,否则为空,再使用document.getElementById(“calcHeight”)
let doc:HTMLElement | null = document.getElementById("calcHeight")
let height:number = doc?doc.clientHeight:32
<div className="multipleBox" id={isShowOption?"calcHeight":""} >
完整代码
兼容单选和多选
import React,{useState, useEffect, useRef} from 'react';
import classNames from 'classnames';
import Icon from '../Icon'
interface ISelect{
mode?:string;
defaultValue?:any;
clearable?:boolean;
children:any;
onChange?:any;
}
interface IOption{
children:any;
value?:any;
label?:any;
}
interface ISelectInfo{
key:string;
isSelect:boolean;
value:any;
label:any;
}
//util工具方法
const getInnerText = (child:any)=>{
if(typeof child.props.children==='string'){
return child.props.children
}else{
return child.props.value
}
}
const isSelect = (dv:any,cv:any)=>{
if(typeof dv==="string"){
return dv===cv
}else if(typeof dv==="object"){
return dv.indexOf(cv)>-1
}
return false
}
const Option:React.FC<IOption>=(props:IOption)=>{
return (
<div>
<p>{props.children}</p>
</div>
)
}
const Select:React.FC<ISelect> = (props)=>{
const { children, onChange, clearable, defaultValue=[], mode } = props;
// init
const setInitData = ()=>{
if(!children || children.length<1){
return []
}
let initData:ISelectInfo[]=[]
for(let i=0;i<children.length;i++){
initData.push({
key:children[i].props.key || children[i].props.value,
isSelect:isSelect(defaultValue,children[i].props.value),
label:children[i].props.label || getInnerText(children[i]) || "",
value:children[i].props.value,
})
}
return initData
}
//usestate
const [isShowOption,setShowStatus] = useState(false)
const [iconRotate,setIconRotate] = useState({ transform:"rotate(0deg)" })
const [top,setTop] = useState(40)
const [selectedInfo,setselectedInfo] = useState<ISelectInfo[]>( setInitData() )
//点击事件监听
const selectRef:any = useRef();
const fn = (e:any)=>{
if(selectRef.current!==e.target && !selectRef.current.contains(e.target)){
setShowStatus(false)
}
}
const addListener = ()=>{
document.addEventListener('click', fn, true);
}
const removeListener = ()=>{
document.removeEventListener('click', fn, true);
}
//useeffect
useEffect(()=>{
if(!isShowOption){
setIconRotate({transform:"rotate(0deg)"})
removeListener()
}else{
setIconRotate({transform:"rotate(-180deg)"})
}
},[isShowOption])
let flag=useRef(true)
useEffect(()=>{
let doc:HTMLElement | null = document.getElementById("calcHeight")
let height:number = doc?doc.clientHeight:32
setTop(10+height)
if(flag.current){
flag.current=false
return
}
if(mode==="multiple"){
let emitVal:any[]=[]
selectedInfo.map((item:ISelectInfo)=>{
if(item.isSelect) emitVal.push(item.value)
})
onChange(emitVal)
}else{
selectedInfo.map((item:ISelectInfo)=>{
if(item.isSelect) onChange(item.value)
})
setShowStatus(false)
}
},[selectedInfo])
//onclick事件: 清空,下拉,选择
const handleClear = (e:any)=>{
e.stopPropagation();
setselectedInfo(selectedInfo.map((info:ISelectInfo)=>{
return {...info,isSelect:false}
}))
}
const dropDown = ()=>{
addListener();
setShowStatus(!isShowOption)
}
const multipleClick = (item:any,e:any)=>{
e.stopPropagation()
if(mode==="multiple"){
//此处更新为异步
setselectedInfo(selectedInfo.map((info:ISelectInfo)=>{
if(info.key===(item.key || item.value)){
return {...info,isSelect:!info.isSelect}
}
return info
}))
}else{
setselectedInfo(selectedInfo.map((info:ISelectInfo)=>{
if(info.key===(item.key || item.value)){
return {...info,isSelect:true}
}
return {...info,isSelect:false}
}))
}
}
//展示框
const displayArea = ()=>{
const selectedClass=classNames("selectedText",{"selectedText-focus":isShowOption})
const dropDownIcon = mode!=="multiple"
?(<Icon className="suffixIcon" style={iconRotate} icon="icon-arrow-down-bold"/>)
:"";
const clearIcon = clearable && selectedInfo.some((item:ISelectInfo)=>item.isSelect)
?(<span className="clearIcon" onClick={(e)=>handleClear(e)}>
<Icon style={{fontSize:"14px"}} icon="icon-close-bold"/>
</span>)
:"";
const displayInline =(item:any)=>{
if(item.isSelect){
return mode==="multiple"
?(
<div className="multipleItem" key={item.key}>
<span className="item-label">
{item.label}
</span>
<span onClick={(e)=>multipleClick(item,e)}>
<Icon className="item-icon" icon="icon-close-bold"/>
</span>
</div>
)
:(
<div style={{paddingRight:"12px"}} key={item.key}>
{item.label}
</div>
)
}
return ""
}
return (
<div className={selectedClass} onClick={()=>dropDown()}>
<div className="multipleBox" id={isShowOption?"calcHeight":""}>
{selectedInfo.map((item:ISelectInfo)=>{
return displayInline(item)
})}
</div>
{dropDownIcon}
{clearIcon}
</div>
)
}
//列表
const OptionList = ()=>{
if(isShowOption){
if(children && children.length>0){
const row = (info:any)=>{
const optionsClasses = classNames("optionItem",{"optionItem-selected":info.isSelect})
const iconArea = info.isSelect?(<div><Icon style={{color:"#1890ff"}} icon="icon-select"/></div>):""
return mode==="multiple"
?(<div className={optionsClasses} key={info.key} onClick={(e)=>multipleClick(info,e)}>
<div style={{flex:"auto",userSelect: "none"}}>
{info.label}
</div>
{iconArea}
</div>)
:(<div className={optionsClasses} key={info.key} onClick={(e)=>multipleClick(info,e)}>
{info.label}
</div>)
}
return (
<div className="selectOptions" style={{top:top+'px'}}>
{selectedInfo.map((info:ISelectInfo)=>{
{return row(info)}
})}
</div>
)
}else{
return (
<div className="selectOptions">
<div>no data</div>
</div>
)
}
}
return ""
}
return (
<div className="select" ref={selectRef}>
{displayArea()}
{OptionList()}
</div>
)
}
export {Select,Option}
样式代码
.select{
position: relative;
width:220px;
min-height: 32px;
}
.suffixIcon{
display: inline-block;
font-size:14px;
line-height: 32px;
position: absolute;
z-index: 2020;
height: 100%;
right: 9px;
top: 0;
text-align: center;
color: #c0c4cc;
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
transition: all .3s cubic-bezier(.645,.045,.355,1);
cursor:pointer;
}
.selectedText{
position: relative;
height: 100%;
line-height: inherit;
padding: 0 8px;
width:inherit;
border: 1px solid #d9d9d9;
box-sizing: content-box;
border-radius: 2px;
background-color:#fff;
cursor: pointer;
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
transition: all .3s cubic-bezier(.645,.045,.355,1);
.multipleBox{
padding-right:12px;
display: flex;
align-items: center;
height: inherit;
/********* 高度自动撑开 **************/
min-height: 32px;
-webkit-box-flex: 1;
-ms-flex: auto;
flex: auto;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
max-width: 100%;
.multipleItem{
background: #f5f5f5;
border: 1px solid #f0f0f0;
border-radius: 2px;
margin:2px 8px 2px 0;
display:inline-block;
display: flex;
align-items: center;
padding:0 4px 0 8px;
//line-height: 28px;
.item-label{
color:rgba(0,0,0,.85);
margin-right: 4px;
user-select: none;
}
.item-icon{
font-size: 12px;
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
transition: all .3s cubic-bezier(.645,.045,.355,1);
color:rgba(0,0,0,.45);
}
.item-icon:hover{
color:rgba(0,0,0,.85);
}
}
}
.clearIcon{
position: absolute;
top: 50%;
right: 11px;
z-index: 2021;
display: inline-block;
width: 12px;
height: 12px;
margin-top: -6px;
color: rgba(0,0,0,.25);
font-size: 12px;
font-style: normal;
line-height: 1;
text-align: center;
text-transform: none;
background: #fff;
cursor: pointer;
opacity: 0;
-webkit-transition: color .3s ease,opacity .15s ease;
transition: color .3s ease,opacity .15s ease;
text-rendering: auto;
&:hover{
color: rgba(0,0,0,.65);
}
}
}
.selectedText:hover{
border-color: #40a9ff;
}
.selectedText:hover .clearIcon{
opacity: 1;
}
.selectedText-focus{
border-color: #40a9ff;
border-right-width: 1px!important;
outline: 0;
-webkit-box-shadow: 0 0 0 2px rgba(24,144,255,.2);
box-shadow: 0 0 0 2px rgba(24,144,255,.2)
}
.selectOptions{
position: absolute;
z-index:9;
//top:45px; 通过js设置
left:0;
overflow-y: scroll;
width:inherit;
max-height: 274px;
border: 1px solid #e4e7ed;
border-radius: 4px;
background-color: #fff;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
box-sizing: content-box;
margin: 5px 0;
height:auto;
.optionItem{
display: flex;
cursor: pointer;
font-size: 14px;
padding:0 20px;
height:34px;
line-height:34px;
color:black;
user-select: none;
}
.optionItem-selected{
font-weight: bold;
}
.optionItem:hover{
background-color: #f5f7fa;
}
.optionItem-selected, .optionItem-selected:hover{
background-color:#e6f7ff
}
}