为什么都没了

This commit is contained in:
路 范
2021-09-24 12:59:34 +08:00
parent a975b3bdee
commit a66921f0f4
962 changed files with 252792 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
import React, { Component } from 'react'
export default class index extends Component {
render() {
return (
<div>
404
</div>
)
}
}

View File

@@ -0,0 +1,235 @@
import React, { Component } from 'react'
import { Alert, Button, Form, Input, message as Message, Modal } from 'antd'
import Container from 'components/container'
import { encryptByRSA } from 'util/rsa'
import { RSA_PUBLIC_KEY } from 'util/global'
import { api } from 'common/api'
import { token } from 'common/token'
export default class index extends Component {
state = {
loading: false,
focusUser: false,
focusPassword: false,
btnDisabled: true,
pattern: '',
descriptions: '',
visible: false,
}
backgroundImage = require(`assets/image/login-bg-0${Math.floor(Math.random() * 4)}.jpg`)
focus = {
user: false,
password: false,
}
form = React.createRef()
onLogin = values => {
this.setState({ loading: true })
let { account, password } = values
password = encryptByRSA(password, RSA_PUBLIC_KEY)
api.login({ account, password })
.then(({ success, data, message }) => {
if (success) {
const { passed, pattern, descriptions, token } = data
// 简单密码需要更改
if (!passed) {
this.setState({
visible: true,
loading: false,
btnDisabled: true,
pattern,
descriptions,
})
} else {
this.onLoginSuccess(token)
}
} else {
this.setState({ loading: false })
Message.error(message)
}
})
.catch(({ message }) => {
if (typeof message === 'object' && message[0]) {
Message.error(message[0].messages[0])
}
this.setState({ loading: false })
})
}
onLoginPass = values => {
this.setState({ loading: true })
const account = this.form.current.getFieldValue('account')
let { password, newPassword } = values
password = encryptByRSA(password, RSA_PUBLIC_KEY)
newPassword = encryptByRSA(newPassword, RSA_PUBLIC_KEY)
const confirm = newPassword // 前端验证两次密码即可.不需要加密
api.loginPass({ account, password, newPassword, confirm })
.then(({ success, data, message }) => {
if (success) {
const { passed, pattern, descriptions, token } = data
// 简单密码需要更改
if (!passed) {
this.setState({
visible: true,
loading: false,
btnDisabled: true,
pattern,
descriptions,
})
} else {
this.onLoginSuccess(token)
}
} else {
this.setState({ loading: false })
Message.error(message)
}
})
.catch(({ message }) => {
if (typeof message === 'object' && message[0]) {
Message.error(message[0].messages[0])
}
this.setState({ loading: false })
})
}
onLoginSuccess(jwtToken) {
token.value = jwtToken
Message.success('登录成功')
this.props.history.replace('/')
}
render() {
const { loading, focusUser, focusPassword, btnDisabled, visible, pattern, descriptions } =
this.state
return (
<div className="yo-login">
<img src={this.backgroundImage.default} alt="" />
<div className="yo-login--placeholder">
<Container mode="sm">
<Form
ref={this.form}
layout="vertical"
onFinish={this.onLogin}
onValuesChange={(changedValues, values) => {
this.setState({
btnDisabled: !values.account || !values.password,
})
}}
>
<Form.Item
name="account"
className={!focusUser && 'yo-login--label'}
colon={false}
label="用户名"
>
<Input
onBlur={() => {
this.setState({
focusUser: !!this.form.current.getFieldValue('account'),
})
}}
onFocus={() => {
this.setState({ focusUser: true })
}}
size="large"
autoComplete="off"
placeholder={focusUser && '请输入用户名/手机号/邮箱'}
/>
</Form.Item>
<Form.Item
name="password"
className={!focusPassword && 'yo-login--label'}
colon={false}
label="密码"
>
<Input.Password
onBlur={() => {
this.setState({
focusPassword:
!!this.form.current.getFieldValue('password'),
})
}}
onFocus={() => {
this.setState({ focusPassword: true })
}}
size="large"
autoComplete="off"
placeholder={focusPassword && '请输入密码'}
/>
</Form.Item>
<Form.Item className="mt-lg">
<Button
disabled={btnDisabled}
loading={loading}
block
htmlType="submit"
size="large"
type="primary"
>
登录
</Button>
</Form.Item>
</Form>
</Container>
</div>
<Modal visible={visible} closable={false} footer={false}>
<Alert type="error" message="密码过于简单,请修改密码!" />
<br />
<Form className="yo-form" onFinish={this.onLoginPass}>
<div className="yo-form-group">
<Form.Item
label="旧密码"
rules={[{ required: true, message: '请输入旧密码' }]}
name="password"
>
<Input.Password autoComplete="off" placeholder="请输入旧密码" />
</Form.Item>
<Form.Item
label="新密码"
rules={[
{ required: true, message: '请输入新密码' },
{ pattern, message: '密码格式错误' },
]}
name="newPassword"
tooltip={descriptions}
>
<Input.Password autoComplete="off" placeholder="请输入新密码" />
</Form.Item>
<Form.Item
label="确认新密码"
rules={[
{ required: true, message: '请确认新密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('newPassword') === value) {
return Promise.resolve()
}
return Promise.reject(new Error('确认新密码不匹配'))
},
}),
]}
name="confirm"
>
<Input.Password autoComplete="off" placeholder="请确认新密码" />
</Form.Item>
</div>
<Form.Item noStyle>
<Button htmlType="submit" type="primary" block loading={loading}>
确认
</Button>
</Form.Item>
</Form>
</Modal>
</div>
)
}
}

