前言
随着管理的文章数量增多,默认的几个分类满足不了现状了,趁着重构的过程把相关的功能考虑进去
本来想自己从头写过一个,看了下Antd
有内置该类型的控件了,就没必要自己造了
一般自己写,肯定优先考虑数组对象格式[{tagName:'a',value:1}]
;
Antd
提供的是纯数组,[string,string]
,那如何不改变它提供的格式情况下拿到我们想要的!
拓展部分我们需要的东东,有兴趣的瞧瞧,没兴趣的止步..
效果图
需求分析及思路
需求梳理
- 从接口拿到
tags
数组且构建枚举对象,tags
支持删除添加 , - 高亮
tag
,追加删除的情况要考虑进去; - 第一个为默认分类,不允许删除
- 高亮颜色支持传入
- 标签文字过长,则截断,用气泡悬浮来展示完全的文本
- 不允许添加同样的(阻止并给予反馈)
- 默认值初始化并且回馈,把值丢给父
实现
- 用
dva
的effect
维护接口数据的获取 - 子组件除了暴露返回值,不做任何涉及
Dva
这类不纯的东西,一切靠props
丢进去
代码实现
在引用处的父组件构建数据获取,主要构建两个,一个待渲染的数组,一个是枚举(其实就是key-value
映射);
因为要考虑和以前的版本兼容,所有一些固定的key-value
,还有默认值也要考虑进去(请求失败的时候)
DocumentType.js
/* * @Author: CRPER * @LastEditors: CRPER * @Github: https://github.com/crper * @Motto: 折腾是一种乐趣,求知是一种追求。不懂就学,懂则分享。 * @Description: 文档类型维护 */ import React, { PureComponent } from 'react'; import { Tag, Input, Tooltip, Icon, message } from 'antd'; // 对象深比较 import isEqual from 'lodash/isEqual'; export default class DocumentType extends PureComponent { static getDerivedStateFromProps(nextProps, prevState) { if (isEqual(nextProps.data, prevState.prevData)) { return null; } if (nextProps.data) { return { defaultValue: nextProps.defaultValue ? nextProps.defaultValue : null, tags: nextProps.data, prevData: nextProps.data, }; } else { return null; } } state = { tags: [], // 标签列表 hightlightIndeX: 0, // 若是外部没有 inputVisible: false, // 输入框默认隐藏 inputValue: '', // 输入框默认值 }; //获取默认值 initDefaultValue = () => { const { defaultValue, hightlightIndeX, tags } = this.state; // 若是有,则取遍历取得;若是外部没有传入默认值则取数组第一位 if (defaultValue) { let index = tags.indexOf(defaultValue); // 若是传入的默认值不存在,则默认取下标为0的 index = index === -1 ? 0 : index; this.setState({ hightlightIndeX: index }, () => { this.props.onChange(this.getTagValueFromIndex(index)); }); } else { this.props.onChange(this.getTagValueFromIndex(hightlightIndeX)); } }; componentDidMount = () => { this.initDefaultValue(); }; // 记录控件的ref input = React.createRef(); // 显示input后,直接聚焦 showInput = () => { this.setState({ inputVisible: true }, () => this.input.current.focus()); }; // 保存input输入的值 handleInputChange = e => { this.setState({ inputValue: e.target.value }); }; // 新增判定 handleInputConfirm = () => { const { inputValue, tags: prevTags, defaultValue } = this.state; // 若是输入的值已经存在或空值,则不添加 if (inputValue === defaultValue) { message.error('已存在同样的类型!!!'); this.setState({ inputValue: '' }); this.input.focus(); return false; } if (!inputValue) { this.setState({ inputVisible: false, inputValue: '' }); return false; } let tags = prevTags; if (inputValue && tags.indexOf(inputValue) === -1) { tags = [...tags, inputValue]; } this.setState({ tags, inputVisible: false, inputValue: '', }); // 传递给父的新增标签回调 if (this.props.addTag) { this.props.addTag(inputValue); } }; // 取得对应index下的tag的值 getTagValueFromIndex = index => { const { tags } = this.state; return tags[index]; }; // 高亮TAG hightlightTag = index => { this.setState({ hightlightIndeX: index }); if (this.props.onChange) { this.props.onChange(this.getTagValueFromIndex(index)); } }; // 删除tag handleClose = removeTag => { const { hightlightIndeX, tags } = this.state; if (this.props.removeTag) { this.props.removeTag(removeTag); } // 若是删除的位置和高亮的位置同一个,则高亮往前一位 if (tags.indexOf(removeTag) === tags.length - 1) { this.hightlightTag(hightlightIndeX - 1); } }; render() { const { tags, inputVisible, inputValue, hightlightIndeX } = this.state; const { plusBtnText, activeColor } = this.props; return ( <div> {tags.map((tag, index) => { const isLongTag = tag.length > 10; const tagElem = ( <Tag key={tag} color={hightlightIndeX === index ? (activeColor ? activeColor : '#40a9ff') : ''} closable={index !== 0} onClick={() => this.hightlightTag(index)} afterClose={() => this.handleClose(tag)} > {isLongTag ? `${tag.slice(0, 10)}...` : tag} </Tag> ); return isLongTag ? ( <Tooltip title={tag} key={tag}> {tagElem} </Tooltip> ) : ( tagElem ); })} {inputVisible && ( <Input ref={this.input} type="text" size="small" style={{ width: 78 }} value={inputValue} onChange={this.handleInputChange} onBlur={this.handleInputConfirm} onPressEnter={this.handleInputConfirm} /> )} {!inputVisible && ( <Tag onClick={this.showInput} style={{ background: '#fff', borderStyle: 'dashed' }}> <Icon type="plus" /> {plusBtnText ? plusBtnText : 'New Tag'} </Tag> )} </div> ); } }
用法
写成受控组件,无数据不渲染
props |
解释 | 格式类型 | 是否可选 |
data |
待遍历的数组 | 数组 | 必选 |
onChange |
选中的回调 | 函数 | 必选 |
addTag |
添加标签的回调 | 函数 | 必选 |
remvoeTag |
移除标签的回调 | 函数 | 必选 |
defaultValue |
默认值 | 字符串 | 可选 |
plusBtnText |
追加按钮文本替换 | 字符串 | 可选 |
activeColor |
高亮的颜色 | 字符串 | 可选 |
{typeNames && typeNames.length > 0 ? ( <Row type="flex" justify="start" align="middle"> <span style={{ fontSize: 16, fontWeight: 700 }}>文章类型</span> <Divider type="vertical" /> <DocumentType data={typeNames} onChange={this.getTagValue} addTag={this.addTag} removeTag={this.removeTag} defaultValue="草稿" activeColor="#108ee9" plusBtnText="新的分类" /> </Row> ) : null}