为什么都没了
This commit is contained in:
11
framework/web-react/src/views/error/404.jsx
Normal file
11
framework/web-react/src/views/error/404.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React, { Component } from 'react'
|
||||
|
||||
export default class index extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
404
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
235
framework/web-react/src/views/login/index.jsx
Normal file
235
framework/web-react/src/views/login/index.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
252
framework/web-react/src/views/main/_layout/content/index.jsx
Normal file
252
framework/web-react/src/views/main/_layout/content/index.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
96
framework/web-react/src/views/main/_layout/header/index.jsx
Normal file
96
framework/web-react/src/views/main/_layout/header/index.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
158
framework/web-react/src/views/main/_layout/header/notice.jsx
Normal file
158
framework/web-react/src/views/main/_layout/header/notice.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
177
framework/web-react/src/views/main/_layout/header/search.jsx
Normal file
177
framework/web-react/src/views/main/_layout/header/search.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
139
framework/web-react/src/views/main/_layout/header/user.jsx
Normal file
139
framework/web-react/src/views/main/_layout/header/user.jsx
Normal 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)
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
14
framework/web-react/src/views/main/_layout/logo.jsx
Normal file
14
framework/web-react/src/views/main/_layout/logo.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
90
framework/web-react/src/views/main/_layout/sider/index.jsx
Normal file
90
framework/web-react/src/views/main/_layout/sider/index.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
133
framework/web-react/src/views/main/_layout/sider/menu.jsx
Normal file
133
framework/web-react/src/views/main/_layout/sider/menu.jsx
Normal 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
|
||||
}
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
293
framework/web-react/src/views/main/index.jsx
Normal file
293
framework/web-react/src/views/main/index.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user