View File

@@ -0,0 +1,252 @@
import React, { Component } from 'react'
import { Divider, Layout, Tabs, Menu, Dropdown } from 'antd'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import AntIcon from 'components/ant-icon'
import { Container } from 'components'
NProgress.configure({
parent: '.ant-layout-content > .yo-tab-external-mount > .yo-tab-external-mount-content',
})
class ComponentDynamic extends Component {
state = {
// 组件内部组件的key,用于刷新
key: null,
component: null,
}
shouldComponentUpdate() {
if (this.props.onRef) {
this.props.onRef(this)
}
return true
}
componentDidMount() {
this.loadComponent()
}
loadComponent() {
NProgress.start()
// 在这里使用setTimeout调用,是防止打开窗口时卡顿
setTimeout(async () => {
let component
try {
component = await import(`../../../../pages${this.props.path}`)
} catch {
component = await import('views/error/404')
}
this.setState(
{
key: Math.random().toString(16).slice(2),
component: component.default,
},
() => {
NProgress.done()
}
)
})
}
render() {
if (this.state.component) {
return (
<this.state.component
key={this.state.key}
{...this.props}
supportInfo={
<Container>
<Divider>
<span className="h6 text-gray">Ewide Core ©2021 v1.0</span>
</Divider>
</Container>
}
/>
)
}
return <></>
}
}
class Iframe extends Component {
shouldComponentUpdate() {
if (this.props.onRef) {
this.props.onRef(this)
}
return true
}
componentDidMount() {
if (this.props.onRef) {
this.props.onRef(this)
}
this.loadComponent()
}
loadComponent() {
NProgress.start()
const iframe = this.refs.content
iframe.onload = () => {
NProgress.done()
}
iframe.onerror = () => {
NProgress.done()
}
iframe.src = this.props.src
}
render() {
const { title } = this.props
return <iframe ref="content" title={title} />
}
}
export default class index extends Component {
state = {
actived: '',
}
panes = []
componentDidMount() {
window.reloadContentWindow = this.onReload
}
static getDerivedStateFromProps(props) {
return {
actived: props.actived,
}
}
onChange(activeKey) {
this.props.parent.setState({
actived: activeKey,
})
}
onClose(targetKey, action) {
if (action === 'remove') {
window.closeContentWindow(targetKey)
}
}
onReload = key => {
key = key || this.state.actived
const pane = this.panes.find(p => p.props.id === key)
if (pane) {
pane.loadComponent()
}
}
render() {
this.panes = []
const { actived } = this.state
return (
<Layout.Content>
<div className="yo-tab-external-mount">
<Tabs
type="editable-card"
hideAdd
activeKey={actived}
onChange={activeKey => this.onChange(activeKey)}
onEdit={(targetKey, action) => this.onClose(targetKey, action)}
>
{this.props.panes.map(pane => {
return (
<Tabs.TabPane
closable={pane.closable}
key={pane.key}
tab={
<Dropdown
trigger={['contextMenu']}
overlay={
<Menu>
<Menu.Item
key="0"
onClick={() => this.onReload(pane.key)}
>
重新加载
</Menu.Item>
<Menu.Divider />
<Menu.Item
key="1"
onClick={() =>
window.closeContentWindow(pane.key)
}
>
关闭
</Menu.Item>
<Menu.Item
key="2"
onClick={() =>
window.closeOtherContentWindow(pane.key)
}
>
关闭其他标签页
</Menu.Item>
<Menu.Item
key="3"
onClick={() =>
window.closeRightContentWindow(pane.key)
}
>
关闭右侧标签页
</Menu.Item>
</Menu>
}
>
<div>
{pane.icon && <AntIcon type={pane.icon} />}
{pane.title}
</div>
</Dropdown>
}
/>
)
})}
</Tabs>
<div className="yo-tab-external-mount-content">
{this.props.panes.map(pane => {
return (
<div
key={pane.key}
className={
(pane.key === actived
? 'yo-tab-external-tabpane-active'
: 'yo-tab-external-tabpane-inactive') +
' yo-tab-external-tabpane'
}
>
{pane.openType === 1 ? (
<ComponentDynamic
path={pane.path}
id={pane.key}
key={pane.key}
param={pane.param}
paneActived={pane.key === actived}
onRef={p => this.panes.push(p)}
/>
) : pane.openType === 2 ? (
<Iframe
src={pane.path}
title={pane.key}
id={pane.key}
onRef={p => this.panes.push(p)}
/>
) : null}
</div>
)
})}
</div>
</div>
</Layout.Content>
)
}
}

