fork from bc4552c5a8
This commit is contained in:
326
client/components/Header/Header.js
Normal file
326
client/components/Header/Header.js
Normal file
@@ -0,0 +1,326 @@
|
||||
import './Header.scss';
|
||||
import React, { PureComponent as Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Icon, Layout, Menu, Dropdown, message, Tooltip, Popover, Tag } from 'antd';
|
||||
import { checkLoginState, logoutActions, loginTypeAction } from '../../reducer/modules/user';
|
||||
import { changeMenuItem } from '../../reducer/modules/menu';
|
||||
import { withRouter } from 'react-router';
|
||||
import Srch from './Search/Search';
|
||||
const { Header } = Layout;
|
||||
import LogoSVG from '../LogoSVG/index.js';
|
||||
import Breadcrumb from '../Breadcrumb/Breadcrumb.js';
|
||||
import GuideBtns from '../GuideBtns/GuideBtns.js';
|
||||
const plugin = require('client/plugin.js');
|
||||
|
||||
let HeaderMenu = {
|
||||
user: {
|
||||
path: '/user/profile',
|
||||
name: '个人中心',
|
||||
icon: 'user',
|
||||
adminFlag: false
|
||||
},
|
||||
solution: {
|
||||
path: '/user/list',
|
||||
name: '用户管理',
|
||||
icon: 'solution',
|
||||
adminFlag: true
|
||||
}
|
||||
};
|
||||
|
||||
plugin.emitHook('header_menu', HeaderMenu);
|
||||
|
||||
const MenuUser = props => (
|
||||
<Menu theme="dark" className="user-menu">
|
||||
{Object.keys(HeaderMenu).map(key => {
|
||||
let item = HeaderMenu[key];
|
||||
const isAdmin = props.role === 'admin';
|
||||
if (item.adminFlag && !isAdmin) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Menu.Item key={key}>
|
||||
{item.name === '个人中心' ? (
|
||||
<Link to={item.path + `/${props.uid}`}>
|
||||
<Icon type={item.icon} />
|
||||
{item.name}
|
||||
</Link>
|
||||
) : (
|
||||
<Link to={item.path}>
|
||||
<Icon type={item.icon} />
|
||||
{item.name}
|
||||
</Link>
|
||||
)}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
<Menu.Item key="9">
|
||||
<a onClick={props.logout}>
|
||||
<Icon type="logout" />退出
|
||||
</a>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const tipFollow = (
|
||||
<div className="title-container">
|
||||
<h3 className="title">
|
||||
<Icon type="star" /> 关注
|
||||
</h3>
|
||||
<p>这里是你的专属收藏夹,便于你找到自己的项目</p>
|
||||
</div>
|
||||
);
|
||||
const tipAdd = (
|
||||
<div className="title-container">
|
||||
<h3 className="title">
|
||||
<Icon type="plus-circle" /> 新建项目
|
||||
</h3>
|
||||
<p>在任何页面都可以快速新建项目</p>
|
||||
</div>
|
||||
);
|
||||
const tipDoc = (
|
||||
<div className="title-container">
|
||||
<h3 className="title">
|
||||
使用文档 <Tag color="orange">推荐!</Tag>
|
||||
</h3>
|
||||
<p>
|
||||
初次使用 YApi,强烈建议你阅读{' '}
|
||||
<a target="_blank" href="https://hellosean1025.github.io/yapi/" rel="noopener noreferrer">
|
||||
使用文档
|
||||
</a>
|
||||
,我们为你提供了通俗易懂的快速入门教程,更有详细的使用说明,欢迎阅读!{' '}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
MenuUser.propTypes = {
|
||||
user: PropTypes.string,
|
||||
msg: PropTypes.string,
|
||||
role: PropTypes.string,
|
||||
uid: PropTypes.number,
|
||||
relieveLink: PropTypes.func,
|
||||
logout: PropTypes.func
|
||||
};
|
||||
|
||||
const ToolUser = props => {
|
||||
let imageUrl = props.imageUrl ? props.imageUrl : `/api/user/avatar?uid=${props.uid}`;
|
||||
return (
|
||||
<ul>
|
||||
<li className="toolbar-li item-search">
|
||||
<Srch groupList={props.groupList} />
|
||||
</li>
|
||||
<Popover
|
||||
overlayClassName="popover-index"
|
||||
content={<GuideBtns />}
|
||||
title={tipFollow}
|
||||
placement="bottomRight"
|
||||
arrowPointAtCenter
|
||||
visible={props.studyTip === 1 && !props.study}
|
||||
>
|
||||
<Tooltip placement="bottom" title={'我的关注'}>
|
||||
<li className="toolbar-li">
|
||||
<Link to="/follow">
|
||||
<Icon className="dropdown-link" style={{ fontSize: 16 }} type="star" />
|
||||
</Link>
|
||||
</li>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
<Popover
|
||||
overlayClassName="popover-index"
|
||||
content={<GuideBtns />}
|
||||
title={tipAdd}
|
||||
placement="bottomRight"
|
||||
arrowPointAtCenter
|
||||
visible={props.studyTip === 2 && !props.study}
|
||||
>
|
||||
<Tooltip placement="bottom" title={'新建项目'}>
|
||||
<li className="toolbar-li">
|
||||
<Link to="/add-project">
|
||||
<Icon className="dropdown-link" style={{ fontSize: 16 }} type="plus-circle" />
|
||||
</Link>
|
||||
</li>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
<Popover
|
||||
overlayClassName="popover-index"
|
||||
content={<GuideBtns isLast={true} />}
|
||||
title={tipDoc}
|
||||
placement="bottomRight"
|
||||
arrowPointAtCenter
|
||||
visible={props.studyTip === 3 && !props.study}
|
||||
>
|
||||
<Tooltip placement="bottom" title={'使用文档'}>
|
||||
<li className="toolbar-li">
|
||||
<a target="_blank" href="https://hellosean1025.github.io/yapi" rel="noopener noreferrer">
|
||||
<Icon className="dropdown-link" style={{ fontSize: 16 }} type="question-circle" />
|
||||
</a>
|
||||
</li>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
<li className="toolbar-li">
|
||||
<Dropdown
|
||||
placement="bottomRight"
|
||||
trigger={['click']}
|
||||
overlay={
|
||||
<MenuUser
|
||||
user={props.user}
|
||||
msg={props.msg}
|
||||
uid={props.uid}
|
||||
role={props.role}
|
||||
relieveLink={props.relieveLink}
|
||||
logout={props.logout}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<a className="dropdown-link">
|
||||
<span className="avatar-image">
|
||||
<img src={imageUrl} />
|
||||
</span>
|
||||
{/*props.imageUrl? <Avatar src={props.imageUrl} />: <Avatar src={`/api/user/avatar?uid=${props.uid}`} />*/}
|
||||
<span className="name">
|
||||
<Icon type="down" />
|
||||
</span>
|
||||
</a>
|
||||
</Dropdown>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
ToolUser.propTypes = {
|
||||
user: PropTypes.string,
|
||||
msg: PropTypes.string,
|
||||
role: PropTypes.string,
|
||||
uid: PropTypes.number,
|
||||
relieveLink: PropTypes.func,
|
||||
logout: PropTypes.func,
|
||||
groupList: PropTypes.array,
|
||||
studyTip: PropTypes.number,
|
||||
study: PropTypes.bool,
|
||||
imageUrl: PropTypes.any
|
||||
};
|
||||
|
||||
@connect(
|
||||
state => {
|
||||
return {
|
||||
user: state.user.userName,
|
||||
uid: state.user.uid,
|
||||
msg: null,
|
||||
role: state.user.role,
|
||||
login: state.user.isLogin,
|
||||
studyTip: state.user.studyTip,
|
||||
study: state.user.study,
|
||||
imageUrl: state.user.imageUrl
|
||||
};
|
||||
},
|
||||
{
|
||||
loginTypeAction,
|
||||
logoutActions,
|
||||
checkLoginState,
|
||||
changeMenuItem
|
||||
}
|
||||
)
|
||||
@withRouter
|
||||
export default class HeaderCom extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
router: PropTypes.object,
|
||||
user: PropTypes.string,
|
||||
msg: PropTypes.string,
|
||||
uid: PropTypes.number,
|
||||
role: PropTypes.string,
|
||||
login: PropTypes.bool,
|
||||
relieveLink: PropTypes.func,
|
||||
logoutActions: PropTypes.func,
|
||||
checkLoginState: PropTypes.func,
|
||||
loginTypeAction: PropTypes.func,
|
||||
changeMenuItem: PropTypes.func,
|
||||
history: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
study: PropTypes.bool,
|
||||
studyTip: PropTypes.number,
|
||||
imageUrl: PropTypes.any
|
||||
};
|
||||
linkTo = e => {
|
||||
if (e.key != '/doc') {
|
||||
this.props.changeMenuItem(e.key);
|
||||
if (!this.props.login) {
|
||||
message.info('请先登录', 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
relieveLink = () => {
|
||||
this.props.changeMenuItem('');
|
||||
};
|
||||
logout = e => {
|
||||
e.preventDefault();
|
||||
this.props
|
||||
.logoutActions()
|
||||
.then(res => {
|
||||
if (res.payload.data.errcode == 0) {
|
||||
this.props.history.push('/');
|
||||
this.props.changeMenuItem('/');
|
||||
message.success('退出成功! ');
|
||||
} else {
|
||||
message.error(res.payload.data.errmsg);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
message.error(err);
|
||||
});
|
||||
};
|
||||
handleLogin = e => {
|
||||
e.preventDefault();
|
||||
this.props.loginTypeAction('1');
|
||||
};
|
||||
handleReg = e => {
|
||||
e.preventDefault();
|
||||
this.props.loginTypeAction('2');
|
||||
};
|
||||
checkLoginState = () => {
|
||||
this.props.checkLoginState
|
||||
.then(res => {
|
||||
if (res.payload.data.errcode !== 0) {
|
||||
this.props.history.push('/');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { login, user, msg, uid, role, studyTip, study, imageUrl } = this.props;
|
||||
return (
|
||||
<Header className="header-box m-header">
|
||||
<div className="content g-row">
|
||||
<Link onClick={this.relieveLink} to="/group" className="logo">
|
||||
<div className="href">
|
||||
<span className="img">
|
||||
<LogoSVG length="32px" />
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Breadcrumb />
|
||||
<div
|
||||
className="user-toolbar"
|
||||
style={{ position: 'relative', zIndex: this.props.studyTip > 0 ? 3 : 1 }}
|
||||
>
|
||||
{login ? (
|
||||
<ToolUser
|
||||
{...{ studyTip, study, user, msg, uid, role, imageUrl }}
|
||||
relieveLink={this.relieveLink}
|
||||
logout={this.logout}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Header>
|
||||
);
|
||||
}
|
||||
}
|
||||
150
client/components/Header/Header.scss
Normal file
150
client/components/Header/Header.scss
Normal file
@@ -0,0 +1,150 @@
|
||||
@import '../../styles/mixin.scss';
|
||||
|
||||
.nav-tooltip {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.user-menu.ant-menu.ant-menu-dark .ant-menu-item-selected {
|
||||
background: #32363a;
|
||||
color: rgba(255, 255, 255, 0.67);
|
||||
}
|
||||
.user-menu.ant-menu-dark .ant-menu-item-selected > a {
|
||||
color: rgba(255, 255, 255, 0.67);
|
||||
}
|
||||
|
||||
/* .header-box.css */
|
||||
.header-box {
|
||||
height: .56rem;
|
||||
line-height: .56rem;
|
||||
padding: 0;
|
||||
.logo {
|
||||
position: relative;
|
||||
float: left;
|
||||
line-height: .56rem;
|
||||
height: .56rem;
|
||||
width: 56px;
|
||||
border-right: 1px solid #55616d;
|
||||
border-left: 1px solid #55616d;
|
||||
background-color: inherit;
|
||||
transition: all .2s;
|
||||
&:hover{
|
||||
background-color: #2395f1;
|
||||
}
|
||||
.href {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
.logo-name {
|
||||
color: $color-white;
|
||||
font-size: .24rem;
|
||||
font-weight: 300;
|
||||
margin-left: .38rem;
|
||||
}
|
||||
.img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-16px,-17px);
|
||||
}
|
||||
.ui-badge {
|
||||
position: absolute;
|
||||
right: -18px;
|
||||
top: 6px;
|
||||
width: 30px;
|
||||
height: 21px;
|
||||
background-size: 236px 21px;
|
||||
background-repeat: no-repeat;
|
||||
background-image: none;
|
||||
}
|
||||
// &:before, &:after {
|
||||
// content: '';
|
||||
// display: block;
|
||||
// width: 2px;
|
||||
// height: .56rem;
|
||||
// background-color: #222;
|
||||
// border-left: 1px solid #575D67;
|
||||
// position: relative;
|
||||
// top: 0;
|
||||
// }
|
||||
// &:before {
|
||||
// float: left;
|
||||
// left: -.08rem;
|
||||
// }
|
||||
// &:after {
|
||||
// float: right;
|
||||
// right: -.27rem;
|
||||
// }
|
||||
}
|
||||
|
||||
.nav-toolbar {
|
||||
font-size: .15rem;
|
||||
float: left;
|
||||
|
||||
}
|
||||
.user-menu{
|
||||
margin-top: 20px;
|
||||
}
|
||||
.user-toolbar{
|
||||
float: right;
|
||||
height: .54rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.item-search {
|
||||
width: 2rem;
|
||||
}
|
||||
.toolbar-li{
|
||||
float: left;
|
||||
font-size: .14rem;
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
margin-left: .16rem;
|
||||
transition: color .2s;
|
||||
& a {
|
||||
color: #ccc;
|
||||
}
|
||||
.dropdown-link {
|
||||
color: #ccc;
|
||||
transition: color .2s;
|
||||
// .ant-avatar-image{
|
||||
// margin-bottom: -10px;
|
||||
// }
|
||||
// .ant-avatar > img{
|
||||
// height: auto;
|
||||
// }
|
||||
.avatar-image{
|
||||
margin-bottom: -10px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
background: #ccc;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
line-height: 0;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.avatar-image > img{
|
||||
height: auto;
|
||||
width:100%;
|
||||
display: bloack;
|
||||
}
|
||||
|
||||
}
|
||||
.anticon.active {
|
||||
color: #2395f1;
|
||||
}
|
||||
&:hover{
|
||||
.dropdown-link {
|
||||
color: #2395f1;
|
||||
}
|
||||
}
|
||||
.name {
|
||||
margin-left: .08rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
158
client/components/Header/Search/Search.js
Normal file
158
client/components/Header/Search/Search.js
Normal file
@@ -0,0 +1,158 @@
|
||||
import React, { PureComponent as Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Icon, Input, AutoComplete } from 'antd';
|
||||
import './Search.scss';
|
||||
import { withRouter } from 'react-router';
|
||||
import axios from 'axios';
|
||||
import { setCurrGroup, fetchGroupMsg } from '../../../reducer/modules/group';
|
||||
import { changeMenuItem } from '../../../reducer/modules/menu';
|
||||
|
||||
import { fetchInterfaceListMenu } from '../../../reducer/modules/interface';
|
||||
const Option = AutoComplete.Option;
|
||||
|
||||
@connect(
|
||||
state => ({
|
||||
groupList: state.group.groupList,
|
||||
projectList: state.project.projectList
|
||||
}),
|
||||
{
|
||||
setCurrGroup,
|
||||
changeMenuItem,
|
||||
fetchGroupMsg,
|
||||
fetchInterfaceListMenu
|
||||
}
|
||||
)
|
||||
@withRouter
|
||||
export default class Srch extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dataSource: []
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
groupList: PropTypes.array,
|
||||
projectList: PropTypes.array,
|
||||
router: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
setCurrGroup: PropTypes.func,
|
||||
changeMenuItem: PropTypes.func,
|
||||
fetchInterfaceListMenu: PropTypes.func,
|
||||
fetchGroupMsg: PropTypes.func
|
||||
};
|
||||
|
||||
onSelect = async (value, option) => {
|
||||
if (option.props.type === '分组') {
|
||||
this.props.changeMenuItem('/group');
|
||||
this.props.history.push('/group/' + option.props['id']);
|
||||
this.props.setCurrGroup({ group_name: value, _id: option.props['id'] - 0 });
|
||||
} else if (option.props.type === '项目') {
|
||||
await this.props.fetchGroupMsg(option.props['groupId']);
|
||||
this.props.history.push('/project/' + option.props['id']);
|
||||
} else if (option.props.type === '接口') {
|
||||
await this.props.fetchInterfaceListMenu(option.props['projectId']);
|
||||
this.props.history.push(
|
||||
'/project/' + option.props['projectId'] + '/interface/api/' + option.props['id']
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
handleSearch = value => {
|
||||
axios
|
||||
.get('/api/project/search?q=' + value)
|
||||
.then(res => {
|
||||
if (res.data && res.data.errcode === 0) {
|
||||
const dataSource = [];
|
||||
for (let title in res.data.data) {
|
||||
res.data.data[title].map(item => {
|
||||
switch (title) {
|
||||
case 'group':
|
||||
dataSource.push(
|
||||
<Option
|
||||
key={`分组${item._id}`}
|
||||
type="分组"
|
||||
value={`${item.groupName}`}
|
||||
id={`${item._id}`}
|
||||
>
|
||||
{`分组: ${item.groupName}`}
|
||||
</Option>
|
||||
);
|
||||
break;
|
||||
case 'project':
|
||||
dataSource.push(
|
||||
<Option
|
||||
key={`项目${item._id}`}
|
||||
type="项目"
|
||||
id={`${item._id}`}
|
||||
groupId={`${item.groupId}`}
|
||||
>
|
||||
{`项目: ${item.name}`}
|
||||
</Option>
|
||||
);
|
||||
break;
|
||||
case 'interface':
|
||||
dataSource.push(
|
||||
<Option
|
||||
key={`接口${item._id}`}
|
||||
type="接口"
|
||||
id={`${item._id}`}
|
||||
projectId={`${item.projectId}`}
|
||||
>
|
||||
{`接口: ${item.title}`}
|
||||
</Option>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
dataSource: dataSource
|
||||
});
|
||||
} else {
|
||||
console.log('查询项目或分组失败');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
// getDataSource(groupList){
|
||||
// const groupArr =[];
|
||||
// groupList.forEach(item =>{
|
||||
// groupArr.push("group: "+ item["group_name"]);
|
||||
// })
|
||||
// return groupArr;
|
||||
// }
|
||||
|
||||
render() {
|
||||
const { dataSource } = this.state;
|
||||
|
||||
return (
|
||||
<div className="search-wrapper">
|
||||
<AutoComplete
|
||||
className="search-dropdown"
|
||||
dataSource={dataSource}
|
||||
style={{ width: '100%' }}
|
||||
defaultActiveFirstOption={false}
|
||||
onSelect={this.onSelect}
|
||||
onSearch={this.handleSearch}
|
||||
// filterOption={(inputValue, option) =>
|
||||
// option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
// }
|
||||
>
|
||||
<Input
|
||||
prefix={<Icon type="search" className="srch-icon" />}
|
||||
placeholder="搜索分组/项目/接口"
|
||||
className="search-input"
|
||||
/>
|
||||
</AutoComplete>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
10
client/components/Header/Search/Search.scss
Normal file
10
client/components/Header/Search/Search.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
$color-grey:#979DA7;
|
||||
|
||||
.search-wrapper{
|
||||
cursor: auto;
|
||||
.search-input{
|
||||
width: 2rem;
|
||||
}
|
||||
.srch-icon{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user