update 响应式处理

This commit is contained in:
2021-06-25 17:59:09 +08:00
parent 4eace63902
commit fd9665c265
9 changed files with 275 additions and 72 deletions

View File

@@ -26,7 +26,7 @@
} }
} }
} }
>.ant-pagination { .ant-pagination {
margin: @padding-md 0; margin: @padding-md 0;
} }
.ant-descriptions { .ant-descriptions {
@@ -44,6 +44,14 @@
} }
} }
} }
&--scroll {
position: relative;
overflow-x: auto;
}
.ant-list-items {
min-width: 1000px;
}
.ant-list-item { .ant-list-item {
transition: @animation-duration-slow; transition: @animation-duration-slow;
transition-property: background, border-bottom-color; transition-property: background, border-bottom-color;
@@ -52,4 +60,36 @@
background: linear-gradient(90deg, transparent 10%, @background-color-light 70%, transparent); background: linear-gradient(90deg, transparent 10%, @background-color-light 70%, transparent);
} }
} }
&-container {
position: relative;
&::before,
&::after {
position: absolute;
top: 0;
bottom: 0;
z-index: 3;
width: 30px;
content: '';
transition: box-shadow @animation-duration-slow;
pointer-events: none;
}
&::before {
left: 0;
}
&::after {
right: 0;
}
&.yo-list--ping-left {
&::before {
box-shadow: inset 10px 0 8px -8px fade(@black, 15%);
}
}
&.yo-list--ping-right {
&::after {
box-shadow: inset -10px 0 8px -8px fade(@black, 15%);
}
}
}
} }

View File

