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;
}
.ant-descriptions {
@@ -44,6 +44,14 @@
}
}
}
&--scroll {
position: relative;
overflow-x: auto;
}
.ant-list-items {
min-width: 1000px;
}
.ant-list-item {
transition: @animation-duration-slow;
transition-property: background, border-bottom-color;
@@ -52,4 +60,36 @@
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 {
th.ant-table-column-has-sorters {
&: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 {
line-height: 20px;

View File

@@ -43,6 +43,8 @@ export default class QueryList extends Component {
state = {
loading: false,
dataSource: [],
ping: [],
}
// 查询表单实例
@@ -91,6 +93,12 @@ export default class QueryList extends Component {
if (this.autoLoad) {
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.setState({
dataSource: res.rows || res.data || res.items,
})
this.setState(
{
dataSource: res.rows || res.data || res.items,
},
() => {
this.onResizeScroll()
}
)
this.pagination.total = res.totalCount
@@ -174,7 +187,26 @@ export default class QueryList extends Component {
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() {
const { loading, dataSource, ping } = this.state
const attrs = {}
Object.keys(this.props).forEach(key => {
if (!propsMap.includes(key)) {
@@ -206,9 +238,21 @@ export default class QueryList extends Component {
</div>
</div>
<div className="yo-list">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<List {...props} />
{!!this.state.dataSource && !!this.state.dataSource.length && (
<Spin spinning={loading} indicator={<AntIcon type="loading" />}>
<div
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
size="small"
{...this.pagination}

View File

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

View File

@@ -1,6 +1,21 @@
const layout = (state = {
siderCollapsed: false
}, action) => {
import { SETTING_KEY } from "common/storage"
import { SIDER_BREAK_POINT } from "util/global"
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) {
// 打开窗口
case 'OPEN_WINDOW':
@@ -13,8 +28,23 @@ const layout = (state = {
return state
// 侧边收起状态
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:
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() {
const { allowSiderCollapsed } = this.state
return (
<Layout.Header>
<Container mode="fluid">
<div className="header-actions">
<span className="header-action mr-md" onClick={() => this.onCollapsed()}>
<AntIcon type="menu" />
</span>
{allowSiderCollapsed && (
<span
className="header-action mr-md"
onClick={() => this.onCollapsed()}
>
<AntIcon type="menu" />
</span>
)}
<Logo />
<Search />
</div>

View File

@@ -1,10 +1,9 @@
import React, { Component } from 'react'
import { Spin, Layout } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { api } from 'common/api'
import { cloneDeep, groupBy, findIndex, last } from 'lodash'
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 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() {
const { loading, panes, actived } = this.state