mirror of
https://github.com/zuiidea/antd-admin
synced 2026-10-30 13:23:49 +00:00
Added component Scrollbar, Added avatar
This commit is contained in:
@@ -12,6 +12,26 @@ export function queryArray(array, key, value) {
|
||||
return array.filter(_ => _[key] === value)
|
||||
}
|
||||
|
||||
export function randomNumber(min, max) {
|
||||
return Math.floor(Math.random() * (max - min) + min)
|
||||
}
|
||||
|
||||
export function randomAvatar() {
|
||||
const avatarList = [
|
||||
'https://randomuser.me/api/portraits/men/32.jpg',
|
||||
'https://images.pexels.com/photos/415829/pexels-photo-415829.jpeg?h=350&auto=compress&cs=tinysrgb',
|
||||
'https://d3iw72m71ie81c.cloudfront.net/female-17.jpg',
|
||||
'https://randomuser.me/api/portraits/men/35.jpg',
|
||||
'https://pbs.twimg.com/profile_images/835224725815246848/jdMBCxHS.jpg',
|
||||
'https://pbs.twimg.com/profile_images/1010888664732200962/OmFg5TO0.jpg',
|
||||
'https://pbs.twimg.com/profile_images/584098247641300992/N25WgvW_.png',
|
||||
'https://d3iw72m71ie81c.cloudfront.net/male-5.jpg',
|
||||
'https://images.pexels.com/photos/413723/pexels-photo-413723.jpeg?h=350&auto=compress&cs=tinysrgb',
|
||||
'https://images.pexels.com/photos/61100/pexels-photo-61100.jpeg?h=350&auto=compress&cs=tinysrgb',
|
||||
]
|
||||
return avatarList[randomNumber(0, avatarList.length - 1)]
|
||||
}
|
||||
|
||||
export const Constant = {
|
||||
ApiPrefix: '/api/v1',
|
||||
NotFound: {
|
||||
|
||||
+54
-31
@@ -1,4 +1,4 @@
|
||||
import { Mock, Constant, qs } from './_utils'
|
||||
import { Mock, Constant, qs, randomAvatar } from './_utils'
|
||||
|
||||
const { ApiPrefix } = Constant
|
||||
|
||||
@@ -14,14 +14,19 @@ let usersListData = Mock.mock({
|
||||
isMale: '@boolean',
|
||||
email: '@email',
|
||||
createTime: '@datetime',
|
||||
avatar () {
|
||||
return Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', this.nickName.substr(0, 1))
|
||||
avatar() {
|
||||
return Mock.Random.image(
|
||||
'100x100',
|
||||
Mock.Random.color(),
|
||||
'#757575',
|
||||
'png',
|
||||
this.nickName.substr(0, 1)
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
let database = usersListData.data
|
||||
|
||||
const EnumRoleType = {
|
||||
@@ -49,16 +54,21 @@ const adminUsers = [
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
permissions: userPermission.ADMIN,
|
||||
}, {
|
||||
avatar: randomAvatar(),
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
username: 'guest',
|
||||
password: 'guest',
|
||||
permissions: userPermission.DEFAULT,
|
||||
}, {
|
||||
avatar: randomAvatar(),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: '吴彦祖',
|
||||
password: '123456',
|
||||
permissions: userPermission.DEVELOPER,
|
||||
avatar: randomAvatar(),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -87,34 +97,37 @@ const NOTFOUND = {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
[`POST ${ApiPrefix}/user/login`] (req, res) {
|
||||
[`POST ${ApiPrefix}/user/login`](req, res) {
|
||||
const { username, password } = req.body
|
||||
const user = adminUsers.filter(item => item.username === username)
|
||||
|
||||
if (user.length > 0 && user[0].password === password) {
|
||||
const now = new Date()
|
||||
now.setDate(now.getDate() + 1)
|
||||
res.cookie('token', JSON.stringify({ id: user[0].id, deadline: now.getTime() }), {
|
||||
maxAge: 900000,
|
||||
httpOnly: true,
|
||||
})
|
||||
res.cookie(
|
||||
'token',
|
||||
JSON.stringify({ id: user[0].id, deadline: now.getTime() }),
|
||||
{
|
||||
maxAge: 900000,
|
||||
httpOnly: true,
|
||||
}
|
||||
)
|
||||
res.json({ success: true, message: 'Ok' })
|
||||
} else {
|
||||
res.status(400).end()
|
||||
}
|
||||
},
|
||||
|
||||
[`GET ${ApiPrefix}/user/logout`] (req, res) {
|
||||
[`GET ${ApiPrefix}/user/logout`](req, res) {
|
||||
res.clearCookie('token')
|
||||
res.status(200).end()
|
||||
},
|
||||
|
||||
[`GET ${ApiPrefix}/user`] (req, res) {
|
||||
[`GET ${ApiPrefix}/user`](req, res) {
|
||||
const cookie = req.headers.cookie || ''
|
||||
const cookies = qs.parse(cookie.replace(/\s/g, ''), { delimiter: ';' })
|
||||
const response = {}
|
||||
const user = {}
|
||||
let user = {}
|
||||
if (!cookies.token) {
|
||||
res.status(200).send({ message: 'Not Login' })
|
||||
return
|
||||
@@ -124,18 +137,17 @@ module.exports = {
|
||||
response.success = token.deadline > new Date().getTime()
|
||||
}
|
||||
if (response.success) {
|
||||
const userItem = adminUsers.filter(_ => _.id === token.id)
|
||||
if (userItem.length > 0) {
|
||||
user.permissions = userItem[0].permissions
|
||||
user.username = userItem[0].username
|
||||
user.id = userItem[0].id
|
||||
const userItem = adminUsers.find(_ => _.id === token.id)
|
||||
if (userItem) {
|
||||
const { password, ...other } = userItem
|
||||
user = other
|
||||
}
|
||||
}
|
||||
response.user = user
|
||||
res.json(response)
|
||||
},
|
||||
|
||||
[`GET ${ApiPrefix}/users`] (req, res) {
|
||||
[`GET ${ApiPrefix}/users`](req, res) {
|
||||
const { query } = req
|
||||
let { pageSize, page, ...other } = query
|
||||
pageSize = pageSize || 10
|
||||
@@ -144,7 +156,7 @@ module.exports = {
|
||||
let newData = database
|
||||
for (let key in other) {
|
||||
if ({}.hasOwnProperty.call(other, key)) {
|
||||
newData = newData.filter((item) => {
|
||||
newData = newData.filter(item => {
|
||||
if ({}.hasOwnProperty.call(item, key)) {
|
||||
if (key === 'address') {
|
||||
return other[key].every(iitem => item[key].indexOf(iitem) > -1)
|
||||
@@ -158,7 +170,11 @@ module.exports = {
|
||||
}
|
||||
return true
|
||||
}
|
||||
return String(item[key]).trim().indexOf(decodeURI(other[key]).trim()) > -1
|
||||
return (
|
||||
String(item[key])
|
||||
.trim()
|
||||
.indexOf(decodeURI(other[key]).trim()) > -1
|
||||
)
|
||||
}
|
||||
return true
|
||||
})
|
||||
@@ -171,17 +187,24 @@ module.exports = {
|
||||
})
|
||||
},
|
||||
|
||||
[`POST ${ApiPrefix}/users/delete`] (req, res) {
|
||||
[`POST ${ApiPrefix}/users/delete`](req, res) {
|
||||
const { ids } = req.body
|
||||
database = database.filter(item => !ids.some(_ => _ === item.id))
|
||||
res.status(204).end()
|
||||
},
|
||||
|
||||
|
||||
[`POST ${ApiPrefix}/user`] (req, res) {
|
||||
[`POST ${ApiPrefix}/user`](req, res) {
|
||||
const newData = req.body
|
||||
newData.createTime = Mock.mock('@now')
|
||||
newData.avatar = newData.avatar || Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', newData.nickName.substr(0, 1))
|
||||
newData.avatar =
|
||||
newData.avatar ||
|
||||
Mock.Random.image(
|
||||
'100x100',
|
||||
Mock.Random.color(),
|
||||
'#757575',
|
||||
'png',
|
||||
newData.nickName.substr(0, 1)
|
||||
)
|
||||
newData.id = Mock.mock('@id')
|
||||
|
||||
database.unshift(newData)
|
||||
@@ -189,7 +212,7 @@ module.exports = {
|
||||
res.status(200).end()
|
||||
},
|
||||
|
||||
[`GET ${ApiPrefix}/user/:id`] (req, res) {
|
||||
[`GET ${ApiPrefix}/user/:id`](req, res) {
|
||||
const { id } = req.params
|
||||
const data = queryArray(database, id, 'id')
|
||||
if (data) {
|
||||
@@ -199,7 +222,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
[`DELETE ${ApiPrefix}/user/:id`] (req, res) {
|
||||
[`DELETE ${ApiPrefix}/user/:id`](req, res) {
|
||||
const { id } = req.params
|
||||
const data = queryArray(database, id, 'id')
|
||||
if (data) {
|
||||
@@ -210,12 +233,12 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
[`PATCH ${ApiPrefix}/user/:id`] (req, res) {
|
||||
[`PATCH ${ApiPrefix}/user/:id`](req, res) {
|
||||
const { id } = req.params
|
||||
const editItem = req.body
|
||||
let isExist = false
|
||||
|
||||
database = database.map((item) => {
|
||||
database = database.map(item => {
|
||||
if (item.id === id) {
|
||||
isExist = true
|
||||
return Object.assign({}, item, editItem)
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"react-draft-wysiwyg": "^1.12.13",
|
||||
"react-helmet": "^5.2.0",
|
||||
"react-highcharts": "^16.0.2",
|
||||
"react-perfect-scrollbar": "^1.2.2",
|
||||
"recharts": "^1.3.0",
|
||||
"store": "^2.0.12"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { PureComponent, Fragment } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Menu, Icon, Layout } from 'antd'
|
||||
import { Menu, Icon, Layout, Avatar } from 'antd'
|
||||
import { Trans } from '@lingui/react'
|
||||
import classnames from 'classnames'
|
||||
import styles from './Header.less'
|
||||
@@ -12,7 +12,7 @@ class Header extends PureComponent {
|
||||
e.key === 'SignOut' && this.props.onSignOut()
|
||||
}
|
||||
render() {
|
||||
const { user, collapsed, onCollapseChange } = this.props
|
||||
const { username, avatar, collapsed, onCollapseChange } = this.props
|
||||
|
||||
return (
|
||||
<Layout.Header className={styles.header}>
|
||||
@@ -33,8 +33,11 @@ class Header extends PureComponent {
|
||||
<SubMenu
|
||||
title={
|
||||
<Fragment>
|
||||
<Icon type="user" />
|
||||
{user.username}
|
||||
<span style={{ color: '#999', marginRight: 4 }}>
|
||||
<Trans>Hi,</Trans>
|
||||
</span>
|
||||
<span>{username}</span>
|
||||
<Avatar style={{ marginLeft: 8 }} src={avatar} />
|
||||
</Fragment>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
padding: 0;
|
||||
:global {
|
||||
.ant-menu-submenu-title {
|
||||
height: 56px;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.ant-menu-horizontal {
|
||||
line-height: 56px;
|
||||
line-height: 72px;
|
||||
|
||||
& > .ant-menu-submenu:hover {
|
||||
color: #1890ff;
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
.ant-menu {
|
||||
border-bottom: none;
|
||||
height: 56px;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.ant-menu-horizontal > .ant-menu-submenu {
|
||||
@@ -46,7 +46,7 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 56px;
|
||||
height: 72px;
|
||||
z-index: 9;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
@@ -56,9 +56,9 @@
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
line-height: 72px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Icon, Switch, Layout } from 'antd'
|
||||
import { withI18n, Trans } from '@lingui/react'
|
||||
import ScrollBar from '../ScrollBar'
|
||||
import { config } from 'utils'
|
||||
import SiderMenu from './Menu'
|
||||
import styles from './Sider.less'
|
||||
@@ -21,25 +22,32 @@ class Sider extends PureComponent {
|
||||
|
||||
return (
|
||||
<Layout.Sider
|
||||
width={256}
|
||||
theme={theme}
|
||||
breakpoint="lg"
|
||||
trigger={null}
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
onBreakpoint={isMobile ? null : onCollapseChange}
|
||||
className={styles.sider}
|
||||
>
|
||||
<div className={styles.logoContainer}>
|
||||
<img alt="logo" src={config.logoPath} />
|
||||
{collapsed ? null : <h1>{config.siteName}</h1>}
|
||||
<div className={styles.brand}>
|
||||
<div className={styles.logo}>
|
||||
<img alt="logo" src={config.logoPath} />
|
||||
{collapsed ? null : <h1>{config.siteName}</h1>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.menuContainer}>
|
||||
<SiderMenu
|
||||
menus={menus}
|
||||
theme={theme}
|
||||
isMobile={isMobile}
|
||||
collapsed={collapsed}
|
||||
onCollapseChange={onCollapseChange}
|
||||
/>
|
||||
<ScrollBar>
|
||||
<SiderMenu
|
||||
menus={menus}
|
||||
theme={theme}
|
||||
isMobile={isMobile}
|
||||
collapsed={collapsed}
|
||||
onCollapseChange={onCollapseChange}
|
||||
/>
|
||||
</ScrollBar>
|
||||
</div>
|
||||
{collapsed ? null : (
|
||||
<div className={styles.switchTheme}>
|
||||
|
||||
@@ -1,39 +1,59 @@
|
||||
@import '~themes/vars.less';
|
||||
|
||||
.logoContainer {
|
||||
height: 96px;
|
||||
.sider {
|
||||
box-shadow: fade(@primary-color, 10%) 0 0 28px 0;
|
||||
z-index: 10;
|
||||
:global {
|
||||
.ant-layout-sider-children {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.brand {
|
||||
z-index: 1;
|
||||
height: 72px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
box-shadow: 0 1px 9px -3px rgba(0, 0, 0, 0.2);
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
img {
|
||||
width: 36px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
vertical-align: text-bottom;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
display: inline-block;
|
||||
font-weight: 700;
|
||||
color: @primary-color;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 0;
|
||||
.text-gradient();
|
||||
h1 {
|
||||
vertical-align: text-bottom;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
display: inline-block;
|
||||
font-weight: 700;
|
||||
color: @primary-color;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 0;
|
||||
.text-gradient();
|
||||
|
||||
:local {
|
||||
animation: fadeRightIn 300ms @ease-in-out;
|
||||
animation-fill-mode: both;
|
||||
:local {
|
||||
animation: fadeRightIn 300ms @ease-in-out;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menuContainer {
|
||||
height: ~'calc(100vh - 144px)';
|
||||
height: ~'calc(100vh - 120px)';
|
||||
overflow-x: hidden;
|
||||
border-right: none;
|
||||
flex: 1;
|
||||
padding: 24px 0;
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: transparent;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import ScrollBar from 'react-perfect-scrollbar'
|
||||
import 'react-perfect-scrollbar/dist/css/styles.css'
|
||||
import './index.less'
|
||||
|
||||
export default ScrollBar
|
||||
@@ -0,0 +1,26 @@
|
||||
:global {
|
||||
.ps--active-x > .ps__rail-x,
|
||||
.ps--active-y > .ps__rail-y {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ps__rail-x:hover > .ps__thumb-x,
|
||||
.ps__rail-x:focus > .ps__thumb-x {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.ps__rail-y:hover > .ps__thumb-y,
|
||||
.ps__rail-y:focus > .ps__thumb-y {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.ps__thumb-y {
|
||||
width: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.ps__thumb-x {
|
||||
height: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ import Editor from './Editor'
|
||||
import FilterItem from './FilterItem'
|
||||
import DropOption from './DropOption'
|
||||
import Loader from './Loader'
|
||||
import ScrollBar from './ScrollBar'
|
||||
import * as MyLayout from './Layout/index.js'
|
||||
import Page from './Page'
|
||||
|
||||
export { MyLayout, Editor, FilterItem, DropOption, Loader, Page }
|
||||
export { MyLayout, Editor, FilterItem, DropOption, Loader, Page, ScrollBar }
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { PureComponent, Fragment } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import withRouter from 'umi/withRouter'
|
||||
import { connect } from 'dva'
|
||||
import { MyLayout } from 'components'
|
||||
import { MyLayout, ScrollBar } from 'components'
|
||||
import { BackTop, Layout, Drawer } from 'antd'
|
||||
import { GlobalFooter } from 'ant-design-pro'
|
||||
import { enquireScreen, unenquireScreen } from 'enquire-js'
|
||||
@@ -64,10 +64,11 @@ class PrimaryLayout extends PureComponent {
|
||||
const menus = routeList.filter(_ => _.menuParentId !== '-1')
|
||||
|
||||
const headerProps = {
|
||||
user,
|
||||
menus,
|
||||
collapsed,
|
||||
onCollapseChange,
|
||||
avatar: user.avatar,
|
||||
username: user.username,
|
||||
onSignOut() {
|
||||
dispatch({ type: 'app/signOut' })
|
||||
},
|
||||
@@ -109,16 +110,18 @@ class PrimaryLayout extends PureComponent {
|
||||
<Sider {...siderProps} />
|
||||
)}
|
||||
<div className={styles.container} id="primaryLayout">
|
||||
<Header {...headerProps} />
|
||||
<Content className={styles.content}>
|
||||
<Bread routeList={routeList} />
|
||||
{hasPermission ? children : <Error />}
|
||||
</Content>
|
||||
<BackTop
|
||||
className={styles.backTop}
|
||||
target={() => document.getElementById('primaryLayout')}
|
||||
/>
|
||||
<GlobalFooter copyright={config.copyright} />
|
||||
<ScrollBar>
|
||||
<Header {...headerProps} />
|
||||
<Content className={styles.content}>
|
||||
<Bread routeList={routeList} />
|
||||
{hasPermission ? children : <Error />}
|
||||
</Content>
|
||||
<BackTop
|
||||
className={styles.backTop}
|
||||
target={() => document.getElementById('primaryLayout')}
|
||||
/>
|
||||
<GlobalFooter copyright={config.copyright} />
|
||||
</ScrollBar>
|
||||
</div>
|
||||
</Layout>
|
||||
</Fragment>
|
||||
|
||||
@@ -6,15 +6,6 @@ body {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-breadcrumb {
|
||||
& > span {
|
||||
|
||||
Reference in New Issue
Block a user