UNPKG

@ovine/craft

Version:
220 lines (191 loc) 6.07 kB
/** * 组件选择器 * TODO: * 1. 修复 当无滚动区域时,每次都nav自动跳到最后一个问题。(这是bootstrap问题,完全理解不了为什么,要选到最后一项) * 正确逻辑是:默认不做任何操作 * https://github.com/twbs/bootstrap/blob/main/js/src/scrollspy.js#L209 * 2. 完成 选择后 将 组件添加到对应位置 */ import { Drawer } from 'amis' import { flattenTree, filterTree } from 'amis/lib/utils/helper' import _ from 'lodash' import React, { useEffect, useMemo, useRef } from 'react' import { app } from '@core/app' import { useImmer, useSubscriber } from '@core/utils/hooks' import { publish } from '@core/utils/message' import { onAddNode } from '@/components/preview/actions' import { message, domId } from '@/constants' import nodeConfig from './nodes' import { StyledSelector } from './styled' const Selector = (props) => { const { label: headerLabel = '添加组件' } = props.info const [state, setState] = useImmer({ searchText: '', }) const storeRef = useRef({}) const { searchText } = state const items = useMemo(() => { // 没有查询时 返回所有数据 if (!searchText) { return flattenTree(nodeConfig) } const filteredItems = filterTree(nodeConfig, (item) => { const { type = '', label = '', parent } = item // 父级 默认全部通过匹配,直接判断 子级 的数据 if (!parent) { return true } // 只判断 type 与 label 两个数据 return type.indexOf(searchText) > -1 || label.indexOf(searchText) > -1 }) return flattenTree( // 过滤掉 没有 子级 的数据 filterTree(filteredItems, (item) => (!item.parent && !!item.children.length) || !!item.parent) ) }, [searchText]) const onSearchChange = _.throttle((e) => { const { value } = e.currentTarget setState((d) => { d.searchText = value }) }, 100) const onItemClick = (item, e) => { const $item = $(e.currentTarget) $item .addClass('active') .siblings() .removeClass('active') onAddNode({ container: props.info, node: item, position: { x: e.pageX, y: e.pageY, }, }) } useEffect(() => { // 使用了 bootstrap scrollspy 组件 https://getbootstrap.com/docs/4.5/components/scrollspy const $scrollSpy = $(`#${domId.editorSelector} [data-spy="scroll"]`) const $scrollNav = $(`#${domId.editorSelectorNav}`) storeRef.current.$scrollSpy = $scrollSpy $scrollSpy.scrollspy() $scrollNav.on('click', '.nav-link', (e) => { const $item = $(e.target) if ($item.hasClass('active')) { return } $scrollSpy.scrollspy('dispose') $item .addClass('active') .siblings() .removeClass('active') const $targetItem = $($item.attr('href')) $scrollSpy.animate( { scrollTop: $scrollSpy.scrollTop() + $targetItem.offset().top - $scrollSpy.offset().top }, 200, () => { $scrollSpy.scrollspy('refresh') } ) }) return () => { $scrollSpy.scrollspy('dispose') } }, []) useEffect(() => { const { $scrollSpy } = storeRef.current if (!items.length) { $scrollSpy.scrollspy('dispose') } else { $scrollSpy.scrollspy('refresh') } }, [items.length]) return ( <StyledSelector id={domId.editorSelector}> <div className="selector-title"> <p>{headerLabel}</p> </div> <div className="selector-input"> <div className="input"> <input placeholder="搜索可用组件" value={searchText} onChange={onSearchChange} /> <i className="fa fa-search" /> </div> </div> {items.length === 0 && ( <div className="selector-empty"> <i className="fa fa-chain-broken" /> <span>暂未找到对应组件</span> </div> )} <div className="selector-list"> <div id={domId.editorSelectorNav} className="selector-nav"> {items.map((item, index) => { if (!item.parent) { return ( <div key={index} className="title nav-link" href={`#spy-${item.type}`}> <i className={`fa fa-${item.icon} p-r-xs`} /> <span>{item.label}</span> </div> ) } return ( <div key={index} className="item nav-link" href={`#spy-${item.parent}-${item.type}`}> {item.label} </div> ) })} </div> <div className="selector-content" data-target={`#${domId.editorSelectorNav}`} data-spy="scroll" > {items.map((item, index) => { if (!item.parent) { return ( <div key={index} id={`spy-${item.type}`} className="title"> {item.label} </div> ) } return ( <div key={index} id={`spy-${item.parent}-${item.type}`} className="item" onClick={(e) => onItemClick(item, e)} > <img src={item.img} alt={item.label} /> <div> <h6>{item.label}</h6> <p>{item.desc}</p> </div> </div> ) })} </div> </div> </StyledSelector> ) } export const toggleSelector = (info = {}) => { publish(message.toggleSelector, info) } export default () => { const [info, setInfo] = useImmer({}) const { isShow = false, ...resetInfo } = info const onHide = () => { setInfo((d) => { d.isShow = false }) } useSubscriber(message.toggleSelector, (data = {}) => { setInfo(() => data) }) return ( <Drawer theme={app.theme.getName()} size="md" onHide={onHide} show={isShow} position="right"> <Selector info={resetInfo} /> </Drawer> ) }