View File

@@ -0,0 +1,96 @@
import React, { Component, useState } from 'react'
import { Layout, Badge, Popover, Menu, Modal, Tooltip, Popconfirm } from 'antd'
import { AntIcon, Container } from 'components'
import store from 'store'
import { api } from 'common/api'
import Logo from '../logo'
import Search from './search'
import Weather from './weather'
import Notice from './notice'
import User from './user'
const { getState, subscribe, dispatch } = store
export default class index extends Component {
state = {
...getState('layout'),
}
constructor(props) {
super(props)
this.unsubscribe = subscribe('layout', state => {
this.setState(state)
})
}
componentWillUnmount() {
this.unsubscribe()
}
onCollapsed() {
dispatch({
type: 'TOGGLE_COLLAPSED',
siderCollapsed: !this.state.siderCollapsed,
})
}
render() {
const { allowSiderCollapsed, theme } = this.state
return (
<Layout.Header>
<Container mode="fluid">
<div className="header-actions">
{allowSiderCollapsed && (
<span
className="header-action mr-md"
onClick={() => this.onCollapsed()}
>
<AntIcon type="menu" />
</span>
)}
<Logo />
<Search />
</div>
<div className="header-actions">
<Weather />
<Tooltip placement="bottom" title="重新加载框架">
<span
className="header-action"
onClick={() => window.reloadContentWindow()}
>
<AntIcon type="reload" />
</span>
</Tooltip>
<Notice />
<Tooltip
placement="bottom"
title={`切换到${{ default: '夜间', dark: '默认' }[theme]}模式`}
>
<Popconfirm
placement="bottomRight"
title="切换模式将刷新整个页面,是否继续?"
onConfirm={() => {
dispatch({
type: 'SET_THEME',
theme: { default: 'dark', dark: 'default' }[theme],
})
window.location.reload()
}}
>
<span className="header-action">
<div className="theme-toggle">
<div className="theme-toggle--real" />
<div className="theme-toggle--imaginary" />
</div>
</span>
</Popconfirm>
</Tooltip>
<User />
</div>
</Container>
</Layout.Header>
)
}
}

View File

