Commit 3f27b99a authored by 邓晓峰's avatar 邓晓峰

feat: 添加全局菜单搜索

parent 47a9c1b6
Pipeline #34808 passed with stages
in 29 minutes 32 seconds
......@@ -201,7 +201,9 @@ const GlobalHeaderRight = props => {
<HeaderSearch
className={`${styles.action} ${styles.search} ${styles.extendsearch}`}
placeholder={intl.formatMessage({id:'component.search.menu.placeholder'})}
offset="46px"
defaultValue=""
mode="tiled"
options={options}
{...props}
onSelect={handleSelect}
......
import React, {
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import PinyinMatch from 'pinyin-match';
import { useHistory } from 'react-router-dom';
import Icon, { RightCircleOutlined } from '@ant-design/icons';
import Modal from '../modal';
import styes from './index.less';
const HistorySvg = () => (
<svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
width="10"
height="10"
>
<path
d="M1016.405333 0h-351.573333a7.594667 7.594667 0 0 0-5.376 12.885333l149.077333 149.674667-244.224 249.770667H434.346667A7.594667 7.594667 0 0 0 426.666667 419.84v169.898667c0 4.266667 3.413333 7.594667 7.594666 7.594666h169.813334c4.266667 0 7.68-3.413333 7.68-7.594666V486.826667l263.253333-257.792 136.106667 135.509333A7.594667 7.594667 0 0 0 1024 359.253333V7.68A7.594667 7.594667 0 0 0 1016.405333 0z"
fill="#C1C1C1"
/>
<path
d="M979.626667 640a45.226667 45.226667 0 0 0-44.202667 46.08v249.258667H88.490667V88.576h245.418666l1.194667-0.085333A45.056 45.056 0 0 0 378.88 44.288 45.056 45.056 0 0 0 333.994667 0H7.68A7.68 7.68 0 0 0 0 7.68v1008.64c0 4.266667 3.413333 7.68 7.68 7.68h1008.64c4.266667 0 7.68-3.413333 7.68-7.68V686.08a45.226667 45.226667 0 0 0-44.373333-46.08z"
fill="#C1C1C1"
/>
</svg>
);
const DelHistory = () => (
<svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
width="12"
height="12"
>
<path
d="M153.6 345.6a38.4 38.4 0 0 1 0-76.8h665.6a38.4 38.4 0 1 1 0 76.8H153.6z"
fill="#888888"
/>
<path
d="M345.6 307.2a38.4 38.4 0 0 1-76.8 0V204.8c0-49.4592 40.1408-89.6 89.6-89.6h256c49.4592 0 89.6 40.1408 89.6 89.6v102.4a38.4 38.4 0 1 1-76.8 0V204.8a12.8 12.8 0 0 0-12.8-12.8H358.4a12.8 12.8 0 0 0-12.8 12.8v102.4zM166.4 460.8a38.4 38.4 0 0 1 76.8 0v358.4c0 7.0656 5.7344 12.8 12.8 12.8h460.8a12.8 12.8 0 0 0 12.8-12.8V460.8a38.4 38.4 0 1 1 76.8 0v358.4A89.6 89.6 0 0 1 716.8 908.8H256A89.6 89.6 0 0 1 166.4 819.2V460.8z"
fill="#888888"
/>
<path
d="M371.2 460.8a38.4 38.4 0 0 1 76.8 0v256a38.4 38.4 0 1 1-76.8 0V460.8zM524.8 460.8a38.4 38.4 0 0 1 76.8 0v256a38.4 38.4 0 1 1-76.8 0V460.8z"
fill="#888888"
/>
</svg>
);
const HistoryHrefIcon = props => <Icon component={HistorySvg} {...props} />;
const DelIcon = props => <Icon component={DelHistory} {...props} />;
const unique = (arr, val) => {
const res = new Map();
return arr.filter(item => !res.has(item[val]) && res.set(item[val], 1));
};
const SearchPanel = props => {
const [visible, setVisible] = useState(false);
// const [disabled, setDisabled] = useState(false);
// const [axis, setAxis] = useState('both');
// const [currentIndex, setCurrentIndex] = useState(-1);
const [eventCounts, setEventCounts] = useState(() => ['resultConsole', -1]);
const featureRef = useRef(null);
const consoleRef = useRef(null);
let recentKeywords = props.recentKeywords.toJS
? props.recentKeywords.toJS()
: props.recentKeywords;
recentKeywords = Array.isArray(recentKeywords)
? recentKeywords
: [recentKeywords];
let recentVisited = props.recentVisited.toJS
? props.recentVisited.toJS()
: props.recentVisited;
recentVisited = Array.isArray(recentVisited)
? recentVisited
: [recentVisited];
let recentProducts = props.recentProducts.toJS
? props.recentProducts.toJS()
: props.recentProducts;
recentProducts = Array.isArray(recentProducts)
? recentProducts
: [recentProducts];
const history = useHistory();
// eslint-disable-next-line consistent-return
function getParents(data, key) {
// eslint-disable-next-line guard-for-in,no-restricted-syntax
for (const i in data) {
if (data[i].key === key) {
return [];
}
if (data[i].routes) {
const ro = getParents(data[i].routes, key);
if (ro !== undefined) return ro.concat(data[i]);
}
}
}
const goFeature = (path, rect, value) => {
// eslint-disable-next-line no-param-reassign
debugger
value = value || props.value;
const findKeywordIndex =
recentKeywords.length > 0
? recentKeywords.findIndex(item => item.data === value)
: -1;
const findRecentVisitedIndex =
recentVisited.length > 0
? recentVisited.findIndex(item => item.data.name === rect.name)
: -1;
if (findKeywordIndex === -1)
props.updateRecentKeywords && props.updateRecentKeywords(value);
if (findRecentVisitedIndex === -1)
props.updateRecentVisited && props.updateRecentVisited(rect);
let parents = getParents(props.menu, rect.key);
parents = parents[parents.length - 1];
const findIndex = props.menu.findIndex(k => k.name === parents.name);
props.updateCurrentIndex && props.updateCurrentIndex(findIndex);
const routes = props.menu[findIndex];
const selectedIndex = routes.routes.findIndex(item => item.name === rect.subSystem);
let currentPath = void 0;
// props.updateOpenKeys && props.updateOpenKeys([parents.key]);
if (rect && rect.routes && rect.routes.length > 0) {
currentPath = rect.routes[0].path;
} else {
currentPath = path;
}
// props.updateSelectedKeys && props.updateSelectedKeys(rect.key);
window.share && window.share.event && window.share.event.emit('event:updateCurrentChildrenRoutes', {
currentPath: currentPath,
currentRoute: rect,
selectedIndex: selectedIndex
});
if(window.__POWERED_BY_QIANKUN__) {
history.push(`/civbase${currentPath}`);
} else {
history.push(currentPath);
}
// props.updatePathname && props.updatePathname(rect.key);
};
const goProduct = (event, item) => {
/* eslint-disable */
event.persist && event.persist();
event && event.stopPropagation();
const findRecentIndex =
recentProducts.length > 0
? recentProducts.findIndex(item => item.data.name === item.name)
: -1;
if (findRecentIndex === -1)
props.updateRecentProduct && props.updateRecentProduct(item);
const findIndex = props.menu.findIndex(k => k.name === item.name);
props.updateCurrentIndex && props.updateCurrentIndex(findIndex);
};
const handlerMore = event => {
event.persist && event.persist();
event && event.nativeEvent.stopImmediatePropagation();
setVisible(!visible);
props.onClose && props.onClose(event);
};
const handlerDelHistory = () => {
props.clearRecentProduct && props.clearRecentProduct();
};
const matchValue = (str, value) => {
const match = PinyinMatch.match(str, value || props.value);
return match;
};
const transformRecentProduct = () =>
unique(recentProducts.map(item => item.data), 'name');
const transformWillSearch = () => {
if (props.willSearch.length === 0) {
return null;
}
return (
<div className={styes.item}>
<div className={styes.title}>你是不是想搜:</div>
<div className={styes.label}>
{(unique(props.willSearch, 'name') || []).map(item => (
// eslint-disable-next-line react/button-has-type
<button
className={styes.btn}
key={item.name}
title={item.title}
onClick={() => props.onWillSearch(item.name)}
>
{item.name}
</button>
))}
</div>
</div>
);
};
const transformConsole = () => {
if (props.console.length === 0) {
return <div className={styes.empty}>没有找到相关内容</div>;
}
return (props.console || []).map((item, index) => {
const match = matchValue(item.name);
const name = match ? (
<span>
{item.name.substring(0, match[0])}
<em>{item.name.substring(match[0], match[1] + 1)}</em>
{item.name.substring(match[1] + 1)}
</span>
) : (
item.name
);
return (
<a
className={styes.featureItem}
key={item.name}
onClick={event => goProduct(event, item)}
tabIndex="1"
onKeyDown={event => event.key === 'Enter' && event.keyCode === 13 && goProduct(event, item)}
>
<span className={styes.text}>{name}</span>
<span className={styes.featIn}>
<RightCircleOutlined />
</span>
</a>
);
});
};
const transformFeature = () => {
if (props.willSearch.length === 0) {
return <div className={styes.empty}>没有找到相关内容</div>;
}
return (unique(props.willSearch.slice(0, 20), 'path') || []).map(
(item, index) => {
const match = matchValue(item.name);
const name = match ? (
<span>
{item.name.substring(0, match[0])}
<em>{item.name.substring(match[0], match[1] + 1)}</em>
{item.name.substring(match[1] + 1)}
</span>
) : (
item.name
);
return (
<a
className={styes.featureItem}
key={index}
onClick={() => goFeature(item.path, item)}
tabIndex="1"
onKeyDown={event => event.key === 'Enter' && event.keyCode === 13 && goFeature(item.path, item)}
>
<span className={styes.text}>
{name}
<HistoryHrefIcon className={styes.href} />
</span>
<span className={styes.featIn}>
{item.parent && item.parent.name}
</span>
</a>
);
},
);
};
useEffect(() => {
setEventCounts(['resultConsole', -1])
}, [props.value]);
const RESULT_KEYS = ['resultConsole', 'resultFeature'];
const getFistKey = (response, key, type) => {
const result = RESULT_KEYS.filter(item => {
const ret = response[item];
return !!(ret && 'success' === ret.type && ret.response.items.length > 0);
});
if(0 === result.length) {
return null;
};
if(!key) {
return "down" === type ? result[0]: type === "left" ? result[0]: type === "right" ? result[result.length - 1]:result[result.length - 1];
}
const index = result.indexOf(key);
return index < 0 ? null: 'down' === type ? index + 1 < result.length ? result[index + 1]: null: index - 1 >= 0 ? result[index - 1]: null;
}
const transfromEventHander = useCallback((children, data, type, handlerKey) => {
if (children === 'none') {
return
}
if (null === children) {
const key = getFistKey(data, null, handlerKey ? handlerKey: type);
if (!key) {
return null;
}
children = key === 'resultConsole' ? consoleRef.current.children: featureRef.current.children
const ret = data[key];
const currentIndex = 'up' === type ? ret.response.items.length - 1 : 0
return 'success' !== ret.type ? null : (
setEventCounts([key, currentIndex]),
'up' === type ? (props.onNavUp && (props.onNavUp(), children[currentIndex].focus())): props.onNavDown && (props.onNavDown(), children[currentIndex].focus())
);
}
let eventsKey = eventCounts[0];
let eventsIndex = eventCounts[1];
let resultData = data[eventsKey];
if(resultData.response.items.length === 0) {
const key = getFistKey(data, null, type);
eventsKey = key;
eventsIndex= -1;
resultData = data[key];
children = key === 'resultConsole' ? consoleRef.current.children: featureRef.current.children
}
if ('success' !== resultData.type) {
return null;
}
const index= "up" === type ? eventsIndex - 1: eventsIndex + 1;
if(index >=0 && index < resultData.response.items.length) {
return setEventCounts([eventsKey, index]), 'up' === type ? (props.onNavUp && (props.onNavUp(), children[index].focus())): props.onNavDown && (props.onNavDown(), children[index].focus())
}
const getKey = getFistKey(data, eventsKey, type);
if(!getKey) {
return null;
}
const result = data[getKey];
if('success' !== result.type) {
return null;
}
if('up' === type) {
children = getKey === 'resultConsole' ? consoleRef.current.children: featureRef.current.children
return setEventCounts([getKey, result.response.items.length - 1]), props.onNavUp && ( props.onNavUp(), children[result.response.items.length - 1].focus())
}
return setEventCounts([getKey, 0]), (props.onNavDown && (props.onNavDown(), featureRef.current.children[0].focus()))
}, [eventCounts, props.console, props.willSearch])
const transfomResponse = (data) => {
return {
query: props.value,
response: {
items: data,
total: data.length
},
type: 'success'
}
}
let children = null;
const eventHander = useCallback((event) => {
const transformData = {
resultConsole: transfomResponse(props.console),
resultFeature: transfomResponse( unique(props.willSearch.slice(0, 20), 'path')),
};
switch (event.key) {
case "Escape":
props.onEsc && ( event.preventDefault(), props.onEsc())
break;
case "ArrowRight":
event.preventDefault();
children = featureRef.current.children;
transfromEventHander(null, transformData, 'down', 'right')
break;
case "ArrowDown":
const type = eventCounts[0]
children = type === 'resultFeature' ? featureRef.current.children: consoleRef.current.children;
event.preventDefault();
transfromEventHander(children, transformData, 'down')
break;
case "ArrowLeft":
event.preventDefault();
// const leftChildren = consoleRef.current.children;
setEventCounts(['resultConsole', -1])
transfromEventHander(null, transformData, 'up', 'left')
break;
case "ArrowUp":
children = eventCounts[0] === 'resultFeature' ? featureRef.current.children: consoleRef.current.children;
event.preventDefault();
transfromEventHander(children, transformData, 'up')
break
}
}, [eventCounts, props.console, props.willSearch]);
useEffect(() => {
props.test && document.addEventListener('keydown', eventHander);
return function() {
return document.removeEventListener('keydown', eventHander)
}
}, [props.test, eventHander, eventCounts])
return (
<div
className={classNames(styes.searchPanel, props.className)}
style={props.style}
onMouseEnter={props.onMouseEnter}
onMouseLeave={props.onMouseLeave}
onFocus={props.onFocusChange}
>
<div className={styes.container} ref={props.wrapperRef}>
{props.value === '' && (
<div className={styes.body} style={{ display: 'flex' }}>
<div className={styes.flexItem}>
{recentKeywords.length > 0 ? (
<>
<h5 className={styes.historyTitle}>
最近搜索
<DelIcon onClick={handlerDelHistory} />
</h5>
<div className={styes.label}>
{(unique(recentKeywords, 'data') || []).map(item => (
// eslint-disable-next-line react/button-has-type
<button
className={styes.btn}
title={item.data}
key={item.data}
onClick={() => props.onWillSearch(item.data)}
>
{item.data}
</button>
))}
</div>
</>
) : null}
<h5
className={styes.historyTitle}
style={{
margin: recentKeywords.length > 0 ? '16px 0px 0px' : '0',
}}
>
最近访问的产品
</h5>
{recentProducts.length > 0 ? (
(transformRecentProduct() || []).map(item => (
<a
title={item.name}
className={styes.historyHref}
key={item.name}
onClick={event => goProduct(event, item)}
onKeyDown={(event => console.log(event))}
>
<span className={styes.title}>{item.name}</span>
<span className={styes.featIn}>
<RightCircleOutlined />
</span>
</a>
))
) : (
<div className={styes.empty}>无最近访问的产品</div>
)}
</div>
<div className={styes.flexItem}>
<div className={styes.title}>关注更多信息</div>
<div className={styes.label}>
<div className={styes.empty}>没有找到相关内容</div>
</div>
</div>
</div>
)}
{props.value !== '' && (
<>
<div className={styes.body}>
{transformWillSearch()}
<div className={styes.features}>
<div className={styes.flex}>
<h5 className={styes.title}>
产品入口({props.console.length})
</h5>
<div className={styes.feature} ref={consoleRef}>{transformConsole()}</div>
</div>
<div className={styes.flex}>
<h5 className={styes.title}>快捷操作</h5>
<div className={styes.feature} ref={featureRef}>{transformFeature()}</div>
</div>
</div>
</div>
{props.willSearch.length > 0 || props.console.length > 0 ? (
<div className={styes.footer}>
<span onClick={handlerMore}>查看全部结果</span>
</div>
) : null}
</>
)}
</div>
<Modal
visible={visible}
triggerElRef={props.target}
{...props}
matchValue={matchValue}
goFeature={goFeature}
goProduct={goProduct}
/>
</div>
);
};
export default SearchPanel;
......@@ -10,7 +10,8 @@ import useMergeValue from 'use-merge-value';
import { SearchOutlined } from '@ant-design/icons';
import styles from './index.less';
import SearchPanel from './panel';
import SearchPanel from './Panel';
import ExtendPanel from './ExtendPanel';
const HeaderSearch = props => {
const {
......@@ -162,7 +163,8 @@ const HeaderSearch = props => {
prefix={<SearchOutlined className="site-form-item-icon" />}
/>
</div>
<SearchPanel
{
props.mode && props.mode === 'tiled' ? <ExtendPanel
value={keyword}
{...props}
target={wrapperRef}
......@@ -171,7 +173,28 @@ const HeaderSearch = props => {
className={focus ? styles.active : styles.hidden}
style={{
left: `${targetOffset}px`,
top: isShowWidget ? '42px' : '52px',
top: isShowWidget ? '42px' : props.offset || '52px',
}}
onEsc={onEsc}
onNavUp={onNavUp}
onNavDown={onNavDown}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onWillSearch={onWillSearch}
onFocusChange={onFocusChange}
willSearch={willSearch}
onClose={onClose}
console={willMenu}
/>: <SearchPanel
value={keyword}
{...props}
target={wrapperRef}
inputRef={inputRef}
test={focus}
className={focus ? styles.active : styles.hidden}
style={{
left: `${targetOffset}px`,
top: isShowWidget ? '42px' : props.offset || '52px',
}}
onEsc={onEsc}
onNavUp={onNavUp}
......@@ -184,6 +207,8 @@ const HeaderSearch = props => {
onClose={onClose}
console={willMenu}
/>
}
</div>
);
};
......
......@@ -254,13 +254,13 @@ const BasicLayout = props => {
const [childrenRoutes, setChildrenRoutes] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [tabActiveKey, setTabActiveKey] = useState("1");
// const [currentRoutes, setCurrentRoutes] = useState([]);
const [siteAction, setSiteAction] = useState(
() => new Site(props, setSiteLoading),
);
const basename = getBaseName();
const history = useHistory();
const currentRoutes = props.route.routes[props.currentMenuIndex];
let currentRoutes = props.route.routes[props.currentMenuIndex]
useEffect(() => {
const initSelectRoute = findPathByLeafId(
`${props.location.pathname}`,
......@@ -277,9 +277,15 @@ const BasicLayout = props => {
currentChildrenRoute = initSelectRoute
}
let childrenName = currentChildrenRoute ? currentChildrenRoute.name: parentMenuName;
if(currentChildrenRoute.routes) {
setTabActiveKey(currentChildrenRoute.routes[0].path)
} else {
setTabActiveKey(currentChildrenRoute.path);
}
const initSelectIndex = currentRoutes.routes.findIndex(item => item.name === childrenName);
setSelectIndex(initSelectIndex);
currentChildrenRoute && currentChildrenRoute.routes ? setChildrenRoutes(currentChildrenRoute.routes): setChildrenRoutes([currentChildrenRoute])
currentChildrenRoute && currentChildrenRoute.routes ? setChildrenRoutes(currentChildrenRoute.routes): setChildrenRoutes([currentChildrenRoute]);
} else {
setChildrenRoutes([
{
......@@ -291,6 +297,14 @@ const BasicLayout = props => {
}
}, []);
window.share && window.share.event && window.share.event.on('event:updateCurrentChildrenRoutes', ({currentPath, currentRoute, selectedIndex}) => {
currentRoute && currentRoute.parent && currentRoute.parent.routes ? setChildrenRoutes(currentRoute.parent.routes): setChildrenRoutes([currentRoute]);
setSelectIndex(selectedIndex);
setTabActiveKey(currentPath)
});
useEffect(() => {
siteAction.setGlobalConfig(props.global);
if (!Cookies.get('token')) {
......@@ -326,13 +340,10 @@ const BasicLayout = props => {
window.share.event.on('updateSite', res => setCityData(res));
return () => {
window.share.event.removeAllListeners('updateSite');
// sessionStorage.removeItem('tabNav')
// sessionStorage.removeItem('tabActiveKey')
};
}, []);
const handlerSecond = (item, index) => {
let current = void 0;
if(item && item.routes) {
setChildrenRoutes(item.routes);
......@@ -341,9 +352,16 @@ const BasicLayout = props => {
setChildrenRoutes([item]);
current = item;
}
setTabActiveKey("1");
window.history.pushState(null, '', `/civbase${current.path}`);
setTabActiveKey(current.path);
setSelectIndex(index);
if(current.routes) {
setSelectedKeys([current.routes[0].href]);
window.history.pushState(null, '', `/civbase${current.routes[0].path}`);
} else {
window.history.pushState(null, '', `/civbase${current.path}`);
}
}
const handleToggleSystem = () => {
......@@ -545,7 +563,7 @@ const BasicLayout = props => {
</div>
</div>
<div className={layoutStyles['menu-item-children']} style={{width: collapse ? 'calc(100% - 46px)': 'calc(100% - 100px)'}}>
<Tabs activeKey={tabActiveKey} tabBarGutter={30} tabPosition="top" onTabClick={(event) => handleSelectMenuItem(event)}>
<Tabs activeKey={tabActiveKey} defaultActiveKey={tabActiveKey} tabBarGutter={30} tabPosition="top" onTabClick={(event) => handleSelectMenuItem(event)}>
{childrenRoutes.map((item, index) => (
<>
<TabPane
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment