前言
项目中要实现类似与vant的DropdownMenu:下拉菜单。看了vans 的效果 其实也没什么难度,于是动手鲁了一个这样的组件。
项目的技术栈为react全家桶+material UI + ant Design mobile。
vans的效果
我自己实现的效果
思路
常规做法获取dom元素,动态修改选中dom的innerHtml。
当然这种方式不是react推荐的
我的做法既然react不推荐直接操作dom元素,那可以采用动态动态修改class的方式达到效果,例如:
- let cls ="normal"
- div未被选中时
- <div className={cls}/>
- div被选中时
- cls+=" current"
- <div className ={cls}>
实现步骤
顶部tab采用三个div的方式布局,由于需要动态修改tab上的标题,所以定义一个数组,reducer中的tab数据结构如下
- let tabs = {};
- tabs[TABKAY.AREA] = {
- key: TABKAY.AREA,text: "全部区域",obj: {}
- };
- tabs[TABKAY.SORT] = {
- key: TABKAY.SORT,text: "综合排序",obj: {}
- };
- tabs[TABKAY.FILTER] = {
- key: TABKAY.FILTER,text: "筛选",obj: SX
- };
- const initialState = {
- areas: [{ id: "",name: "全部区域" }],tabs: tabs,actionKey: TABKAY.AREA,// 标识了当前选中tab
- closePanel: true // 标识panel div 是否显示
- };
- renderTabs() {
- const { tabs,actionKey,closePanel } = this.props;
- //---------
- if (!closePanel) {
- fixedBody();
- } else {
- looseBody();
- }
- //---------
- let aray = [];
- for (let key in tabs) {
- let item = tabs[key];
- let cls = item.key + " item";
- if (item.key === actionKey && !closePanel) {
- cls += " current";
- }
- aray.push(
- <div className={cls} key={item.key} onClick={() => this.onChangeTab(item.key)}>
- {item.text}
- </div>);
- }
- return aray;
- }
样式:这里边有个技巧,就是利用了css元素选择器的伪类的方式巧妙实现了箭头以及箭头的旋转动画
- .item {
- flex: 1;
- font-size: 15px;
- border-right: 0.5px solid #eaeaea;
- text-align: center;
- &:last-child {
- border: none;
- }
- &.AREA:after,&.SORT:after,&.FILTER:after {
- content: "";
- display: inline-block;
- width: 5px;
- height: 5px;
- margin-bottom: 2px;
- margin-left: 6px;
- border: 2px solid #666;
- border-width: 0 2px 2px 0;
- transform: rotate(45deg);
- -webkit-transform: rotate(45deg);
- -webkit-transition: .3s;
- transition: .3s;
- }
- &.current {
- color: #0084ff;
- }
- &.current:after {
- border-color: #0084ff;
- transform: rotate(225deg);
- -webkit-transform: rotate(225deg);
- }
chrome 查看元素
全部区域tab被选中:
综合tab被选中
每次点击不同的tab时 都会自动的渲染current这个css样式,这样就实现了下拉菜单的功能。
完整代码
- /**
- * Class:
- * Author: miyaliunian
- * Date: 2019/5/26
- * Description: tabs 选择器
- * 医院列表
- */
- import React,{ Component } from "react";
- import { ZHPX,TABKAY } from "@api/Constant";
- //Util
- import { fixedBody,looseBody } from "@utils/fixRollingPenetration";
- //Redux
- import { connect } from "react-redux";
- import { bindActionCreators } from "redux";
- import { actions as tabActions,getTabs,getAreasList,getActionKey,getClosePanel } from "@reduxs/modules/tabs";
- import { actions as hospitalActions,} from "@reduxs/modules/hospital";
- //样式
- import "./tabs.less";
- class Tabs extends Component {
- /**
- * 变化当前点击的item状态 同时filter 请求
- * @param filterItem 当前选中的元素
- * @param key 哪个tab是选中状态
- */
- changeDoFilter(filterItem,key,event) {
- const { tabActions: { changeFilter },hospitalActions:{filterHosiContentList} } = this.props;
- event.stopPropagation();
- changeFilter(filterItem,(filter) => {
- filterHosiContentList(filter);
- });
- }
- /**
- * 筛选tab确定按钮
- * @param event
- */
- filterPanel(event) {
- const {tabActions:{closePanelAction},tabs,hospitalActions:{filterHosiContentList}} = this.props;
- event.stopPropagation();
- closePanelAction(()=>{
- filterHosiContentList(tabs)
- })
- }
- /**
- * 点击切换Tab
- * @param key
- */
- onChangeTab(key) {
- const { actionKey,tabActions: { changeTab } } = this.props;
- let closePanel = false;
- //如果前后点击的是同一个tab 就关闭panel
- if (actionKey === key && !this.props.closePanel) {
- closePanel = true;
- }
- closePanel ? looseBody() : fixedBody();
- changeTab(key,closePanel);
- }
- /**
- * 渲染顶部tab
- */
- renderTabs() {
- const { tabs,closePanel } = this.props;
- //---------
- if (!closePanel) {
- fixedBody();
- } else {
- looseBody();
- }
- //---------
- let aray = [];
- for (let key in tabs) {
- let item = tabs[key];
- let cls = item.key + " item";
- if (item.key === actionKey && !closePanel) {
- cls += " current";
- }
- aray.push(
- <div className={cls} key={item.key} onClick={() => this.onChangeTab(item.key)}>
- {item.text}
- </div>);
- }
- return aray;
- }
- /**
- * 全部区域
- * @returns {*}
- */
- renderAreaContent() {
- const { areasList } = this.props;
- return areasList.map((item,index) => {
- let cls = item.active + " area-item";
- return (
- <li key={index} className={"area-item"} onClick={(e) => this.changeDoFilter(item,TABKAY.AREA,e)}>
- {item.name}
- </li>
- );
- });
- }
- /**
- * 全部排序
- * @returns {any[]}
- */
- renderSORTContent() {
- let sortList = ZHPX;
- return sortList.map((item,index) => {
- let cls = item.action ? "type-item active" : "type-item";
- return (
- <li key={index} className={cls} onClick={(e) => this.changeDoFilter(item,TABKAY.SORT,e)}>
- {item.name}
- </li>
- );
- });
- }
- /**
- * 筛选
- * @returns {*}
- */
- renderFilterInnerContent(items/*filterList*/) {
- return items.map((item,index) => {
- let cls = "cate-Box";
- if (item.active) {
- cls += " active";
- }
- return (
- <div key={index} className={cls} onClick={(e) => this.changeDoFilter(item,TABKAY.FILTER,e)}>
- {item.name}
- </div>
- );
- });
- }
- renderFILTERContent() {
- let filterList = [];
- const { tabs } = this.props;
- filterList = tabs[TABKAY.FILTER].obj;
- return filterList.map((item,index) => {
- return (
- <li key={index} className={"filter-item"}>
- <p className={"filter-title"}>{index == 0 ? `医院类型: ${item.groupTitle}` : `医院等级: ${item.groupTitle}`}</p>
- <div className={"item-content"}>
- {this.renderFilterInnerContent(item.items,filterList)}
- </div>
- </li>
- );
- });
- }
- /**
- * 渲染过滤面板
- */
- renderContent() {
- const { tabs,actionKey } = this.props;
- let array = [];
- for (let key in tabs) {
- let item = tabs[key];
- let cls = item.key + "-panel";
- if (item.key === actionKey) {
- cls += " current";
- }
- // 全部区域
- if (item.key === TABKAY.AREA) {
- array.push(
- <ul key={item.key} className={cls}>
- {this.renderAreaContent()}
- </ul>
- );
- } else if (item.key === TABKAY.SORT) {
- // 综合排序
- array.push(
- <ul key={item.key} className={cls}>
- {this.renderSORTContent()}
- </ul>
- );
- } else if (item.key === TABKAY.FILTER) {
- // 筛选
- array.push(
- <ul key={item.key} className={cls}>
- {this.renderFILTERContent()}
- <div className={"filterBtn"} onClick={(e) => this.filterPanel(e)}>
- 确定
- </div>
- </ul>
- );
- }
- }
- return array;
- }
- render() {
- const { closePanel,tabActions: { closePanelAction } } = this.props;
- let cls = "panel";
- if (!closePanel) {
- cls += " show";
- } else {
- cls = "panel";
- }
- return (
- <div className={"tab-header"}>
- <div className={"tab-header-top border-bottom"}>
- {this.renderTabs()}
- </div>
- <div className={cls} onClick={() => closePanelAction(()=>{})}>
- <div className={"panel-inner"}>
- {this.renderContent()}
- </div>
- </div>
- </div>
- );
- }
- componentDidMount() {
- const { areasList,tabActions: { loadAreas } } = this.props;
- if (areasList && areasList.length !== 1) {
- return;
- }
- loadAreas();
- }
- }
- const mapStateToProps = state => {
- return {
- areasList: getAreasList(state),tabs: getTabs(state),actionKey: getActionKey(state),closePanel: getClosePanel(state)
- };
- };
- const mapDispatchToProps = dispatch => {
- return {
- tabActions: bindActionCreators(tabActions,dispatch),hospitalActions: bindActionCreators(hospitalActions,dispatch)
- };
- };
- export default connect(mapStateToProps,mapDispatchToProps)(Tabs);