@@ -0,0 +1,158 @@
import React, { Component } from 'react'
import { Badge, Button, List, Popover, Spin, Tag } from 'antd'
import { AntIcon, Image, NoticeDetail } from 'components'
import { api } from 'common/api'
import InfiniteScroll from 'react-infinite-scroller'
import moment from 'moment'
import store from 'store'
const { getState, dispatch, subscribe } = store
export default class notice extends Component {
state = {
...getState('notice'),
loading: false,
hasMore: true,
}
detail = React.createRef()
constructor(props) {
super(props)
this.unsubscribe = subscribe('notice', notice => {
this.setState({ ...notice })
})
}
async componentDidMount() {
const { data: count } = await api.sysNoticeUnread()
dispatch({
type: 'SET_NOTICE_COUNT',
count,
})
}
componentWillUnmount() {
this.unsubscribe()
}
finish() {
this.setState({ loading: false, hasMore: false })
}
async onInfiniteOnLoad(pageIndex) {
this.setState({ loading: true })
const { list } = this.state
if (list.length >= 30) {
return this.finish()
}
const {
data: { items },
} = await api.sysNoticeReceived({
pageIndex,
pageSize: 5,
sortField: 'publicTime',
sortOrder: 'descend',
})
if (!items.length) {
return this.finish()
}
dispatch({
type: 'SET_NOTICE_LIST',
list: [...list, ...items],
})
this.setState({
loading: false,
})
}
renderList() {
const { list, loading, hasMore } = this.state
return (
<InfiniteScroll
loadMore={pageIndex => this.onInfiniteOnLoad(pageIndex)}
hasMore={!loading && hasMore}
useWindow={false}
threshold={50}
>
<List
itemLayout="vertical"
dataSource={list}
renderItem={item => (
<List.Item key={item.id}>
<List.Item.Meta
avatar={<Image id={item.avatar} type="avatar" />}
title={
<>
{item.readStatus ? (
<Tag color="#2db7f5">已读</Tag>
) : (
<Tag color="#f50">未读</Tag>
)}
<a
onClick={() =>
this.detail.current.onOpenDetail(item.id)
}
>
{item.title}
</a>
</>
}
description={
<span className="text-normal ml-xs">
{moment(item.createdTime || item.publicTime).fromNow()}
</span>
}
/>
<div className="ellipsis-2 text-gray">{item.content}</div>
</List.Item>
)}
>
{loading && hasMore && (
<div className="pt-md pb-md text-center">
<Spin indicator={<AntIcon type="loading" />} />
</div>
)}
</List>
{!hasMore && (
<Button
type="text"
block
onClick={() =>
window.openContentWindowByMenuName('sys_notice_mgr_received')
}
>
查看全部
</Button>
)}
</InfiniteScroll>
)
}
render() {
const { count } = this.state
return (
<Popover
arrowPointAtCenter={true}
placement="bottomRight"
content={this.renderList()}
overlayInnerStyle={{ width: 300 }}
overlayStyle={{ zIndex: 999 }}
overlayClassName="yo-popover-infinite-scroll"
>
<span className="header-action">
<Badge count={count}>
<AntIcon type="message" />
</Badge>
</span>
<NoticeDetail ref={this.detail} />
</Popover>
)
}
}

View File