@@ -22,6 +22,14 @@
} }
} }
} }
.ant-table {
.ant-table-container {
&::before,
&::after {
z-index: 3;
}
}
}
.ant-table-thead { .ant-table-thead {
th.ant-table-column-has-sorters { th.ant-table-column-has-sorters {
&:hover { &:hover {

View File

@@ -19,6 +19,20 @@
} }
} }
} }
&--collapsed {
position: absolute;
top: 0;
left: 0;
bottom: 0;
z-index: 4;
transform: translateX(-100%);
&.open {
transform: translateX(0);
box-shadow: 2px 0 8px fade(@black , 20%);
}
}
&--bar { &--bar {
line-height: 20px; line-height: 20px;

View File

@@ -43,6 +43,8 @@ export default class QueryList extends Component {
state = { state = {
loading: false, loading: false,
dataSource: [], dataSource: [],
ping: [],
} }
// 查询表单实例 // 查询表单实例
@@ -91,6 +93,12 @@ export default class QueryList extends Component {
if (this.autoLoad) { if (this.autoLoad) {
this.onLoadData() this.onLoadData()
} }
window.addEventListener('resize', this.onResizeScroll)
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResizeScroll)
} }
/** /**
@@ -108,9 +116,14 @@ export default class QueryList extends Component {
this.query this.query
) )
this.setState({ this.setState(
dataSource: res.rows || res.data || res.items, {
}) dataSource: res.rows || res.data || res.items,
},
() => {
this.onResizeScroll()
}
)
this.pagination.total = res.totalCount this.pagination.total = res.totalCount
@@ -174,7 +187,26 @@ export default class QueryList extends Component {
this.onLoadData() this.onLoadData()
} }
onResizeScroll = () => {
this.onListScrollX({ target: this.refs['scroll-x'] })
}
onListScrollX(e) {
const { offsetWidth, scrollWidth, scrollLeft } = e.target
const ping = []
if (offsetWidth + scrollLeft < scrollWidth && scrollLeft > 0) {
ping.push(...['left', 'right'])
} else if (offsetWidth + scrollLeft < scrollWidth) {
ping.push('right')
} else if (offsetWidth + scrollLeft >= scrollWidth && offsetWidth < scrollWidth) {
ping.push('left')
}
this.setState({ ping })
}
render() { render() {
const { loading, dataSource, ping } = this.state
const attrs = {} const attrs = {}
Object.keys(this.props).forEach(key => { Object.keys(this.props).forEach(key => {
if (!propsMap.includes(key)) { if (!propsMap.includes(key)) {
@@ -206,9 +238,21 @@ export default class QueryList extends Component {
</div> </div>
</div> </div>
<div className="yo-list"> <div className="yo-list">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}> <Spin spinning={loading} indicator={<AntIcon type="loading" />}>
<List {...props} /> <div
{!!this.state.dataSource && !!this.state.dataSource.length && ( className={['yo-list-container', ...ping.map(p => `yo-list--ping-${p}`)]
.filter(p => p)
.join(' ')}
>
<div
className="yo-list--scroll"
ref="scroll-x"
onScroll={e => this.onListScrollX(e)}
>
<List {...props} />
</div>
</div>
{!!dataSource && !!dataSource.length && (
<Pagination <Pagination
size="small" size="small"
{...this.pagination} {...this.pagination}

View File

@@ -1,6 +1,7 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { Breadcrumb, Empty, Input, Layout, Spin, Tooltip, Tree } from 'antd' import { Breadcrumb, Button, Col, Empty, Input, Layout, Row, Spin, Tooltip, Tree } from 'antd'
import { AntIcon, Container } from 'components' import { AntIcon, Container } from 'components'
import { SIDER_BREAK_POINT } from 'util/global'
function generateKey(data, level) { function generateKey(data, level) {
const n = level || [0] const n = level || [0]
@@ -23,7 +24,7 @@ function generateList(data) {
this.list.push({ this.list.push({
key, key,
[this.replaceFields.value]: data[i][this.replaceFields.value], [this.replaceFields.value]: data[i][this.replaceFields.value],
[this.replaceFields.title]: data[i][this.replaceFields.title] [this.replaceFields.title]: data[i][this.replaceFields.title],
}) })
if (data[i][this.replaceFields.children]) { if (data[i][this.replaceFields.children]) {
generateList.call(this, data[i][this.replaceFields.children]) generateList.call(this, data[i][this.replaceFields.children])
@@ -32,7 +33,7 @@ function generateList(data) {
} }
function getParentKey(key, tree) { function getParentKey(key, tree) {
let parentKey; let parentKey
for (let i = 0; i < tree.length; i++) { for (let i = 0; i < tree.length; i++) {
const node = tree[i] const node = tree[i]
if (node[this.replaceFields.children]) { if (node[this.replaceFields.children]) {
@@ -43,23 +44,26 @@ function getParentKey(key, tree) {
} }
} }
} }
return parentKey; return parentKey
} }
function renderTitle(nodeData) { function renderTitle(nodeData) {
const title = nodeData[this.replaceFields.title] const title = nodeData[this.replaceFields.title]
if (this.state.searchValue && title.indexOf(this.state.searchValue) > -1) { if (this.state.searchValue && title.indexOf(this.state.searchValue) > -1) {
return <> return (
{title.substr(0, title.indexOf(this.state.searchValue))} <>
<span style={{ color: '#f50' }}>{this.state.searchValue}</span> {title.substr(0, title.indexOf(this.state.searchValue))}
{title.substr(title.indexOf(this.state.searchValue) + this.state.searchValue.length)} <span style={{ color: '#f50' }}>{this.state.searchValue}</span>
</> {title.substr(
title.indexOf(this.state.searchValue) + this.state.searchValue.length
)}
</>
)
} }
return <>{title}</> return <>{title}</>
} }
function renderBreadcrumbItem() { function renderBreadcrumbItem() {
const path = ['顶级'] const path = ['顶级']
const findPath = (data, level) => { const findPath = (data, level) => {
@@ -91,14 +95,16 @@ function renderBreadcrumbItem() {
} }
export default class QueryTreeLayout extends Component { export default class QueryTreeLayout extends Component {
state = { state = {
loading: false, loading: false,
dataSource: [], dataSource: [],
expandedKeys: [], expandedKeys: [],
selectedKey: '', selectedKey: '',
autoExpandParent: true, autoExpandParent: true,
searchValue: '' searchValue: '',
collapsed: false,
showSider: false,
} }
list = [] list = []
@@ -108,16 +114,18 @@ export default class QueryTreeLayout extends Component {
replaceFields = { replaceFields = {
value: 'id', value: 'id',
title: 'title', title: 'title',
children: 'children' children: 'children',
} }
constructor(props) { constructor(props) {
super(props) super(props)
this.autoLoad = typeof this.props.autoLoad === 'boolean' ? this.props.autoLoad : true this.autoLoad = typeof this.props.autoLoad === 'boolean' ? this.props.autoLoad : true
this.loadData = typeof this.props.loadData === 'function' ? this.props.loadData : async () => { } this.loadData =
typeof this.props.loadData === 'function' ? this.props.loadData : async () => {}
this.defaultExpanded = typeof this.props.defaultExpanded === 'boolean' ? this.props.defaultExpanded : false this.defaultExpanded =
typeof this.props.defaultExpanded === 'boolean' ? this.props.defaultExpanded : false
if (this.props.replaceFields) { if (this.props.replaceFields) {
this.replaceFields = this.props.replaceFields this.replaceFields = this.props.replaceFields
@@ -131,6 +139,13 @@ export default class QueryTreeLayout extends Component {
if (this.autoLoad) { if (this.autoLoad) {
this.onLoadData() this.onLoadData()
} }
window.addEventListener('resize', this.onResizeLayout)
this.onResizeLayout()
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResizeLayout)
} }
/** /**
@@ -151,7 +166,7 @@ export default class QueryTreeLayout extends Component {
} }
this.setState({ this.setState({
dataSource: data, dataSource: data,
expandedKeys expandedKeys,
}) })
this.onLoaded() this.onLoaded()
} }
@@ -162,8 +177,8 @@ export default class QueryTreeLayout extends Component {
onLoading = () => { onLoading = () => {
this.setState({ this.setState({
loading: { loading: {
indicator: <AntIcon type="loading" /> indicator: <AntIcon type="loading" />,
} },
}) })
} }
@@ -178,28 +193,28 @@ export default class QueryTreeLayout extends Component {
this.onLoadData() this.onLoadData()
} }
onExpand = (expandedKeys) => { onExpand = expandedKeys => {
this.setState({ this.setState({
expandedKeys, expandedKeys,
autoExpandParent: false autoExpandParent: false,
}) })
} }
onSelect = (selectedKeys) => { onSelect = selectedKeys => {
const selectedIds = [] const selectedIds = []
selectedKeys.forEach(p => { selectedKeys.forEach(p => {
const data = this.list.find(m => m.key === p) const data = this.list.find(m => m.key === p)
selectedIds.push(data[this.replaceFields.value]) selectedIds.push(data[this.replaceFields.value])
}) })
this.setState({ this.setState({
selectedKey: selectedIds[0] selectedKey: selectedIds[0],
}) })
if (this.props.onSelect) { if (this.props.onSelect) {
this.props.onSelect(selectedIds[0]) this.props.onSelect(selectedIds[0])
} }
} }
onSearch = (value) => { onSearch = value => {
const expandedKeys = this.list const expandedKeys = this.list
.map(p => { .map(p => {
if (p[this.replaceFields.title].indexOf(value) > -1) { if (p[this.replaceFields.title].indexOf(value) > -1) {
@@ -212,34 +227,49 @@ export default class QueryTreeLayout extends Component {
this.setState({ this.setState({
expandedKeys, expandedKeys,
autoExpandParent: !!value, autoExpandParent: !!value,
searchValue: value searchValue: value,
})
}
onResizeLayout = () => {
this.setState({
collapsed: window.innerWidth <= SIDER_BREAK_POINT,
}) })
} }
render() { render() {
const { loading, dataSource, expandedKeys, autoExpandParent, collapsed, showSider } =
const { dataSource, expandedKeys, autoExpandParent } = this.state this.state
const props = { const props = {
treeData: dataSource, treeData: dataSource,
expandedKeys, expandedKeys,
autoExpandParent autoExpandParent,
} }
const on = { const on = {
onExpand: (expandedKeys) => this.onExpand(expandedKeys), onExpand: expandedKeys => this.onExpand(expandedKeys),
onSelect: (selectedKeys) => this.onSelect(selectedKeys) onSelect: selectedKeys => this.onSelect(selectedKeys),
} }
return ( return (
<Layout className="yo-tree-layout"> <Layout className="yo-tree-layout">
<Layout.Sider width="240px"> <Layout.Sider
width="240px"
className={[
collapsed ? 'yo-tree-layout--collapsed' : '',
showSider ? 'open' : '',
]
.filter(p => p)
.join(' ')}
onMouseLeave={() => this.setState({ showSider: false })}
>
<Layout.Header> <Layout.Header>
<div className="header-actions"> <div className="header-actions">
<Input.Search <Input.Search
allowClear={true} allowClear={true}
placeholder="请输入检索关键字" placeholder="请输入检索关键字"
onSearch={(value) => this.onSearch(value)} onSearch={value => this.onSearch(value)}
/> />
</div> </div>
</Layout.Header> </Layout.Header>
@@ -248,37 +278,56 @@ export default class QueryTreeLayout extends Component {
<AntIcon type="undo" onClick={() => this.onReloadData()} /> <AntIcon type="undo" onClick={() => this.onReloadData()} />
</Tooltip> </Tooltip>
<Tooltip placement="bottom" title="折叠全部"> <Tooltip placement="bottom" title="折叠全部">
<AntIcon type="switcher" onClick={() => this.setState({ expandedKeys: [] })} /> <AntIcon
type="switcher"
onClick={() => this.setState({ expandedKeys: [] })}
/>
</Tooltip> </Tooltip>
</div> </div>
<div className="yo-tree-layout--content"> <div className="yo-tree-layout--content">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />} wrapperClassName="h-100-p"> <Spin
{ spinning={loading}
!this.state.loading && !this.state.dataSource.length ? indicator={<AntIcon type="loading" />}
<Empty wrapperClassName="h-100-p"
image={ >
<div className="text-center pt-md"> {!loading && !dataSource.length ? (
<AntIcon className="h3 mt-xl mb-md" type="smile" /> <Empty
<p>暂无数据</p> image={
</div> <div className="text-center pt-md">
} <AntIcon className="h3 mt-xl mb-md" type="smile" />
description={false} <p>暂无数据</p>
/> </div>
: }
<Tree description={false}
{...props} />
{...on} ) : (
titleRender={(nodeData) => renderTitle.call(this, nodeData)} <Tree
/> {...props}
} {...on}
titleRender={nodeData => renderTitle.call(this, nodeData)}
/>
)}
</Spin> </Spin>
</div> </div>
</Layout.Sider> </Layout.Sider>
<Layout.Content> <Layout.Content>
<Container mode="fluid"> <Container mode="fluid">
<Breadcrumb className="mt-md mb-md"> <Row gutter={16} align="middle">
{renderBreadcrumbItem.call(this)} {collapsed && (
</Breadcrumb> <Col>
<Button
type="text"
icon={<AntIcon type="menu" />}
onClick={() => this.setState({ showSider: true })}
/>
</Col>
)}
<Col flex="1">
<Breadcrumb className="mt-md mb-md">
{renderBreadcrumbItem.call(this)}
</Breadcrumb>
</Col>
</Row>
</Container> </Container>
{this.props.children} {this.props.children}
</Layout.Content> </Layout.Content>

View File

@@ -1,6 +1,21 @@
const layout = (state = { import { SETTING_KEY } from "common/storage"
siderCollapsed: false import { SIDER_BREAK_POINT } from "util/global"
}, action) => {
const defaultState = {
siderCollapsed: false,
allowSiderCollapsed: true
}
const localStorageState = () => {
return JSON.parse(window.localStorage.getItem(SETTING_KEY))
}
const mergeState = {
...defaultState,
...localStorageState()
}
const layout = (state = mergeState, action) => {
switch (action.type) { switch (action.type) {
// 打开窗口 // 打开窗口
case 'OPEN_WINDOW': case 'OPEN_WINDOW':
@@ -13,8 +28,23 @@ const layout = (state = {
return state return state
// 侧边收起状态 // 侧边收起状态
case 'TOGGLE_COLLAPSED': case 'TOGGLE_COLLAPSED':
const _state = { ...state, siderCollapsed: action.siderCollapsed } {
return _state if (window.innerWidth <= SIDER_BREAK_POINT) {
return state
}
const _state = { ...state, siderCollapsed: action.siderCollapsed }
window.localStorage.setItem(SETTING_KEY, JSON.stringify(_state))
return _state
}
case 'AUTO_TOGGLE_COLLAPSED':
{
const _state = {
...state,
siderCollapsed: localStorageState().siderCollapsed || action.siderCollapsed,
allowSiderCollapsed: !action.siderCollapsed
}
return _state
}
default: default:
return state return state
} }

View File

@@ -36,4 +36,6 @@ export const RSA_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQU
/** /**
* 城市名称 * 城市名称
*/ */
export const CITY = '黄石市' export const CITY = '黄石市'
export const SIDER_BREAK_POINT = 1366

View File

@@ -33,13 +33,20 @@ export default class index extends Component {
} }
render() { render() {
const { allowSiderCollapsed } = this.state
return ( return (
<Layout.Header> <Layout.Header>
<Container mode="fluid"> <Container mode="fluid">
<div className="header-actions"> <div className="header-actions">
<span className="header-action mr-md" onClick={() => this.onCollapsed()}> {allowSiderCollapsed && (
<AntIcon type="menu" /> <span
</span> className="header-action mr-md"
onClick={() => this.onCollapsed()}
>
<AntIcon type="menu" />
</span>
)}
<Logo /> <Logo />
<Search /> <Search />
</div> </div>

View File

@@ -1,10 +1,9 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { Spin, Layout } from 'antd' import { Spin, Layout } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { api } from 'common/api' import { api } from 'common/api'
import { cloneDeep, groupBy, findIndex, last } from 'lodash' import { cloneDeep, groupBy, findIndex, last } from 'lodash'
import store from 'store' import store from 'store'
import { EMPTY_ID } from 'util/global' import { EMPTY_ID, SIDER_BREAK_POINT } from 'util/global'
import Header from './_layout/header' import Header from './_layout/header'
import Sider from './_layout/sider' import Sider from './_layout/sider'
@@ -83,6 +82,9 @@ export default class index extends Component {
}) })
}) })
}) })
window.addEventListener('resize', this.onResizeSider)
this.onResizeSider()
} }
/** /**
@@ -231,6 +233,13 @@ export default class index extends Component {
}) })
} }
onResizeSider = () => {
dispatch({
type: 'AUTO_TOGGLE_COLLAPSED',
siderCollapsed: window.innerWidth <= SIDER_BREAK_POINT,
})
}
render() { render() {
const { loading, panes, actived } = this.state const { loading, panes, actived } = this.state