有如下組件:
index.js:(src/components/FileTree)
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import './index.less'
import { Icon, Input, Popconfirm } from 'antd'
import classnames from 'classnames'
import { Iconfont } from 'components'
export default class FileTree extends Component {
constructor (props) {
super(props)
this.state = {
count: 0,
selectedKey: props.selectedKey || null,
expandedKeysMap: this.arrayToMap(props.expandedKeys),
hoverNode: null,
confirmVisible: false
}
this.tempExpandedMap = {}
this.placeHolder = document.createElement('li')
this.placeHolder.className = 'tree-placeholder'
}
arrayToMap (list) {
const object = {}
list && list.map(l => {
object[l] = true
})
return object
}
componentWillReceiveProps (nextProps) {
if (nextProps.selectedKey !== this.props.selectedKey) {
this.setState({
selectedKey: nextProps.selectedKey
})
}
if (nextProps.expandedKeys && nextProps.expandedKeys.length !== this.props.expandedKeys.length) {
this.setState({
expandedKeysMap: this.arrayToMap(nextProps.expandedKeys)
})
}
if (nextProps.search !== this.props.search) {
// 保留搜索前的展開(kāi)狀態(tài)
if (this.props.search === '') {
this.preMap = { ...this.state.expandedKeysMap }
}
// 恢復(fù)搜索前的展開(kāi)狀態(tài)
if (nextProps.search === '') {
this.setState({
expandedKeysMap: { ...this.preMap }
})
return
}
this.tempExpandedMap = {}
// 查找需要展開(kāi)的節(jié)點(diǎn)
this.filterTree(nextProps.data, nextProps.search, false)
this.setState({
expandedKeysMap: { ...this.tempExpandedMap }
})
}
}
onClick (el) {
const { onClick, onSelectedKeyChange } = this.props
const { expandedKeysMap } = this.state
expandedKeysMap[el.id] = !expandedKeysMap[el.id]
this.setState({
selectedKey: el.id,
expandedKeysMap
})
onSelectedKeyChange && onSelectedKeyChange(el.id, el)
onClick && onClick(el)
}
onDoubleClick (e, el) {
this.props.onDoubleClick && this.props.onDoubleClick(e, el, this.props.type)
}
onContextMenu (el, e) {
e.stopPropagation()
this.props.onContextMenu && this.props.onContextMenu(e, el)
}
onDragStart (it, e) {
e.stopPropagation()
this.dragged = e.currentTarget.parentNode
e.dataTransfer.effectAllowed = 'move'
this.props.onDrag && this.props.onDrag(it)
}
onDragEnd (it, e) {
const { data, onMove } = this.props
const from = this.dragged.dataset.id
const to = this.over.dataset.id
const fromPath = from.split('_')
const toPath = to.split('_')
const isAfter = this.nodePlacement === 'after'
this.dragged.style.display = 'block'
this.over.parentNode.removeChild(this.placeHolder)
// 尋找需要更新的兩個(gè)文件夾并進(jìn)行移動(dòng)
if (fromPath.length === toPath.length) {
let fromSearch = data
let toSearch = data
fromPath.map((f, i) => {
const fromStep = parseInt(f)
let toStep = parseInt(toPath[i])
if (i === fromPath.length - 1) {
if (isAfter) toStep++
if (fromSearch === toSearch && fromStep < toStep) toStep--
// 移動(dòng)源數(shù)據(jù)
onMove(data, fromSearch, fromStep, toSearch, toStep)
// toSearch.splice(toStep, 0, fromSearch.splice(fromStep, 1)[0])
} else {
fromSearch = fromSearch[fromStep].subTree
toSearch = toSearch[toStep].subTree
}
})
// 關(guān)閉拖拽
this.triggerDraggable(it, false)()
}
}
onDrop (it, e) {
e.stopPropagation()
e.preventDefault()
this.props.onDrop && this.props.onDrop(it)
}
onDragEnter (it, e) {
const { expandedKeysMap } = this.state
const targetLi = e.currentTarget.parentNode
// 同級(jí)拖動(dòng),折疊文件夾,子級(jí)拖動(dòng)到父級(jí),展開(kāi)文件夾
expandedKeysMap[it.id] = targetLi.parentNode !== this.dragged.parentNode
this.setState({
expandedKeysMap
})
}
onDragOver (e) {
e.preventDefault()
const targetLi = e.currentTarget.parentNode
this.dragged.style.display = 'none'
// 子文件夾不能添加到和父文件夾同一級(jí),拖動(dòng)到父文件夾上時(shí)默認(rèn)不處理,現(xiàn)在只支持兩級(jí)
if (targetLi.parentNode === this.dragged.parentNode.parentNode.parentNode) {
e.dataTransfer.dropEffect = 'none'
return
}
// 父文件夾不能添加到和子文件夾同一級(jí),拖動(dòng)到子文件夾上時(shí)默認(rèn)不處理,現(xiàn)在只支持兩級(jí)
if (targetLi.parentNode.parentNode.parentNode === this.dragged.parentNode) {
e.dataTransfer.dropEffect = 'none'
return
}
this.over = targetLi
const relY = e.clientY - this.over.offsetTop
const height = this.over.offsetHeight / 2
const parent = targetLi.parentNode
if (relY > height) {
this.nodePlacement = 'after'
parent.insertBefore(this.placeHolder, targetLi.nextElementSibling)
} else if (relY < height) {
this.nodePlacement = 'before'
parent.insertBefore(this.placeHolder, targetLi)
}
}
onDragLeave () {
}
// 拖拽開(kāi)關(guān)
triggerDraggable = (el, can) => () => {
el.draggable = can
const { expandedKeysMap } = this.state
expandedKeysMap[el.id] = false
this.setState({
expandedKeysMap
})
}
// 添加Hover樣式
addHover = (el) => () => {
if (this.state.confirmVisible) {
return
}
this.setState({
hoverNode: el.id
})
}
// 移除當(dāng)前Hover樣式
removeHover = () => {
if (this.state.confirmVisible) {
return
}
this.setState({
hoverNode: null
})
}
onConfirmPopover = (visible) => {
this.setState({
confirmVisible: visible
})
}
renderAContent (data, el) {
const { expandedKeysMap } = this.state
const { search, readOnly, name } = this.props
const { content, isEdit, id, subTree, count } = el
// 高亮關(guān)鍵字
let showContent = content
if (search.length) {
const index = content.toLowerCase().search(search.toLowerCase())
if (index > -1) {
const beforeStr = content.substr(0, index)
const afterStr = content.substr(index + search.length)
const matchStr = content.substr(index, search.length)
showContent = (
<span>{beforeStr}<span style={{ color: '#f50', backgroundColor: '#ffff00' }}>{matchStr}</span>{afterStr}</span>
)
}
}
return (
<div className="tree_detail"
onMouseEnter={this.addHover(el)}>
<div className="tree_main">
{!isEdit ? (
<div className="tree_hd">
<Icon type={ subTree != null ? (!expandedKeysMap[id] ? 'plus-square-o' : 'minus-square-o') : ''}/>
<span className="tree_title" title={content}>
{showContent}({count || 0})
</span>
</div>
) : (
<div className="tree_hd">
<Input
autoFocus
defaultValue={content}
onPressEnter={this.hideEdit(el)}
onBlur={this.hideEdit(el)}
className="tree_input"
/>
</div>
)}
</div>
{isEdit || readOnly ? null : (
<div className="tree_addon">
<span>
<Iconfont type="edit" onClick={this.showEdit(el)}/>
<Popconfirm
title={
<div style={{ width: 300 }}>
<p className="tree-confirm-title">刪除{name}</p>
<p className="tree-confirm-content">{name === '標(biāo)簽' && count ?
`該${name}將從系統(tǒng)中刪除,您無(wú)法再通過(guò)“${content}”找到對(duì)應(yīng)的${count}項(xiàng)任務(wù),確定刪除嗎?` :
`確定刪除“${content}”嗎?`}</p>
</div>
}
okType="danger"
okText="刪除"
cancelText="取消"
overlayClassName="tree-popover-confirm"
onConfirm={this.deleteItem(data, el)}
onVisibleChange={this.onConfirmPopover}
onClick={e => e.stopPropagation()}
>
<Icon type="close-circle-o" />
</Popconfirm>
{search.length || data.length < 2 ? null : <Iconfont type="sider-collaspe"
onMouseDown={this.triggerDraggable(el, true)}
onMouseUp={this.triggerDraggable(el, false)}/>}
</span>
</div>
)}
</div>
)
}
renderDropDir (data, liClass, el, subTree, positionId) {
const { readOnly } = this.props
return (
<li className={classnames('tree_item', liClass, { 'actived': this.state.selectedKey === el.id }, { 'hover': this.state.hoverNode === el.id })}
key={el.id}
data-id={positionId}
onContextMenu={this.onContextMenu.bind(this, el)}>
<div className="tree_wp"
onDrop={readOnly ? null : this.onDrop.bind(this, el)}
onDragOver={readOnly ? null : this.onDragOver.bind(this)}
onDragEnter={readOnly ? null : this.onDragEnter.bind(this, el)}
onDragLeave={readOnly ? null : this.onDragLeave.bind(this)}
onClick={this.onClick.bind(this, el)}
onDoubleClick={this.onDoubleClick.bind(this, el)}>
{this.renderAContent(data, el)}
</div>
{subTree}
</li>
)
}
renderDragItem (data, liClass, el, subTree, positionId) {
const { readOnly } = this.props
return <li className={classnames('tree_item', liClass, { 'actived': this.state.selectedKey === el.id }, { 'hover': this.state.hoverNode === el.id })}
key={el.id}
data-id={positionId}
onContextMenu={this.onContextMenu.bind(this, el)}>
<div className="tree_wp"
draggable={!readOnly && el.draggable}
onDragStart={readOnly ? null : this.onDragStart.bind(this, el)}
onDragEnd={readOnly ? null : this.onDragEnd.bind(this, el)}
onClick={this.onClick.bind(this, el)}
onDoubleClick={this.onDoubleClick.bind(this, el)}>
{this.renderAContent(data, el)}
</div>
{subTree}
</li>
}
renderLi (data, liClass, el, subTree, positionId) {
return <li className={classnames('tree_item', liClass, { 'actived': this.state.selectedKey === el.id }, { 'hover': this.state.hoverNode === el.id })}
key={el.id}
data-id={positionId}
onContextMenu={this.onContextMenu.bind(this, el)}>
<div className="tree_wp"
onClick={this.onClick.bind(this, el)}
onDoubleClick={this.onDoubleClick.bind(this, el)}>
{this.renderAContent(data, el)}
</div>
{subTree}
</li>
}
renderDragAndDropItem (data, liClass, el, subTree, positionId) {
const { readOnly } = this.props
return (
<li className={classnames('tree_item', liClass, { 'actived': this.state.selectedKey === el.id }, { 'hover': this.state.hoverNode === el.id })}
key={el.id}
data-id={positionId}
onContextMenu={this.onContextMenu.bind(this, el)}>
<div className="tree_wp"
draggable={!readOnly && el.draggable}
onDragStart={readOnly ? null : this.onDragStart.bind(this, el)}
onDragEnd={readOnly ? null : this.onDragEnd.bind(this, el)}
onDrop={readOnly ? null : this.onDrop.bind(this, el)}
onDragOver={readOnly ? null : this.onDragOver.bind(this)}
onDragEnter={readOnly ? null : this.onDragEnter.bind(this, el)}
onDragLeave={readOnly ? null : this.onDragLeave.bind(this)}
onClick={this.onClick.bind(this, el)}
onDoubleClick={this.onDoubleClick.bind(this, el)}>
{this.renderAContent(data, el)}
</div>
{subTree}
</li>
)
}
renderTree (parentId, displayData, originalData, isRoot = false, parentPosition = '') {
const { readOnly } = this.props
const { expandedKeysMap } = this.state
return (
<ul className={classnames('tree', { 'tree-root': isRoot })}
onMouseLeave={isRoot ? this.removeHover : null}>
{displayData && displayData.map((it, index) => {
const { id, subTree, filteredSubTree, liClass, isDrop, isDrag } = it
const positionId = parentPosition ? parentPosition + '_' + index : `${index}`
const subTreeRender = filteredSubTree ? this.renderTree(id, filteredSubTree, subTree, false, positionId) : ''
const isOpened = !!expandedKeysMap[id]
const liClassName = (isOpened ? 'tree-open' : '') + ' ' + (liClass || '')
if (isDrop && !isDrag) {
return this.renderDropDir(originalData, liClassName, it, subTreeRender, positionId)
} else if (isDrag && !isDrop) {
return this.renderDragItem(originalData, liClassName, it, subTreeRender, positionId)
} else if (isDrop && isDrag) {
return this.renderDragAndDropItem(originalData, liClassName, it, subTreeRender, positionId)
} else {
return this.renderLi(originalData, liClassName, it, subTreeRender, positionId)
}
})}
{isRoot || readOnly ? null : (
<li className="tree_item-add">
<a href="javascript:void(0)" onClick={this.addItem(originalData, parentId)}><Icon type="plus"/>新建</a>
</li>
)}
</ul>
)
}
showEdit = (el) => (e) => {
e.stopPropagation()
el.isEdit = true
this.forceUpdate()
}
hideEdit = (el) => (e) => {
const { onRename, onCreate, data } = this.props
el.isEdit = false
if (e.target.value || el.isNew) {
const newName = e.target.value.trim()
if (el.isNew) {
if (newName) {
onCreate(data, el, newName)
} else {
el.parent.pop()
}
} else {
if (newName !== el.content) {
onRename(data, el, newName)
}
}
el.content = newName
}
this.forceUpdate()
}
addItem = (parent, parentId) => () => {
const { count } = this.state
const newItem = {
'content': '',
'id': `new${count}`,
'isDir': false,
'isDrag': true,
'isDrop': true,
'isEdit': true,
'isNew': true,
'parentId': parentId,
'parent': parent
}
parent.push(newItem)
this.setState({
count: count + 1
})
}
deleteItem = (parent, el) => (e) => {
const { onDelete, data } = this.props
e.stopPropagation()
let index = -1
parent.find((p, i) => {
if (p.id === el.id) {
index = i
return true
}
})
if (index > -1) {
onDelete(data, el, parent, index)
// parent.splice(index, 1)
this.forceUpdate()
}
}
filterTree (data, search, stop) {
return data.filter(d => {
const { subTree, content } = d
let exist = content.toLowerCase().search(search.toLowerCase()) > -1 || stop
if (subTree) {
// 如果父級(jí)匹配到了,那么子級(jí)就不用再過(guò)濾了
const filteredSubTree = this.filterTree(subTree, search, exist)
d.filteredSubTree = filteredSubTree
if (filteredSubTree.length) {
exist = true
}
// 記錄需要展開(kāi)子樹(shù)的節(jié)點(diǎn)
if (search && exist) {
this.tempExpandedMap[d.id] = true
}
}
return exist
})
}
render () {
const { data, noDataTips, search } = this.props
const filteredTree = this.filterTree(data, search, false)
return filteredTree.length ? this.renderTree(0, filteredTree, data, true) : <span>{noDataTips}</span>
}
}
FileTree.defaultProps = {
name: '文件夾',
search: '',
noDataTips: '沒(méi)有找到相關(guān)內(nèi)容',
readOnly: false,
expandedKeys: []
}
FileTree.propTypes = {
type: PropTypes.string,
name: PropTypes.string,
readOnly: PropTypes.bool,
data: PropTypes.array,
onChange: PropTypes.func,
onClick: PropTypes.func,
onDoubleClick: PropTypes.func,
onContextMenu: PropTypes.func,
onDrag: PropTypes.func,
onDrop: PropTypes.func,
search: PropTypes.string,
noDataTips: PropTypes.string,
selectedKey: PropTypes.any,
expandedKeys: PropTypes.array,
onSelectedKeyChange: PropTypes.func,
onRename: PropTypes.func,
onDelete: PropTypes.func,
onCreate: PropTypes.func,
onMove: PropTypes.func,
}
在(src/Pages/Favorite/Favorite.js)使用如下語(yǔ)句引入:
import FileTree from '@/components/FileTree'
出現(xiàn)下圖錯(cuò)誤:
檢查到已經(jīng)export了這個(gè)組件,請(qǐng)各位大神看看還有哪里出錯(cuò)了呢?
import 'components'
會(huì)被認(rèn)為是引用全局module,如果要引用當(dāng)前目錄下的某個(gè)組件,應(yīng)該這么寫(xiě):
import './components'
https://segmentfault.com/a/11...
前段時(shí)間的一篇文章這里有介紹到module的路徑問(wèn)題,雖說(shuō)是原生ES-Module,但是在處理非全局module下,規(guī)則基本通用(除了后綴)
北大青鳥(niǎo)APTECH成立于1999年。依托北京大學(xué)優(yōu)質(zhì)雄厚的教育資源和背景,秉承“教育改變生活”的發(fā)展理念,致力于培養(yǎng)中國(guó)IT技能型緊缺人才,是大數(shù)據(jù)專(zhuān)業(yè)的國(guó)家
達(dá)內(nèi)教育集團(tuán)成立于2002年,是一家由留學(xué)海歸創(chuàng)辦的高端職業(yè)教育培訓(xùn)機(jī)構(gòu),是中國(guó)一站式人才培養(yǎng)平臺(tái)、一站式人才輸送平臺(tái)。2014年4月3日在美國(guó)成功上市,融資1
北大課工場(chǎng)是北京大學(xué)校辦產(chǎn)業(yè)為響應(yīng)國(guó)家深化產(chǎn)教融合/校企合作的政策,積極推進(jìn)“中國(guó)制造2025”,實(shí)現(xiàn)中華民族偉大復(fù)興的升級(jí)產(chǎn)業(yè)鏈。利用北京大學(xué)優(yōu)質(zhì)教育資源及背
博為峰,中國(guó)職業(yè)人才培訓(xùn)領(lǐng)域的先行者
曾工作于聯(lián)想擔(dān)任系統(tǒng)開(kāi)發(fā)工程師,曾在博彥科技股份有限公司擔(dān)任項(xiàng)目經(jīng)理從事移動(dòng)互聯(lián)網(wǎng)管理及研發(fā)工作,曾創(chuàng)辦藍(lán)懿科技有限責(zé)任公司從事總經(jīng)理職務(wù)負(fù)責(zé)iOS教學(xué)及管理工作。
浪潮集團(tuán)項(xiàng)目經(jīng)理。精通Java與.NET 技術(shù), 熟練的跨平臺(tái)面向?qū)ο箝_(kāi)發(fā)經(jīng)驗(yàn),技術(shù)功底深厚。 授課風(fēng)格 授課風(fēng)格清新自然、條理清晰、主次分明、重點(diǎn)難點(diǎn)突出、引人入勝。
精通HTML5和CSS3;Javascript及主流js庫(kù),具有快速界面開(kāi)發(fā)的能力,對(duì)瀏覽器兼容性、前端性能優(yōu)化等有深入理解。精通網(wǎng)頁(yè)制作和網(wǎng)頁(yè)游戲開(kāi)發(fā)。
具有10 年的Java 企業(yè)應(yīng)用開(kāi)發(fā)經(jīng)驗(yàn)。曾經(jīng)歷任德國(guó)Software AG 技術(shù)顧問(wèn),美國(guó)Dachieve 系統(tǒng)架構(gòu)師,美國(guó)AngelEngineers Inc. 系統(tǒng)架構(gòu)師。