This commit is contained in:
2024-03-01 20:28:14 +08:00
commit 076c21dc36
491 changed files with 84482 additions and 0 deletions

View 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>
);
}
}

View 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;
}
}
}
}

View 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>
);
}
}

View File

@@ -0,0 +1,10 @@
$color-grey:#979DA7;
.search-wrapper{
cursor: auto;
.search-input{
width: 2rem;
}
.srch-icon{
}
}