@@ -0,0 +1,177 @@
import React, { Component } from 'react'
import { AutoComplete, Input } from 'antd'
import { AntIcon } from 'components'
import store from 'store'
import { concat } from 'lodash'
const { getState, subscribe } = store
function unZip(menus) {
const result = []
menus.forEach(item => {
if (item.children) {
result.push({
parent: item.meta.title,
children: item.children,
})
} else {
result.push({
parent: item.meta.title,
children: [item],
})
}
})
return result
}
function renderTitle(title) {
return <span>{title}</span>
}
function renderItem(title, menu) {
return {
value: title,
openType: menu.openType,
component: menu.component,
link: menu.link,
redirect: menu.redirect,
menu,
label: (
<>
{title}
<div className="text-normal">
{
{
1: menu.component,
2: menu.link,
3: menu.redirect,
}[menu.openType]
}
</div>
</>
),
}
}
export default class search extends Component {
state = {
options: [],
searchValue: '',
}
nav = getState('nav').nav
autoComplete = React.createRef()
constructor(props) {
super(props)
this.unsubscribe = subscribe('nav', state => {
this.getOptions(state.nav)
})
}
componentDidMount() {
this.getOptions(this.nav)
}
componentWillUnmount() {
this.unsubscribe()
}
onSearch(searchValue, option) {
searchValue = searchValue.toLowerCase()
return (
(option.value && option.value.toLowerCase().includes(searchValue)) ||
(option.openType === 1 &&
option.component &&
option.component.toLowerCase().includes(searchValue)) ||
(option.openType === 2 &&
option.link &&
option.link.toLowerCase().includes(searchValue)) ||
(option.openType === 3 &&
option.redirect &&
option.redirect.toLowerCase().includes(searchValue))
)
}
onSelect(value, option) {
const { id, meta, component, link, redirect, openType } = option.menu
// 选中时清空输入框内容,并失去焦点
this.setState({ searchValue: '' })
this.autoComplete.current.blur()
window.openContentWindow({
key: id,
title: meta.title,
icon: meta.icon,
path: (() => {
switch (openType) {
case 1:
return component
case 2:
return link
case 3:
return redirect
default:
return null
}
})(),
openType,
})
}
getOptions(nav) {
const menus = unZip(
concat.apply(
this,
nav.map(p => p.menu)
)
)
const options = menus.map(item => {
if (item.parent) {
return {
label: renderTitle(item.parent),
options: item.children.map(menu => {
return renderItem(menu.meta.title, menu)
}),
}
} else {
return {
options: item.children.map(menu => {
return this.renderItem(menu.meta.title, menu)
}),
}
}
})
this.setState({ options })
}
render() {
const { options, searchValue } = this.state
return (
<AutoComplete
ref={this.autoComplete}
value={searchValue}
options={options}
dropdownMatchSelectWidth={false}
dropdownStyle={{ width: '300px' }}
dropdownClassName="certain-category-search-dropdown"
optionFilterProp="value"
filterOption={(searchValue, option) => this.onSearch(searchValue, option)}
onSelect={(value, option) => this.onSelect(value, option)}
onChange={value => this.setState({ searchValue: value })}
>
<Input
allowClear
placeholder="请输入检索关键字"
suffix={<AntIcon type="search" />}
/>
</AutoComplete>
)
}
}

View File

@@ -0,0 +1,139 @@
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
import { Modal, message as Message, Dropdown, Button, Menu, Popover, Tag, Row, Col } from 'antd'
import { isEqual } from 'lodash'
import store from 'store'
import { api } from 'common/api'
import { token } from 'common/token'
import { AntIcon, Image } from 'components'
const { getState, dispatch, subscribe } = store
const storePath = 'user'
class User extends Component {
state = {
user: getState(storePath),
}
constructor(props) {
super(props)
this.unsubscribe = subscribe(storePath, () => {
this.setState({
user: getState(storePath),
})
})
}
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state)
}
componentWillUnmount() {
this.unsubscribe()
}
onAccountSetting = () => {
window.openContentWindow({
id: 'account-home',
title: '个人设置',
icon: '',
path: '/system/account',
})
}
onLogout = () => {
Modal.confirm({
title: '提示',
content: '是否确定退出登录',
onOk: async () => {
const { success, message } = await api.logout()
if (success) {
token.value = ''
dispatch({ type: 'RESET_USER_ACCOUNT' })
dispatch({ type: 'RESET_NAV' })
this.props.history.replace('/login')
} else {
Message.error(message)
}
},
})
}
renderMenu() {
const { user } = this.state
return (
<>
<div className="p-md pb-xs">
<Row align="bottom" justify="space-between">
<Col>
<b className="h5">{user.nickName || user.name}</b>
</Col>
<Col>
<span className="text-gray">{user.account}</span>
</Col>
</Row>
<p className="text-gray">上次登录时间{user.lastLoginTime}</p>
{user.adminType === 1 && (
<Tag color="pink" className="mb-xs">
超级管理员
</Tag>
)}
{user.roles &&
user.roles.map(role => (
<Tag key={role.id} color="purple" className="mb-xs">
{role.name}
</Tag>
))}
</div>
<Menu selectable={false}>
<Menu.Divider />
<Menu.Item key="1" onClick={() => this.onAccountSetting()}>
<AntIcon type="user" className="mr-sm" />
个人中心
</Menu.Item>
<Menu.Item key="2">
<AntIcon type="file-text" className="mr-sm" />
操作手册
</Menu.Item>
<Menu.Divider />
<Menu.Item key="-1" onClick={() => this.onLogout()}>
<AntIcon type="logout" className="mr-sm" />
退出登录
</Menu.Item>
</Menu>
</>
)
}
render() {
const { user } = this.state
return (
<div className="user-container">
<div className="user-container-inner">
<Popover
arrowPointAtCenter={true}
overlayClassName="yo-user-popover"
placement="bottomRight"
content={this.renderMenu()}
>
<div className="user--base">
<Image
width="32"
type="avatar"
className="user--avatar"
icon={<AntIcon type="user" />}
id={user.avatar}
/>
</div>
</Popover>
</div>
</div>
)
}
}
export default withRouter(User)

