Commit f50f9f3f authored by 邓晓峰's avatar 邓晓峰

feat: 菜单搜索添加键盘选择事件

parent eddbeca5
Pipeline #22809 skipped with stages
......@@ -2,7 +2,7 @@ module.exports = {
mock: true,
dev: {
'/CityInterface': {
// target: 'http://192.168.10.151:8055',
// target: 'http://192.168.10.150:8777',
target: 'https://panda-water.cn',
// target: 'http://192.168.19.102:8055',
......@@ -20,7 +20,7 @@ module.exports = {
},
},
'/cityinterface': {
// target: 'http://192.168.10.151:8055',
// target: 'http://192.168.10.150:8777',
// target: 'http://192.168.10.150:8050',
target: 'https://panda-water.cn',
// target: 'http://192.168.19.102:8055',
......@@ -52,8 +52,8 @@ module.exports = {
'/Publish': {
// target: 'http://192.168.12.8:8098',
// target: 'http://192.168.10.20:8888',
// target: 'http://192.168.10.151:8055',
target: 'https://panda-water.cn',
target: 'http://192.168.10.151:8055',
// target: 'https://panda-water.cn',
// target: 'http://192.168.19.102:8055',
// target: 'https://panda-water.com',
// target: 'http://192.168.10.150:8050',
......
......@@ -88,7 +88,7 @@
"@babel/runtime": "^7.10.5",
"promise.prototype.finally": "^3.1.2",
"@wisdom-utils/components": "0.0.6",
"@wisdom-utils/utils": "0.0.27",
"@wisdom-utils/utils": "0.0.28",
"@wisdom-utils/vapp-browser-vm": "^0.0.11",
"@wisdom-utils/vapp-browser-vm-plugins": "^0.0.13",
"animate.css": "^4.1.1",
......
......@@ -9,6 +9,7 @@ export const API = {
GENERATE_QRCODE: 'cityinterface/rest/services.svc/generatetokenByqrcode',
GENERATE_TOKEN_CHANGE: '/cityinterface/rest/services.svc/generatetokenquick',
GET_WEB_SITE_CONFIG: '/CityInterface/rest/services.svc/GetWebSiteConfig',
GET_WEB_SITE_CONFIG_GEWAY: '/Publish/OMS/PlatformCenter/GetWebSiteConfig',
GET_USER_INFO: '/CityInterface/rest/services.svc/getUserInfo',
GET_OA: '/CityInterface/rest/services/OA.svc/getLoginInfo',
GET_LOGS: '/CityInterface/rest/services/portal.svc/OMMonitor/SaveLoginInfo',
......@@ -55,7 +56,12 @@ const services = {
},
getWebSiteConfig: {
url: API.GET_WEB_SITE_CONFIG,
url: () =>
window.globalConfig &&
window.globalConfig &&
window.globalConfig.hasGateWay
? API.GET_WEB_SITE_CONFIG_GEWAY
: API.GET_WEB_SITE_CONFIG,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_HTTP,
},
......
......@@ -64,6 +64,16 @@ const HeaderSearch = props => {
// inputRef.current.blur();
// setIsShowWidget(true)
};
const onEsc = () => {
setValue('');
};
const onNavUp = () => {
inputRef.current.blur();
};
const onNavDown = () => {
// console.log('onNavDown');
};
const onSearch = event => {
event.persist && event.persist();
setKeyword(event.target.value);
......@@ -146,6 +156,7 @@ const HeaderSearch = props => {
onKeyDown={event => {
event.key === 'Escape' &&
(setKeyword(''), setFocus(false), inputRef.current.blur());
wrapperRef.current.focus();
}}
onChange={onSearch}
prefix={<SearchOutlined className="site-form-item-icon" />}
......@@ -156,11 +167,15 @@ const HeaderSearch = props => {
{...props}
target={wrapperRef}
inputRef={inputRef}
test={focus}
className={focus ? styles.active : styles.hidden}
style={{
left: `${targetOffset}px`,
top: isShowWidget ? '42px' : '52px',
}}
onEsc={onEsc}
onNavUp={onNavUp}
onNavDown={onNavDown}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onWillSearch={onWillSearch}
......
......@@ -85,6 +85,7 @@
}
.container {
padding: 8px 0px;
outline: none;
.body {
min-width: 480px;
max-width: 600px;
......@@ -256,7 +257,7 @@
&:visited {
color: rgb(17, 17, 17);
}
&:hover {
&:hover, &:focus {
background-color: rgb(247, 247, 247);
text-decoration: none;
color: rgb(17, 17, 17);
......@@ -266,6 +267,7 @@
}
}
}
.text {
flex: 1 1 0%;
overflow: hidden;
......
import React, { useState } from 'react';
import React, { useState, useEffect, useCallback, useRef } from 'react';
import classNames from 'classnames';
import PinyinMatch from 'pinyin-match';
......@@ -60,7 +60,10 @@ 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;
......@@ -207,6 +210,8 @@ const SearchPanel = props => {
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}>
......@@ -238,6 +243,8 @@ const SearchPanel = props => {
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}
......@@ -251,6 +258,130 @@ const SearchPanel = props => {
},
);
};
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
......@@ -260,7 +391,7 @@ const SearchPanel = props => {
onMouseLeave={props.onMouseLeave}
onFocus={props.onFocusChange}
>
<div className={styes.container}>
<div className={styes.container} ref={props.wrapperRef}>
{props.value === '' && (
<div className={styes.body} style={{ display: 'flex' }}>
<div className={styes.flexItem}>
......@@ -301,6 +432,7 @@ const SearchPanel = props => {
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}>
......@@ -330,11 +462,11 @@ const SearchPanel = props => {
<h5 className={styes.title}>
产品入口({props.console.length})
</h5>
<div className={styes.feature}>{transformConsole()}</div>
<div className={styes.feature} ref={consoleRef}>{transformConsole()}</div>
</div>
<div className={styes.flex}>
<h5 className={styes.title}>快捷操作</h5>
<div className={styes.feature}>{transformFeature()}</div>
<div className={styes.feature} ref={featureRef}>{transformFeature()}</div>
</div>
</div>
</div>
......
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useRef, useState, useCallback } from 'react';
import ReactDOM from 'react-dom';
import { AutoComplete, Input, Pagination, Tabs } from 'antd';
......@@ -54,9 +54,11 @@ function itemRender(current, type, originalElement) {
}
const Modal = props => {
const dragRef = useRef(null);
const searchRef = useRef(null);
const [isWidgetShow, setIsWidgetShow] = useState(props.visible);
const [keyword, setKeyWord] = useState(props.value);
const [searchProduct, setSearchProduct] = useState([]);
const [eventCounts, setEventCounts] = useState(() => ['resultConsole', -1]);
// const [tabIndex]
const [pageCurrent, setPageCurrent] = useState(1);
const [fasts, setFasts] = useState(
......@@ -98,6 +100,108 @@ const Modal = props => {
setFasts(searchResult);
}, [keyword]);
useEffect(() => {
setKeyWord(props.value);
}, [props.value]);
const onNavUp = () => {};
const onNavDown = () => {};
const RESULT_KEYS = ['resultConsole'];
const getFistKey = (response, key, type) => {
/* eslint-disable */
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]: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) => {
if (children === 'none') {
return
}
if (null === children) {
const key = getFistKey(data, null, type);
if (!key) {
return null;
}
const ret = data[key];
const currentIndex = 'up' === type ? ret.response.items.length - 1 : 0
return 'success' !== ret.type ? null : (
setEventCounts([key, currentIndex]),
'up' === type ? (children[currentIndex].focus()): (children[currentIndex].focus())
);
}
let eventsKey = eventCounts[0];
let eventsIndex = eventCounts[1];
let resultData = data[eventsKey];
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 ? children[index].focus(): 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) {
return setEventCounts([getKey, result.response.items.length - 1]), children[result.response.items.length - 1].focus()
}
return setEventCounts([getKey, 0]), searchRef.current.children[0].focus()
}, [eventCounts, fasts])
const transfomResponse = (data) => {
return {
query: props.value,
response: {
items: data,
total: data.length
},
type: 'success'
}
}
const eventHander = useCallback(
event => {
// eslint-disable-next-line default-case
const data = {
resultConsole: transfomResponse(searchRef.current.children)
};
switch (event.code) {
case 'ArrowDown':
event.preventDefault();
transfromEventHander(searchRef.current.children, data, 'down')
break;
case 'ArrowUp':
event.preventDefault();
transfromEventHander(searchRef.current.children, data, 'up')
break;
}
},
[fasts, eventCounts],
);
useEffect(() => {
props.visible && document.addEventListener('keydown', eventHander);
return function() {
return document.removeEventListener('keydown', eventHander);
};
}, [props.visible, fasts, eventCounts]);
useEffect(() => {
setIsWidgetShow(props.visible);
}, [props.visible]);
......@@ -122,7 +226,6 @@ const Modal = props => {
<li
className={styles['group-item']}
title={item.data || item.name}
tabIndex={-1}
dataindex={index}
>
<div className={styles['item-inner']}>
......@@ -191,7 +294,7 @@ const Modal = props => {
);
}
return (
<div className={styles.resultContainer}>
<div className={styles.resultContainer} ref={searchRef}>
{splitDataSource.map((item, index) => {
const match = props.matchValue(item.name, keyword);
const name =
......@@ -207,10 +310,12 @@ const Modal = props => {
return (
<a
className={styles.item}
key={item.name}
key={item.name + index}
tabIndex="1"
onClick={event => {
item.parent ? goFeature(event, item) : goProduct(event, item);
}}
onKeyDown={event => event.key === 'Enter' && event.keyCode === 13 && item.parent ? goFeature(event, item) : goProduct(event, item)}
>
<span className={styles.title}>
{name}
......@@ -299,23 +404,7 @@ const Modal = props => {
/>
</AutoComplete>
</div>
<div className={styles.result}>
{transformSearchFasts()}
{/* <Tabs defaultActiveKey="1" size="small">
<TabPane tab="产品功能入口" key="1">
{
transformSeachProductList()
}
</TabPane>
<TabPane tab="快捷操作" key="2">
{
transformSearchFasts()
}
</TabPane>
</Tabs> */}
</div>
<div className={styles.result}>{transformSearchFasts()}</div>
</div>
</div>
</Draggable>
......
......@@ -193,7 +193,7 @@
color: inherit;
padding: 0 0;
text-decoration: none;
&:hover {
&:hover, &:focus {
background-color: #f7f7f7;
cursor: pointer;
:global(.@{ant-prefix}icon-right-circle) {
......
......@@ -7,4 +7,6 @@ export const SERVICE_APP_LOGIN_MODE = {
phone: 'phone',
};
export const SERVICE_INTERFACE_SUCCESS_CODE = 0;
export const SERVICE_APP_CLOSE_ALL_TABS = 'app.close.tabs';
......@@ -44,16 +44,18 @@ const noMatch = (
/>
);
const renderIcon = (icon, size) => {
const renderIcon = (icon, size, alias) => {
const transformIcon = () => icon.replace(/\s*/g, '');
const url = `${baseURI}/${alias}/${transformIcon()}`;
return (
<img
src={`${baseURI}/civweb4/${transformIcon()}`}
src={url}
style={{ width: `${size}px`, height: `${size}px`, opacity: '0.8' }}
alt=""
/>
);
};
const menuExtraRender = currentRoutes => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [toggleSystem, setToggleSystem] = useState(false);
......@@ -587,7 +589,7 @@ const BasicLayout = props => {
if (collapse) {
return (
<>
{renderIcon(menuItemProps.extData && menuItemProps.extData.icon, 17)}
{renderIcon(menuItemProps.extData && menuItemProps.extData.icon, 17, menuItemProps.alias)}
<div
className={`ant-pro-menu-item ${styles.shortName} ${
styles.minixName
......@@ -611,7 +613,7 @@ const BasicLayout = props => {
}
return (
<>
{renderIcon(menuItemProps.extData && menuItemProps.extData.icon, 17)}
{renderIcon(menuItemProps.extData && menuItemProps.extData.icon, 17, menuItemProps.alias)}
{defaultDom}
</>
);
......@@ -653,6 +655,7 @@ const BasicLayout = props => {
{renderIcon(
menuItemProps.extData && menuItemProps.extData.icon,
17,
menuItemProps.alias,
)}
<span className="ant-pro-menu-item">
<span className="ant-pro-menu-item-title">
......@@ -681,6 +684,7 @@ const BasicLayout = props => {
{renderIcon(
menuItemProps.extData && menuItemProps.extData.icon,
17,
menuItemProps.alias,
)}
{collapse ? (
<div className={`ant-pro-menu-item ${styles.shortName}`}>
......
......@@ -5,7 +5,10 @@ import sha1 from 'sha1';
import { appService } from '../../../api';
import 'kit_logger';
import SlideVerify from '../../../components/SlideVerify';
import { SERVICE_APP_LOGIN_MODE } from '../../../constants';
import {
SERVICE_APP_LOGIN_MODE,
SERVICE_INTERFACE_SUCCESS_CODE,
} from '../../../constants';
// eslint-disable-next-line no-undef
const Logger = logger('login');
......@@ -160,22 +163,24 @@ class Login {
'request.preventCache': Date.now(),
})
.then(response => {
if (response && response.length > 0) {
self.globalConfig = Object.assign(
self.globalConfig,
response.shift(),
{
const result =
response && response.code === SERVICE_INTERFACE_SUCCESS_CODE
? Array.isArray(response.data)
? response.data
: []
: response;
if (result && result.length > 0) {
self.globalConfig = Object.assign(self.globalConfig, result.shift(), {
theme: self.globalConfig.theme,
menu: self.globalConfig.menu,
style: self.globalConfig.style,
},
);
});
self.updateConfig && self.updateConfig(self.globalConfig);
self.getProjectItems().then(() => {
window.subSysCfg = {};
response.forEach(item => {
result.forEach(item => {
window.subSysCfg[item.client] = item;
});
......@@ -205,8 +210,14 @@ class Login {
)
// eslint-disable-next-line no-shadow
.then(response => {
if (response && response.length > 0) {
const mainConf = response.shift();
const resultData =
response && response.code === SERVICE_INTERFACE_SUCCESS_CODE
? Array.isArray(response.data)
? response.data
: []
: response;
if (resultData && resultData.length > 0) {
const mainConf = resultData.shift();
if (mainConf.homepage)
self.globalConfig.homepage = mainConf.homepage;
if (mainConf.bannerLogo)
......@@ -245,7 +256,7 @@ class Login {
}
})
.catch(error => {
console.log(error)
console.log(error);
// Logger.log(error);
this.handleLoginError();
// Logger.log('获取网络配置失败');
......
......@@ -4,7 +4,9 @@ import { transformURL } from './utils';
export const isURL = function(url) {
// eslint-disable-next-line no-useless-escape
return /^(http|https)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/.test(url);
}
};
const DEFAULT_APPLICATION = 'civweb4';
/* eslint-disable */
export const guid = function(prefix) {
let date = new Date().getTime();
......@@ -39,17 +41,22 @@ const getURL = url => {
const generRotes = (widgets, parent, level = 0) => {
const ret = [];
widgets.forEach((item, index) => {
if (item.hasOwnProperty('widgets')) {
if(!widgets) {
return
}
(widgets).forEach((item, index) => {
if (item.hasOwnProperty('widgets') && item.widgets !== null) {
const _level_ = level + 1;
const path = `/civweb/${guid('web_console')}`;
const subKey = guid('panda');
const alias = DEFAULT_APPLICATION;
ret.push({
name: item.label,
level: _level_,
path,
component: 'BasicLayout',
parent,
alias: alias,
routes: generRotes(
item.widgets,
Object.assign({}, item, { path, key: subKey }),
......@@ -57,11 +64,11 @@ const generRotes = (widgets, parent, level = 0) => {
),
extData: {
...item,
icon: (item && item.icon.replace(/\s*/g, '')) || '_',
icon: (item && item.icon && item.icon !== null && item.icon.replace(/\s*/g, '')) || '_',
},
});
} else {
const baseURL = item.product || 'civweb4';
const baseURL = item.product || DEFAULT_APPLICATION;
let url = item.hasOwnProperty('url') ? `/${baseURL}/${item.url.replace(/\s*/g, '')}`
: !item.hasOwnProperty('url')
? guid('web_console')
......@@ -85,7 +92,7 @@ const generRotes = (widgets, parent, level = 0) => {
target: isURL(url) ? '_blank' : '',
key: guid('panda'),
hideInMenu: l > 3,
application: item.product,
alias: item.product || DEFAULT_APPLICATION,
extData: {
...item,
icon: (item && item.icon.replace(/\s*/g, '')) || '_',
......
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