View File

@@ -0,0 +1,51 @@
import React, { Component } from 'react'
import { Space } from 'antd'
import AntIcon from 'components/ant-icon'
import { axios } from 'common/api'
import { AMAP_WEBAPI_KEY } from 'util/global'
export default class weather extends Component {
state = {
weather: '',
temperature: 0,
}
async componentDidMount() {
const {
data: { adcode },
} = await axios.get('https://restapi.amap.com/v3/ip', {
params: {
key: AMAP_WEBAPI_KEY,
},
})
const {
data: { lives },
} = await axios.get('http://restapi.amap.com/v3/weather/weatherInfo', {
params: {
key: AMAP_WEBAPI_KEY,
city: adcode,
},
})
if (Array.isArray(lives)) {
const { weather, temperature } = lives.shift()
this.setState({
weather,
temperature,
})
}
}
render() {
const { weather, temperature } = this.state
return (
<span className="header-action">
<Space align="center" style={{ lineHeight: 1 }}>
<AntIcon type="cloud" />
<span>{temperature}</span>
<span>{weather}</span>
</Space>
</span>
)
}
}

View File

@@ -0,0 +1,14 @@
import React, { Component } from 'react'
export default class index extends Component {
logo = require('assets/image/logo-w.png')
render() {
return (
<div className="logo">
<img src={this.logo.default} alt="" />
</div>
)
}
}

View File

@@ -0,0 +1,90 @@
import React, { Component } from 'react'
import { Layout } from 'antd'
import Swiper, { Mousewheel, Scrollbar } from 'swiper'
import 'swiper/swiper-bundle.css'
import Menu from './menu'
import store from 'store'
Swiper.use([Mousewheel, Scrollbar])
const { getState, subscribe } = store
let timer, swiper
const siderSwiperOptions = {
direction: 'vertical',
slidesPerView: 'auto',
freeMode: true,
scrollbar: {
el: '#layout--swiper-scrollbar',
},
mousewheel: true,
}
const UpdateSwiper = () => {
clearTimeout(timer)
timer = setTimeout(() => {
// 需要更新两次
swiper.update()
swiper.update()
}, 300)
}
export default class index extends Component {
state = {
...getState('layout'),
}
constructor(props) {
super(props)
this.unsubscribe = subscribe('layout', state => {
this.setState(state)
})
}
componentDidMount() {
swiper = new Swiper('#layout--swiper-container', siderSwiperOptions)
window.addEventListener('resize', () => {
UpdateSwiper()
})
}
componentWillUnmount() {
this.unsubscribe()
}
updateSwiper() {
UpdateSwiper()
}
static getDerivedStateFromProps(props, state) {
UpdateSwiper()
return null
}
render() {
return (
<Layout.Sider
className="yo-layout-sider"
width="200"
collapsed={this.state.siderCollapsed || false}
>
<div className="yo-sider-nav">
<div className="swiper-container" id="layout--swiper-container">
<div className="swiper-wrapper">
<div className="swiper-slide">
<Menu
parent={this}
menuStyle={{ height: '100%', borderRight: 0 }}
/>
</div>
</div>
<div className="swiper-scrollbar" id="layout--swiper-scrollbar"></div>
</div>
</div>
</Layout.Sider>
)
}
}

View File

@@ -0,0 +1,133 @@
import React, { Component } from 'react'
import { Menu } from 'antd'
import AntIcon from 'components/ant-icon'
import store from 'store'
const { getState, subscribe } = store
export default class index extends Component {
state = {
...getState('nav'),
}
constructor(props) {
super(props)
this.unsubscribe = subscribe('nav', state => {
this.setState(state)
})
}
componentDidMount() {
window.openContentWindowByMenuName = this.onOpenContentWindowByMenuName
}
componentWillUnmount() {
this.unsubscribe()
}
renderMenu = menu => {
return menu.map(p => {
return p.children ? this.renderSubMenu(p) : this.renderMenuItem(p)
})
}
renderSubMenu = menu => {
return (
<Menu.SubMenu
key={menu.id}
title={menu.meta.title}
icon={menu.meta.icon && <AntIcon type={menu.meta.icon} />}
>
{this.renderMenu(menu.children)}
</Menu.SubMenu>
)
}
renderMenuItem = menu => {
return (
<Menu.Item key={menu.id} onClick={() => this.onOpenContentWindow(menu)}>
{menu.meta.icon && <AntIcon type={menu.meta.icon} />}
<span>{menu.meta.title}</span>
</Menu.Item>
)
}
onOpenContentWindowByMenuName = name => {
for (const item of this.state.nav) {
for (const menu of item.menu) {
if (menu.name === name) {
this.onOpenContentWindow(menu)
return
}
if (menu.children) {
for (const child of menu.children) {
if (child.name === name) {
this.onOpenContentWindow(child)
return
}
}
}
}
}
}
onOpenContentWindow = menu => {
const { id, meta, component, link, redirect, openType } = menu
window.openContentWindow({
key: id,
title: meta.title,
icon: meta.icon,
path: (() => {
switch (openType) {
case 1:
return component
case 2:
return link
case 3:
return redirect
default:
return null
}
})(),
openType,
})
}
onMenuOpenChange = () => {
this.props.parent.updateSwiper()
}
render() {
const props = {
mode: 'inline',
selectable: false,
style: this.props.menuStyle,
theme: 'light',
}
const on = {
onOpenChange: this.onMenuOpenChange,
}
return (
<>
{this.state.nav.map((item, i) => {
if (item.menu.length) {
return (
<section key={i}>
<div className="yo-sider-nav--app">{item.app.name}</div>
<Menu {...props} {...on}>
{this.renderMenu(item.menu)}
</Menu>
</section>
)
} else {
return false
}
})}
</>
)
}
}

View File

@@ -0,0 +1,293 @@
import React, { Component } from 'react'
import { Spin, Layout } from 'antd'
import { api } from 'common/api'
import { cloneDeep, groupBy, findIndex, last } from 'lodash'
import store from 'store'
import { EMPTY_ID, SIDER_BREAK_POINT } from 'util/global'
import Header from './_layout/header'
import Sider from './_layout/sider'
import Content from './_layout/content'
const { dispatch } = store
const serializeMenu = menus => {
const menu = cloneDeep(menus)
const children = groupBy(menu, 'pid')
const serialize = m => {
m.forEach(p => {
if (children[p.id]) {
p.children = serialize(children[p.id])
}
})
return m
}
return children[EMPTY_ID] ? serialize(children[EMPTY_ID]) : []
}
const setNav = async nav => {
const getNav = []
nav.apps.forEach(app => {
const menu = serializeMenu(nav.menus.filter(p => p.application === app.code))
getNav.push({
app,
menu,
})
})
return getNav
}
const getNewID = () => {
return Math.random().toString(16).slice(2)
}
export default class index extends Component {
state = {
loading: true,
panes: [],
actived: '',
test: 0,
}
componentDidMount() {
window.openContentWindow = this.onOpenContentWindow
window.closeContentWindow = this.onCloseContentWindow
window.closeOtherContentWindow = this.onCloseOtherContentWindow
window.closeRightContentWindow = this.onCloseRightContentWindow
api.getLoginUser().then(async ({ data }) => {
const nav = await setNav(data)
dispatch({
type: 'SET_USER_ACCOUNT',
user: data,
})
dispatch({
type: 'SET_NAV',
nav,
})
this.setState({ loading: false }, () => {
this.onOpenContentWindow({
title: '工作台',
path: '/home',
icon: 'home',
closable: false,
})
})
})
window.addEventListener('resize', this.onResizeSider)
this.onResizeSider()
}
/**
* 打开窗口
* @param {*} settings
* @returns
*/
onOpenContentWindow = settings => {
if (settings.path) {
const key = settings.key || getNewID()
/**
* 如果当前标签页已打开,则只需要选中
*/
const pane = this.state.panes.find(p => p.key === key)
if (pane) {
this.onChangeContentWindow(key)
return
}
let path = (p => {
if (p.startsWith('http')) return p
if (p.startsWith('/')) return p
else return `/${p}`
})(settings.path)
if ([2, 3].includes(settings.openType)) {
const param = (p => {
const arr = []
if (p && p.constructor === Object) {
Object.keys(p).forEach(key => {
arr.push(`${key}=${(p[key] || '').toString()}`)
})
}
return arr.join('&')
})(settings.param)
path += param ? `?${param}` : ''
if (settings.openType === 3) {
// 打开新的浏览器窗口
window.open(path)
return
}
}
/**
* 向标签页队列中添加一个新的标签页
*/
const newPane = {
key,
closable: settings.closable === undefined ? true : settings.closable,
icon: settings.icon,
title: settings.title || '新建窗口',
subTitle: settings.subTitle,
component: null,
path,
param: settings.param,
loaded: false,
openType: settings.openType || 1,
}
this.setState({
panes: [...this.state.panes, newPane],
})
this.onChangeContentWindow(key)
//this.$refs.content.onLoadContentWindow(key);
} else {
console.warn('wrong component path')
}
}
/**
* 关闭窗口
* @param {*} key
*/
onCloseContentWindow = key => {
key = key || this.state.actived
const panes = this.state.panes
const i = findIndex(panes, p => p.key === key)
panes.splice(i, 1)
this.setState(
{
panes,
},
() => {
if (panes.length) {
if (key === this.state.actived) {
const pane = panes[i]
if (pane) {
this.onChangeContentWindow(pane.key)
} else {
this.onChangeContentWindow(panes[i - 1].key)
}
}
}
}
)
}
onCloseOtherContentWindow = key => {
if (!key) return
const panes = this.state.panes
let flag = false
for (let i = panes.length - 1; i >= 0; i--) {
const p = panes[i]
if (p.key !== key && p.closable) {
if (p.key === this.state.actived) {
flag = true
}
panes.splice(i, 1)
}
}
this.setState(
{
panes,
},
() => {
if (flag) {
this.onChangeContentWindow(last(panes).key)
}
}
)
}
onCloseRightContentWindow = key => {
if (!key) return
const panes = this.state.panes
let flag = false
for (let i = panes.length - 1; i >= 0; i--) {
const p = panes[i]
if (p.key !== key && p.closable) {
if (p.key === this.tabActived) {
flag = true
}
panes.splice(i, 1)
} else if (p.key === key) {
break
}
}
this.setState(
{
panes,
},
() => {
if (flag) {
this.onChangeContentWindow(last(panes).key)
}
}
)
}
onReloadContentWindow = key => {
if (!key) key = this.state.actived
//this.$refs.content.onLoadContentWindow(key);
}
/**
* 选中窗口
* @param {*} key
*/
onChangeContentWindow = key => {
this.setState({
actived: key,
})
}
onResizeSider = () => {
dispatch({
type: 'AUTO_TOGGLE_COLLAPSED',
siderCollapsed: window.innerWidth <= SIDER_BREAK_POINT,
})
}
render() {
const { loading, panes, actived } = this.state
return (
<section className="yo-layout--spin">
<Spin
spinning={loading}
indicator={
<div className="loader-container">
<p>Loading</p>
</div>
}
>
<Layout className="yo-layout--top-nav--container-fluid yo-layout--top-nav">
<Layout>
<Header />
<Layout className="yo-nav-theme--light">
<Sider />
<Content parent={this} panes={panes} actived={actived} />
</Layout>
</Layout>
</Layout>
</Spin>
</section>
)
}
}