Jelajahi Sumber

报表移动端搜索栏调整(pc端亦有调整)

zhuth 6 tahun lalu
induk
melakukan
36202ad57e

+ 1 - 0
package.json

@@ -11,6 +11,7 @@
   "dependencies": {
   "dependencies": {
     "ant-design-pro": "^2.0.0-beta.2",
     "ant-design-pro": "^2.0.0-beta.2",
     "antd": "^3.20.5",
     "antd": "^3.20.5",
+    "antd-mobile": "^2.3.1",
     "cfb": "^1.1.3",
     "cfb": "^1.1.3",
     "copy-to-clipboard": "^3.1.0",
     "copy-to-clipboard": "^3.1.0",
     "dva": "^2.4.1",
     "dva": "^2.4.1",

+ 1 - 1
src/components/common/dataPreview/dataPreview.less

@@ -43,7 +43,7 @@
             }
             }
         }
         }
     }
     }
-    &.mobile {
+    &.es-mobile {
         max-width: 100%;
         max-width: 100%;
         margin: 0;
         margin: 0;
     }
     }

+ 6 - 1
src/components/common/filterBox/filter.jsx

@@ -2,6 +2,7 @@ import React from 'react'
 import { Form, Input, InputNumber, DatePicker, Select, Spin, Divider, Icon, Tag } from 'antd'
 import { Form, Input, InputNumber, DatePicker, Select, Spin, Divider, Icon, Tag } from 'antd'
 import * as service from '../../../services/index'
 import * as service from '../../../services/index'
 import URLS from '../../../constants/url'
 import URLS from '../../../constants/url'
+import EllipsisTooltip from '../ellipsisTooltip/index';
 import moment from 'moment'
 import moment from 'moment'
 import './filter.less'
 import './filter.less'
 const { Option: SelectOption } = Select
 const { Option: SelectOption } = Select
@@ -26,7 +27,10 @@ class Filter extends React.Component {
         const { label, type, operator, operatorLabel } = filter;
         const { label, type, operator, operatorLabel } = filter;
         
         
         return <div className='filter'>
         return <div className='filter'>
-            <span className='filter-name'>{label + ' ' + operatorLabel + ' : '}</span>
+            <EllipsisTooltip title={label} style={{ maxWidth: '6.25em' }}>
+                <span className='filter-name'>{label}</span>
+            </EllipsisTooltip>
+            <span className='filter-sep'>{operatorLabel + ' : '}</span>
             <FormItem className='filter-value filter-value1'>
             <FormItem className='filter-value filter-value1'>
                 { this.generateValueItem(filter, 1) }
                 { this.generateValueItem(filter, 1) }
             </FormItem>
             </FormItem>
@@ -72,6 +76,7 @@ class Filter extends React.Component {
         }else if(type === 'time') {
         }else if(type === 'time') {
             if(operator === 'between') {
             if(operator === 'between') {
                 field = <RangePicker
                 field = <RangePicker
+                    className='ant-calendar-picker-between'
                     defaultValue={defaultValue}
                     defaultValue={defaultValue}
                     ranges={{
                     ranges={{
                         '今天': [moment().startOf('day'), moment().endOf('day')],
                         '今天': [moment().startOf('day'), moment().endOf('day')],

+ 11 - 0
src/components/common/filterBox/filter.less

@@ -1,13 +1,24 @@
 .filter {
 .filter {
     display: flex;
     display: flex;
+    height: 40px;
     .filter-name {
     .filter-name {
+        max-width: 6.25em;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+    }
+    .filter-sep {
         margin: 0 8px;
         margin: 0 8px;
     }
     }
     .filter-value {
     .filter-value {
         flex: 1;
         flex: 1;
+        margin-right: 8px;
         .ant-select-selection__rendered, .ant-select-selection {
         .ant-select-selection__rendered, .ant-select-selection {
             min-width: 100px;
             min-width: 100px;
         }
         }
+        .ant-calendar-picker-between {
+            max-width: 16em;
+        }
     }
     }
     &.vertical {
     &.vertical {
         flex-direction: column;
         flex-direction: column;

+ 6 - 1
src/components/common/filterBox/filter2.jsx

@@ -3,6 +3,7 @@ import { Form, Input, InputNumber, DatePicker, Select, Spin, Divider, Icon, Tag
 import * as service from '../../../services/index'
 import * as service from '../../../services/index'
 import URLS from '../../../constants/url'
 import URLS from '../../../constants/url'
 import moment from 'moment'
 import moment from 'moment'
+import EllipsisTooltip from '../ellipsisTooltip/index';
 import './filter.less'
 import './filter.less'
 const { Option: SelectOption } = Select
 const { Option: SelectOption } = Select
 const FormItem = Form.Item
 const FormItem = Form.Item
@@ -23,7 +24,10 @@ class Filter extends React.Component {
         const { label, type, operator, operatorLabel } = filter;
         const { label, type, operator, operatorLabel } = filter;
         
         
         return <div className={`filter ${vertical ? 'vertical' : ''}`}>
         return <div className={`filter ${vertical ? 'vertical' : ''}`}>
-            <span className='filter-name'>{label + ' ' + operatorLabel + ' : '}</span>
+            <EllipsisTooltip title={label} style={{ maxWidth: '6.25em' }}>
+                <span className='filter-name'>{label}</span>
+            </EllipsisTooltip>
+            <span className='filter-sep'>{operatorLabel + ' : '}</span>
             <FormItem className='filter-value filter-value1'>
             <FormItem className='filter-value filter-value1'>
                 { this.generateValueItem(filter, 1) }
                 { this.generateValueItem(filter, 1) }
             </FormItem>
             </FormItem>
@@ -86,6 +90,7 @@ class Filter extends React.Component {
         }else if(type === 'time') {
         }else if(type === 'time') {
             if(operator === 'between') {
             if(operator === 'between') {
                 field = <RangePicker
                 field = <RangePicker
+                    className='ant-calendar-picker-between'
                     defaultValue={defaultValue}
                     defaultValue={defaultValue}
                     ranges={{
                     ranges={{
                         '今天': [moment().startOf('day'), moment().endOf('day')],
                         '今天': [moment().startOf('day'), moment().endOf('day')],

+ 41 - 16
src/components/dashboardDesigner/content.jsx

@@ -1,11 +1,12 @@
-import React, { Fragment } from 'react';
+import React from 'react';
 import { Layout, Tag, Icon } from 'antd';
 import { Layout, Tag, Icon } from 'antd';
 import { connect } from 'dva';
 import { connect } from 'dva';
 import ViewLayout from './viewLayout';
 import ViewLayout from './viewLayout';
 import FilterBox from '../common/filterBox/filterBox2';
 import FilterBox from '../common/filterBox/filterBox2';
 import Filter from '../common/filterBox/filter2';
 import Filter from '../common/filterBox/filter2';
+import MobileFilterList from './mobileFilterList';
 import ConfigSider from './configSider';
 import ConfigSider from './configSider';
-import FilterList from './filterList'
+import FilterList from './filterList';
 
 
 const { Header, Content, Sider } = Layout;
 const { Header, Content, Sider } = Layout;
 
 
@@ -30,7 +31,8 @@ class DashboardDesignerContent extends React.Component {
             visibleFilterList: false,
             visibleFilterList: false,
             filtersOpened: false, // 展开所有filter
             filtersOpened: false, // 展开所有filter
             filterOvered: false, // filter数量超出了一行
             filterOvered: false, // filter数量超出了一行
-            filterContentHeight: 40 // filter栏高度
+            filterContentHeight: 40, // filter栏高度
+            toggleVisible: true, // 条件栏展开开关
         };
         };
         this.contentRef=React.createRef();
         this.contentRef=React.createRef();
     }
     }
@@ -61,11 +63,10 @@ class DashboardDesignerContent extends React.Component {
      */
      */
     refreshContentSize = () => {
     refreshContentSize = () => {
         let viewLayoutEl = this.contentRef.current;
         let viewLayoutEl = this.contentRef.current;
-        let header = viewLayoutEl.getElementsByClassName('ant-layout-header')[0];
         let viewContentEl = viewLayoutEl.getElementsByClassName('dashboard-viewcontent')[0];
         let viewContentEl = viewLayoutEl.getElementsByClassName('dashboard-viewcontent')[0];
         let _scroll = viewContentEl.scrollHeight > viewContentEl.clientHeight;
         let _scroll = viewContentEl.scrollHeight > viewContentEl.clientHeight;
         let padding = 0;
         let padding = 0;
-        let width = viewContentEl.offsetWidth - padding * 2 - (_scroll ? 10 : 2); // 有滚动条时需要减去滚动条的宽度
+        let width = viewContentEl.offsetWidth - padding * 2 - ( _scroll ? ( esMobile ? 4 : 10) : 2); // 有滚动条时需要减去滚动条的宽度
         let height = viewContentEl.offsetHeight - padding * 2;
         let height = viewContentEl.offsetHeight - padding * 2;
         
         
         this.setState({
         this.setState({
@@ -102,8 +103,8 @@ class DashboardDesignerContent extends React.Component {
     }
     }
 
 
     isParent = (obj,parentObj) => {
     isParent = (obj,parentObj) => {
-        while (obj != undefined && obj != null && obj.tagName.toUpperCase() != 'BODY'){
-            if (obj == parentObj){
+        while (obj !== undefined && obj !== null && obj.tagName.toUpperCase() !== 'BODY'){
+            if (obj === parentObj){
                 return true;
                 return true;
             }
             }
             obj = obj.parentNode;
             obj = obj.parentNode;
@@ -112,7 +113,7 @@ class DashboardDesignerContent extends React.Component {
     }
     }
 
 
     closeFiltersEventListener = e => {
     closeFiltersEventListener = e => {
-        let viewContent = document.getElementsByClassName('react-grid-layout')[0];
+        let viewContent = document.getElementsByClassName('dashboard-viewcontent')[0];
         let sider = document.getElementsByClassName('config-sider')[0];
         let sider = document.getElementsByClassName('config-sider')[0];
         let header = document.getElementsByClassName('dashboarddesigner-header')[0];
         let header = document.getElementsByClassName('dashboarddesigner-header')[0];
 
 
@@ -127,8 +128,12 @@ class DashboardDesignerContent extends React.Component {
 
 
         this.setState({
         this.setState({
             filtersOpened: true,
             filtersOpened: true,
-            filterContentHeight: filterContentScrollHeight
+            filterContentHeight: Math.min(filterContentScrollHeight, document.body.clientHeight)
         });
         });
+        
+        window.setTimeout(() => {
+            this.filtersRef.style.overflow = 'auto';
+        }, 300);
         document.addEventListener('click', this.closeFiltersEventListener)
         document.addEventListener('click', this.closeFiltersEventListener)
     }
     }
 
 
@@ -137,6 +142,7 @@ class DashboardDesignerContent extends React.Component {
             filtersOpened: false,
             filtersOpened: false,
             filterContentHeight: 40
             filterContentHeight: 40
         });
         });
+        this.filtersRef.style.overflow = 'hidden';
         document.removeEventListener('click', this.closeFiltersEventListener);
         document.removeEventListener('click', this.closeFiltersEventListener);
     }
     }
 
 
@@ -154,12 +160,11 @@ class DashboardDesignerContent extends React.Component {
                 filterContentOffsetHeight = filterContent.offsetHeight;
                 filterContentOffsetHeight = filterContent.offsetHeight;
                 filterContentScrollHeight = filterContent.scrollHeight
                 filterContentScrollHeight = filterContent.scrollHeight
 
 
-                console.log(filterContentScrollHeight)
                 if(filterContentScrollHeight === 40) {
                 if(filterContentScrollHeight === 40) {
                     obj.filterOvered = false;
                     obj.filterOvered = false;
                     obj.filtersOpened = false;
                     obj.filtersOpened = false;
                 }
                 }
-                obj.filterContentHeight = filterContentScrollHeight;
+                obj.filterContentHeight = Math.min(filterContentScrollHeight, document.body.clientHeight);
             }else {
             }else {
                 filterContentOffsetHeight = filterContent.offsetHeight;
                 filterContentOffsetHeight = filterContent.offsetHeight;
                 filterContentScrollHeight = filterContent.scrollHeight
                 filterContentScrollHeight = filterContent.scrollHeight
@@ -171,12 +176,18 @@ class DashboardDesignerContent extends React.Component {
         }, 300);
         }, 300);
     }
     }
 
 
+    setToggleVisible = (visible) => {
+        this.setState({
+            toggleVisible: visible
+        });
+    }
+
     render() {
     render() {
         const { dashboardDesigner, isOwner, isShareView, isShareKeyView, isViewMode } = this.props;
         const { dashboardDesigner, isOwner, isShareView, isShareKeyView, isViewMode } = this.props;
         const { dataSources, editMode, filters, relationColumns } = dashboardDesigner;
         const { dataSources, editMode, filters, relationColumns } = dashboardDesigner;
         const { visibleFilterBox, visibleFilterList, contentSize, filtersOpened, filterContentHeight, filterOvered } = this.state;
         const { visibleFilterBox, visibleFilterList, contentSize, filtersOpened, filterContentHeight, filterOvered } = this.state;
 
 
-        return <Layout className='content'>
+        return <Layout className={`content${esMobile ? ' es-mobile' : ''}`}>
             <Content className={`dashboard-content${(isShareView || isShareKeyView || isViewMode) ? ' nomargin' : ''}`}>
             <Content className={`dashboard-content${(isShareView || isShareKeyView || isViewMode) ? ' nomargin' : ''}`}>
                 <Layout className='content-layout'>
                 <Layout className='content-layout'>
                     {isOwner && editMode && !isShareView && !isShareKeyView && !isViewMode && <Sider className={`config-sider${ (isOwner && editMode) ? '' : ' config-sider-closed' }`} width={(isOwner && editMode && !isShareView && !isShareKeyView && !isViewMode) ? 200 : 0}>
                     {isOwner && editMode && !isShareView && !isShareKeyView && !isViewMode && <Sider className={`config-sider${ (isOwner && editMode) ? '' : ' config-sider-closed' }`} width={(isOwner && editMode && !isShareView && !isShareKeyView && !isViewMode) ? 200 : 0}>
@@ -187,28 +198,42 @@ class DashboardDesignerContent extends React.Component {
                     */}
                     */}
                     <main ref={this.contentRef} className='viewlayout ant-layout-content'>
                     <main ref={this.contentRef} className='viewlayout ant-layout-content'>
                         {(editMode || (filters && filters.length > 0) )&& <Header>
                         {(editMode || (filters && filters.length > 0) )&& <Header>
-                            {/* <div className='toggle'><Icon type="caret-up" /></div> */}
                             <div ref = {node => this.filtersRef = node} className={`filters`} style={{ height: `${filterContentHeight}px`, width: `${contentSize.width + (contentSize._scroll ? 10 : 0)}px` }}>
                             <div ref = {node => this.filtersRef = node} className={`filters`} style={{ height: `${filterContentHeight}px`, width: `${contentSize.width + (contentSize._scroll ? 10 : 0)}px` }}>
-                                <div key={filters.length} className='content'>
+                                <div key={filters.length} className={`content${esMobile ? ' es-mobile' : ''}`}>
                                     {isOwner && editMode && !isShareView && !isShareKeyView && !isViewMode && <Tag
                                     {isOwner && editMode && !isShareView && !isShareKeyView && !isViewMode && <Tag
                                         onClick={this.showFilterBox}
                                         onClick={this.showFilterBox}
                                         className={`filter-tag filter-tag-add`}
                                         className={`filter-tag filter-tag-add`}
                                     >
                                     >
                                         <Icon type="filter" theme="outlined" />
                                         <Icon type="filter" theme="outlined" />
                                     </Tag>}
                                     </Tag>}
-                                    {filters.map(f => (
+                                    {!esMobile && filters.map(f => (
                                         <Filter
                                         <Filter
                                             key={f.key}
                                             key={f.key}
                                             filter={f}
                                             filter={f}
                                             changeFilterValue={this.changeFilterValue}
                                             changeFilterValue={this.changeFilterValue}
                                         />
                                         />
                                     ))}
                                     ))}
+                                    {
+                                        esMobile && <MobileFilterList
+                                            filters={filters}
+                                            filtersOpened={filtersOpened}
+                                            closeFilters={this.closeFilters}
+                                            expandFilters={this.expandFilters}
+                                            setToggleVisible={this.setToggleVisible}
+                                            changeFilterValue={this.changeFilterValue}
+                                        />
+                                    }
                                 </div>
                                 </div>
-                                {filterOvered && <div className='right' style={{ display: filters.length > 0 ? 'block' : 'none', fontSize: '20px' }}>
+                                {!esMobile && <div className='right' style={{ display: filters.length > 0 && filterOvered ? 'block' : 'none' }}>
                                     <Icon title={filtersOpened ? '收起' : '展开'} type={filtersOpened ? 'caret-down' : 'caret-up'} onClick={() => {
                                     <Icon title={filtersOpened ? '收起' : '展开'} type={filtersOpened ? 'caret-down' : 'caret-up'} onClick={() => {
                                         filtersOpened ? this.closeFilters() : this.expandFilters();
                                         filtersOpened ? this.closeFilters() : this.expandFilters();
                                     }}/>
                                     }}/>
                                 </div>}
                                 </div>}
+                                {/* {esMobile && filterOvered && <div className='right'>
+                                    <Icon type='bars' onClick={() => {
+                                        filtersOpened ? this.closeFilters() : this.expandFilters();
+                                    }}/>
+                                </div>} */}
                                 {/* {overFilters && <div className='right' style={{ display: filters.length > 0 ? 'block' : 'none' }}>
                                 {/* {overFilters && <div className='right' style={{ display: filters.length > 0 ? 'block' : 'none' }}>
                                     <Fragment>
                                     <Fragment>
                                         <span style={{ cursor: 'pointer' }} onClick={() => {
                                         <span style={{ cursor: 'pointer' }} onClick={() => {

+ 13 - 4
src/components/dashboardDesigner/layout.less

@@ -79,14 +79,19 @@
                     justify-content: space-between;
                     justify-content: space-between;
                     width: 100%;
                     width: 100%;
                     background: #FCFCFC;
                     background: #FCFCFC;
-                    padding-left: 8px;
                     border: none;
                     border: none;
                     overflow: hidden;
                     overflow: hidden;
                     transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
                     transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-                    .content {
+                    >.content {
                         flex: 1;
                         flex: 1;
                         display: flex;
                         display: flex;
                         flex-wrap: wrap;
                         flex-wrap: wrap;
+                        width: 100%;
+                        padding: 0 30px 0 10px;
+                        &.es-mobile {
+                            padding-left: 0;
+                            padding-right: 0;
+                        }
                         .filter-tag {
                         .filter-tag {
                             max-width: 200px;
                             max-width: 200px;
                             overflow: hidden;
                             overflow: hidden;
@@ -105,11 +110,15 @@
                             border: none;
                             border: none;
                             background: transparent;
                             background: transparent;
                             line-height: 20px;
                             line-height: 20px;
+                            padding-left: 0;
+                            margin-left: 0;
                             cursor: pointer;
                             cursor: pointer;
                         }
                         }
                     }
                     }
-                    .right {
-                        padding-right: 8px;
+                    >.right {
+                        position: fixed;
+                        right: 10px;
+                        font-size: 20px;
                         .anticon {
                         .anticon {
                             cursor: pointer;
                             cursor: pointer;
                         }
                         }

+ 541 - 0
src/components/dashboardDesigner/mobileFilterList.jsx

@@ -0,0 +1,541 @@
+import React from 'react';
+import { Button, Spin } from 'antd';
+import List from 'antd-mobile/lib/list';
+import 'antd-mobile/lib/list/style/css'; 
+import InputItem from 'antd-mobile/lib/input-item';
+import 'antd-mobile/lib/input-item/style/css'; 
+import Calendar from 'antd-mobile/lib/calendar';
+import 'antd-mobile/lib/calendar/style/css';
+import DatePicker from 'antd-mobile/lib/date-picker';
+import 'antd-mobile/lib/date-picker/style/css';
+import DatePickerView from 'antd-mobile/lib/date-picker-view';
+import 'antd-mobile/lib/date-picker-view';
+import Menu from 'antd-mobile/lib/menu';
+import 'antd-mobile/lib/menu/style/css';
+import moment from 'moment';
+import calendar_zhCN from 'antd-mobile/lib/calendar/locale/zh_CN';
+import datePicker_zhCN from 'antd-mobile/lib/date-picker/locale/zh_CN';
+import URLS from '../../constants/url';
+import * as service from '../../services/index'
+import './mobileFilterList.less';
+
+const ListItem = List.Item;
+const now = new Date();
+const ranges = [
+    { label: '今天', value : [new Date(moment().startOf('day')), new Date(moment().endOf('day'))] },
+    { label: '本月', value: [new Date(moment().startOf('month')), new Date(moment().endOf('month'))] },
+    { label: '上月', value: [new Date(moment().month(moment().month() - 1).startOf('month')), new Date(moment().month(moment().month() - 1).endOf('month'))] },
+    { label: '近半年', value: [new Date(moment().subtract(6, "months").startOf('day')), new Date(moment().endOf('day'))] },
+    { label: '本年', value: [new Date(moment().startOf('year')), new Date(moment().endOf('year'))] },
+    { label: '上年', value: [new Date(moment().subtract(1, "years").startOf('year')), new Date(moment().subtract(1, "years").endOf('year'))] },
+]
+
+class MobileFilterList extends React.Component {
+    originbodyScrollY = document.getElementsByTagName('body')[0].style.overflowY;
+
+    state = {
+        currentFilter: undefined,
+        showCalendar: false,
+        showMenu: false,
+        calendarConfig: {},
+        calendarValue: false,
+        showCalendarDatePicker: false,
+        calendarStartDate: undefined,
+        calendarEndDate: undefined,
+        calendarDatePickerStartDate: undefined,
+        calendarDatePickerEndDate: undefined,
+        menuConfig: {},
+        menuData: [{ value: 'nothing', label: '暂无数据', disabled: true }],
+        menuSelected: []
+    }
+
+    changeFilterValue = (filter, value, index) => {
+        const { changeFilterValue: propsChangeFilterValue } = this.props;
+        filter['value' + index] = value;
+        if(propsChangeFilterValue && typeof propsChangeFilterValue === 'function') {
+            propsChangeFilterValue({ ...filter });
+        }
+    }
+
+    changeFilterValues = (filter, values) => {
+        const { changeFilterValue: propsChangeFilterValue } = this.props;
+        filter['value1'] = values[0];
+        filter['value2'] = values[1];
+        if(propsChangeFilterValue && typeof propsChangeFilterValue === 'function') {
+            propsChangeFilterValue({ ...filter });
+        }
+    }
+
+    generateFilterItems = () => {
+        const { filters } = this.props;
+
+        return filters.map((filter, index) => {
+            let { type, label, operator, operatorLabel, value1, value2 } = filter;
+            let filterItem;
+
+            if(['index', 'string'].indexOf(type) !== -1) {
+                filterItem = <List key={`filter-value-${index}-${value1}`} renderHeader={() => `${label} (${operatorLabel}) :`}>
+                    <InputItem
+                        defaultValue={value1}
+                        clear
+                        onBlur={(value) => {
+                            if(value !== value1) {
+                                this.changeFilterValue(filter, value, 1)
+                            }
+                        }}
+                    />
+                </List>
+            }else if(['scale', 'ordinal'].indexOf(type) !== -1) {
+                if(operator === 'between') {
+                    filterItem = <List className='row' key={`filter-value-${index}-${value1}-${value2}`} renderHeader={() => `${label} (${operatorLabel}) :`}>
+                        <InputItem
+                            defaultValue={value1}
+                            type="digit"
+                            clear
+                            onBlur={(value) => {
+                                if(value !== value1) {
+                                    this.changeFilterValue(filter, value, 1)
+                                }
+                            }}
+                        />
+                        至
+                        <InputItem
+                            defaultValue={value2}
+                            type="digit"
+                            clear
+                            onBlur={(value) => {
+                                if(value !== value2) {
+                                    this.changeFilterValue(filter, value, 2)
+                                }
+                            }}
+                        />
+                    </List>
+                }else {
+                    filterItem = <List key={`filter-value-${index}-${value1}`} renderHeader={() => `${label} (${operatorLabel}) :`}>
+                        <InputItem
+                            defaultValue={value1}
+                            type="digit"
+                            clear
+                            onBlur={(value) => {
+                                if(value !== value1) {
+                                    this.changeFilterValue(filter, value, 1)
+                                }
+                            }}
+                        />
+                    </List>
+                }
+            }else if(type === 'time') {
+                if(operator === 'between') {
+                    filterItem = <List key={Math.random()} renderHeader={() => `${label} (${operatorLabel}) :`}>
+                        <ListItem
+                            onClick={() => {
+                                console.log(value1, value2);
+                                document.getElementsByTagName('body')[0].style.overflowY = 'hidden';
+                                let obj = {};
+                                obj.showCalendar = true;
+                                obj.currentFilter = filter;
+                                obj.calendarValue = value1 && value2 ? [new Date(value1), new Date(value2)] : false;
+                                this.setState(obj);
+                            }}
+                        >{value1 ? `${moment(value1).format('YYYY-MM-DD')} 至 ${moment(value2).format('YYYY-MM-DD')}` : ''}</ListItem>
+                    </List>
+                }else {
+                    filterItem = <List key={Math.random()} renderHeader={() => `${label} (${operatorLabel}) :`}>
+                        <DatePicker
+                            mode="date"
+                            defaultValue={value1}
+                            onChange={(value) => {
+                                this.changeFilterValue(filter, value, 1)
+                            }}
+                            minDate={new Date(moment().subtract(50, 'years'))}
+                            maxDate={new Date(moment().add(50, 'years'))}
+                            locale={datePicker_zhCN}
+                        >
+                            <ListItem>{value1 ? moment(value1).format('YYYY-MM-DD') : ''}</ListItem>
+                        </DatePicker>
+                    </List>
+                }
+            }else if(type === 'categorical') { // 类别
+                if(operator === 'include' || operator==='notInclude') { // 包含/不包含
+                    filterItem = <List key={Math.random()} renderHeader={() => `${label} (${operatorLabel}) :`}>
+                        <InputItem
+                            defaultValue={value1}
+                            clear
+                            onBlur={(value) => {
+                                if(value !== value1) {
+                                    this.changeFilterValue(filter, value, 1)
+                                }
+                            }}
+                        />
+                    </List>
+                }else if(operator === 'contain' || operator === 'notContain') { // 包括/不包括
+                    filterItem = <List key={Math.random()} renderHeader={() => `${label} (${operatorLabel}) :`}>
+                        <ListItem onClick={() => {
+                            this.fetchColumnData(filter);
+                            this.setState({
+                                currentFilter: filter,
+                                menuConfig: {
+                                    single: false
+                                },
+                                menuSelected: value1,
+                                showMenu: true,
+                            });
+                        }}>{value1.join(',')}</ListItem>
+                    </List>
+                }else { // 等于/不等于
+                    filterItem = <List key={Math.random()} renderHeader={() => `${label} (${operatorLabel}) :`}>
+                        <ListItem onClick={() => {
+                            this.fetchColumnData(filter);
+                            this.setState({
+                                currentFilter: filter,
+                                menuConfig: {
+                                    single: true
+                                },
+                                menuSelected: value1 ? [value1] : [],
+                                showMenu: true,
+                            });
+                        }}>{value1}</ListItem>
+                    </List>
+                }
+            }else {
+                filterItem = <List key={`filter-value-${index}-${value1}`} renderHeader={() => `${label} (${operatorLabel}) :`}>
+                    <InputItem
+                        defaultValue={value1}
+                        clear
+                        onBlur={(value) => {
+                            if(value !== value1) {
+                                this.changeFilterValue(filter, value, 1)
+                            }
+                        }}
+                    />
+                </List>
+            }
+
+            return filterItem;
+        });
+    }
+
+    onCalendarCancel = () => {
+        document.getElementsByTagName('body')[0].style.overflowY = this.originbodyScrollY;
+        this.setState({
+            showCalendar: false,
+        });
+    }
+
+    onMenuOk = (value) => {
+        this.setState({
+            showMenu: false
+        });
+        this.changeFilterValue(this.state.currentFilter, this.state.menuConfig.single ? value[0] : value, 1)
+    }
+
+    onMenuCancel = () => {
+        this.setState({
+            showMenu: false
+        });
+    }
+
+    fetchColumnData = (filter, options) => {
+        const { keyword, mandatory } = options || {};
+        const { key: fkey, combined : isCusMode, dataSource, value1 } = filter;
+        const menuData = this.state[`menuData-${fkey}`] || [];
+        let columns;
+        if(isCusMode) {
+            columns = dataSource.map(d => ({
+                id: d.dataSource.code,
+                columnName: d.column.name,
+                keyword,
+            }))
+        }
+        if(!menuData || menuData.length === 0 || mandatory) {
+            let obj = {};
+            obj[`menuData-${fkey}`] = [];
+            this.setState({ ...obj, menuData: [{ value: 'nothing', label: <div><Spin size="small" />加载中...</div>, disabled: true }], fetching: true }, () => {
+                const body = isCusMode ? columns : {
+                    id: dataSource.code,
+                    columnName: filter.name,
+                    keyword, 
+                };
+                service.fetch({
+                    url: isCusMode ? URLS.DATASOURCE_QUERY_COLUMNDATA_MUL : URLS.DATASOURCE_QUERY_COLUMNDATA,
+                    allow: true,
+                    body: body,
+                }).then(r => {
+                    if(r.code > 0) {
+                        return r;
+                    }else {
+                        let obj = {};
+                        throw obj;
+                    }
+                }).then(r => {
+                    const resData = r.data || [];
+                    // 需要把已经选择的值作为选项展示出来
+                    if(value1) {
+                        if(value1 instanceof Array) { // 如果value1是数组
+                            value1.forEach(v => {
+                                let idx = resData.findIndex(r => r === v);
+                                if(idx > -1) {
+                                    resData.splice(idx, 1);
+                                }
+                            });
+                            let obj = {},
+                                data = value1.concat(resData).map(d => ({
+                                    value: d, label: d
+                                }));
+                            obj[`menuData-${fkey}`] = data;
+                            this.setState({
+                                ...obj,
+                                menuData: data,
+                                fetching: false
+                            });
+                        }else {
+                            let idx = resData.findIndex(r => r === value1);
+                            let obj = {},
+                                data = [value1].concat(resData).map(d => ({
+                                    value: d, label: d
+                                }));
+                            if(idx > -1) {
+                                resData.splice(idx, 1);
+                            }
+
+                            obj[`menuData-${fkey}`] = data;
+                            this.setState({
+                                ...obj,
+                                menuData: data,
+                                fetching: false
+                            });
+                        }
+                    }else {
+                        let obj = {},
+                        data = resData.map(d => ({ value: d, label: d }));
+                        obj[`menuData-${fkey}`] = data;
+                        this.setState({
+                            ...obj,
+                            menuData: data.length > 0 ? data : [{
+                                value: 'nothing',
+                                label: '暂无数据',
+                                disabled: true
+                            }],
+                            fetching: false
+                        });
+                    }
+                }).catch(ex => {
+                    let obj = {};
+                    obj[`menuData-${fkey}`] = [];
+                    this.setState({
+                        ...obj,
+                        menuData: [{ value: 'nothing', label: '暂无数据', disabled: true }],
+                        fetching: false
+                    });
+                    console.error('fetch error', ex);
+                });
+            });
+        }else {
+            this.setState({
+                menuData: menuData,
+            });
+        }
+    }
+
+    renderCalendarHeader = (headerProps) => {
+        return <div className="header">
+            <span className="left" onClick={() => {
+                this.setState({
+                    showCalendar: false
+                })
+            }}>
+                <svg className="am-icon am-icon-cross am-icon-md">
+                    <svg id="cross" viewBox="0 0 44 44"><path fillRule="evenodd" d="M24.008 21.852l8.97-8.968L31.092 11l-8.97 8.968L13.157 11l-1.884 1.884 8.968 8.968-9.24 9.24 1.884 1.885 9.24-9.24 9.24 9.24 1.885-1.884-9.24-9.24z"></path></svg>
+                </svg>
+            </span>
+            <span className="title" onClick={() => {
+                this.setState({
+                    showCalendarDatePicker: true,
+                    calendarDatePickerStartDate: this.state.calendarStartDate,
+                    calendarDatePickerEndDate: this.state.calendarEndDate
+                })
+            }}>日期选择</span>
+            <span className="right" onClick={() => {
+                headerProps.onClear();
+                this.setState({
+                    showCalendar: false,
+                })
+                this.changeFilterValues(this.state.currentFilter, [undefined, undefined]);
+            }}>清除</span>
+        </div>
+    }
+
+    renderCalendarShortcut = (select) => {
+        return <div className="shortcut-panel">
+            { ranges.map((r, i) => (
+                <div key={i} className="item" onClick={() => {
+                    select(r.value[0], r.value[1]);
+                    this.setState({
+                        calendarStartDate: r.value[0],
+                        calendarEndDate: r.value[1]
+                    });
+                }}>{r.label}</div>
+            )) }
+            {
+                // 下面的元素写在这里只是为了使用select事件给日历面板赋值
+            }
+            { this.state.showCalendarDatePicker && <div className="menu-mask" onClick={() => {
+                this.setState({
+                    showCalendarDatePicker: false
+                })
+            }} /> }
+            { this.state.showCalendarDatePicker && <div className='calendar-date-picker'>
+                <div className="am-picker-popup-header">
+                    <div className="am-picker-popup-item am-picker-popup-header-left" onClick={() => {
+                        this.setState({
+                            showCalendarDatePicker: false,
+                        })
+                    }}>取消</div>
+                    <div className="am-picker-popup-item am-picker-popup-title"></div>
+                    <div className="am-picker-popup-item am-picker-popup-header-right" onClick={() => {
+                        select(this.state.calendarDatePickerStartDate, this.state.calendarDatePickerEndDate);
+                        this.setState({
+                            showCalendarDatePicker: false,
+                            calendarStartDate: this.state.calendarDatePickerStartDate,
+                            calendarEndDate: this.state.calendarDatePickerEndDate
+                        })
+                    }}>确定</div>
+                </div>
+                <div className="sub-title">开始时间</div>
+                <DatePickerView
+                    mode='date'
+                    value={this.state.calendarDatePickerStartDate || (this.state.calendarDatePickerEndDate || now)}
+                    minDate={new Date(moment().subtract(50, 'years'))}
+                    maxDate={new Date(moment().add(50, 'years'))}
+                    onChange={(value) => {
+                        let obj = {
+                            calendarDatePickerStartDate: value
+                        };
+                        if(this.state.calendarDatePickerEndDate && moment(value).isAfter(this.state.calendarDatePickerEndDate)) {
+                            obj.calendarDatePickerEndDate = value;
+                        }
+                        this.setState(obj)
+                    }}
+                />
+                <div className="sub-title">结束时间</div>
+                <DatePickerView
+                    mode='date'
+                    value={this.state.calendarDatePickerEndDate || (this.state.calendarDatePickerStartDate || now)}
+                    minDate={new Date(moment().subtract(50, 'years'))}
+                    maxDate={new Date(moment().add(50, 'years'))}
+                    onChange={(value) => {
+                        let obj = {
+                            calendarDatePickerEndDate: value
+                        };
+                        if(this.state.calendarDatePickerStartDate && moment(value).isBefore(this.state.calendarDatePickerStartDate)) {
+                            obj.calendarDatePickerStartDate = value;
+                        }
+                        this.setState(obj)
+                    }}
+                />
+            </div>}
+        </div>
+    }
+
+    render() {
+        const { filtersOpened, closeFilters, expandFilters } = this.props;
+        const { showMenu, showCalendar, calendarValue, currentFilter, menuData, menuSelected, menuConfig } = this.state;
+        console.log(calendarValue);
+        return (
+            <div className='filter-list-mobile'>
+                <List
+                    className='list-title'
+                    renderHeader={() => <Button className='toggle' type="link" icon='bars' onClick={() => {
+                        filtersOpened ? closeFilters() : expandFilters();
+                    }}>{filtersOpened ? '收起' : '筛选'}</Button>}
+                />
+                { this.generateFilterItems() }
+                <List>
+                    <List.Item>
+                        <div
+                            style={{ width: '100%', color: '#108ee9', textAlign: 'center' }}
+                            onClick={() => {
+                                closeFilters();
+                            }}
+                        >
+                            收起
+                        </div>
+                    </List.Item>
+                </List>
+                <Calendar
+                    ref={node => this.calendarRef = node}
+                    // key={`calendar-${hashcode(calendarValue)}`}
+                    locale={calendar_zhCN}
+                    visible={showCalendar}
+                    onCancel={this.onCalendarCancel}
+                    defaultValue={calendarValue}
+                    onSelect={(date, oldDate) => {
+                        let startDate = oldDate[0],
+                            endDate = oldDate[1],
+                            obj = {};
+
+                        if(!startDate || endDate) {
+                            obj.calendarStartDate = date;
+                            // obj.calendarValue = [date, date];
+                            obj.calendarEndDate = date
+                        }else {
+                            obj.calendarEndDate = date
+                            // obj.calendarValue = [this.state.calendarStartDate, date];
+                        }
+                        this.setState(obj);
+                    }}
+                    onConfirm={(start, end) => {
+                        this.changeFilterValues(currentFilter, [start, end]);
+                        this.setState({
+                            showCalendar: false,
+                            calendarStartDate: undefined,
+                            calendarEndDate: undefined
+                        });
+                    }}
+                    // onSelectHasDisableDate={this.onSelectHasDisableDate}
+                    // defaultDate={now}
+                    renderHeader={this.renderCalendarHeader}
+                    showShortcut={true}
+                    renderShortcut={this.renderCalendarShortcut}
+                    rowSize='normal'
+                />
+                
+                {showMenu && <div className="menu-mask" onClick={() => {
+                    this.setState({
+                        showMenu: false
+                    })
+                }} />}
+                {showMenu && <div className='filter-menu-box'>
+                    <InputItem
+                        clear
+                        placeholder='搜索'
+                        onChange={(value) => {
+                            this.fetchColumnData(currentFilter, { keyword: value, mandatory: true })
+                        }}
+                    />
+                    <Menu
+                        multiSelect={true}
+                        data={menuData}
+                        className='filter-menu'
+                        value={menuSelected}
+                        level={1}
+                        height={document.documentElement.clientHeight * 0.6}
+                        onOk={this.onMenuOk}
+                        onCancel={this.onMenuCancel}
+                        onChange={(item) => {
+                            let selected = item;
+                            if(menuConfig.single) {
+                                selected = item.slice(-1);
+                            }
+                            this.setState({
+                                menuSelected: selected
+                            });
+                        }}
+                    />
+                </div>}
+            </div>
+        )
+    }
+};
+
+export default MobileFilterList;

+ 102 - 0
src/components/dashboardDesigner/mobileFilterList.less

@@ -0,0 +1,102 @@
+.filter-list-mobile {
+    width: 100%;
+    padding-top: 40px;
+    >.am-list {
+        >.am-list-header {
+            padding: 0 10px;
+        }
+        >.am-list-body {
+            height: 40px;
+            >.am-list-item {
+                min-height: 40px;
+                padding-left: 10px;
+            }
+        }
+        &.list-title {
+            position: fixed;
+            height: 40px;
+            margin-top: -40px;
+            width: 100%;
+            z-index: 999;
+            background-color: white;
+            border-bottom: 1px solid #DBE5F2;
+            >.am-list-header {
+                padding: 5px 0;
+                text-align: right;
+                .toggle {
+                    font-size: 16px;
+                }
+            }
+        }
+        &.row {
+            .am-list-body {
+                display: flex;
+                flex-direction: row;
+            }
+        }
+    }
+
+    // .am-list-extra {
+    //     display: none;
+    // }
+
+    .menu-mask {
+        position: fixed;
+        bottom: 0;
+        width: 100%;
+        height: 100%;
+        background-color: rgba(0, 0, 0, 0.4);
+        z-index: 999;
+    }
+
+    .am-calendar {
+        .content {
+            .header {
+                >div {
+                    margin: 0 auto;
+                    .am-list-extra {
+                        display: none;
+                    }
+                }
+            }
+            .shortcut-panel {
+                padding: 0;
+                >.item:first-child {
+                    margin-left: 30px;
+                }
+                >.item:last-of-type {
+                    margin-right: 30px;
+                }
+                .sub-title {
+                    padding: 0 20px;
+                }
+            }
+        }
+    }
+
+    .calendar-date-picker {
+        position: fixed;
+        width: 100%;
+        bottom: 0;
+        background: white;
+        z-index: 1001;
+    }
+
+    .am-calendar .single-month .row .cell .info {
+        line-height: 15px;
+    }
+    .am-calendar .single-month .row .cell .date-wrapper .left, .am-calendar .single-month .row .cell .date-wrapper .right {
+        margin: 0 -1px;
+    }
+
+    .filter-menu-box {
+        position: fixed;
+        width: 100%;
+        bottom: 0;
+        z-index: 1000;
+
+        .filter-menu .am-flexbox .am-flexbox-item:last-child .am-list .am-list-item {
+            border-bottom: 1px solid #f7f7f7;
+        }
+    }
+}

+ 2 - 2
src/components/dashboardDesigner/viewLayout.jsx

@@ -136,12 +136,12 @@ class ViewLayout extends React.Component {
                 verticalCompact={true}
                 verticalCompact={true}
                 compactType='vertical'
                 compactType='vertical'
             >
             >
-                {(children.length === 0) ? <div key={`default-chartview-${((contentSize.height - 8) / 48)}`} className='default-chartview' data-grid={{ x: 0, y: 0, w: maxLayoutW, h: ((contentSize.height - 8) / 48) , minW: maxLayoutW, maxW: maxLayoutW, minH: 2, maxH: ((contentSize.height - 8) / 48), static: true }}>
+                {(children.length === 0) ? <div key={`default-chartview-${((contentSize.height - 8) / 48)}`} className='default-chartview' data-grid={{ x: 0, y: 0, w: maxLayoutW, h: Math.max((contentSize.height - 8) / 48, 2) , minW: maxLayoutW, maxW: maxLayoutW, static: true }}>
                     <EmptyContent />
                     <EmptyContent />
                 </div> : children}
                 </div> : children}
             </ReactGridLayout>
             </ReactGridLayout>
             {visiblePreviewBox && <Modal
             {visiblePreviewBox && <Modal
-                className={`previewbox ${themeName} ${esMobile ? 'mobile' : ''}`}
+                className={`previewbox ${themeName} ${esMobile ? 'es-mobile' : ''}`}
                 width={'100%'}
                 width={'100%'}
                 height={'100%'}
                 height={'100%'}
                 visible={visiblePreviewBox}
                 visible={visiblePreviewBox}

+ 2 - 2
src/components/dashboardDesigner/viewLayout.less

@@ -124,7 +124,7 @@
         }
         }
       }
       }
     }
     }
-    &.mobile {
+    &.es-mobile {
         .chartview-toolbar {
         .chartview-toolbar {
           .chart-tools {
           .chart-tools {
             display: block;
             display: block;
@@ -341,7 +341,7 @@
       }
       }
     }
     }
   }
   }
-  &.mobile {
+  &.es-mobile {
     top: 0;
     top: 0;
     margin: 0;
     margin: 0;
     max-width: 100%;
     max-width: 100%;

+ 1 - 1
src/components/dashboardDesigner/viewLayoutItem.jsx

@@ -26,7 +26,7 @@ class ViewLayoutItem extends React.Component {
         const iconCls = editMode ? 'visible-icon' : '';
         const iconCls = editMode ? 'visible-icon' : '';
 
 
         return (
         return (
-            <div className={`chartview${ isPreview ? ' chartview-preview' : (editMode ? ' chartview-edit' : '')}${esMobile?' mobile':''}`} style={{ height: '100%' }}>
+            <div className={`chartview${ isPreview ? ' chartview-preview' : (editMode ? ' chartview-edit' : '')}${esMobile?' es-mobile':''}`} style={{ height: '100%' }}>
                 <div className='chartview-toolbar mover'>
                 <div className='chartview-toolbar mover'>
                     <div className='chart-title'>
                     <div className='chart-title'>
                         {editing ? <Input width={200} ref={node => this['inputRef-' + code] = node} defaultValue={name}
                         {editing ? <Input width={200} ref={node => this['inputRef-' + code] = node} defaultValue={name}

+ 12 - 1
src/index.ejs

@@ -2,7 +2,18 @@
 <html lang="en">
 <html lang="en">
 <head>
 <head>
   <meta charset="UTF-8">
   <meta charset="UTF-8">
-  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
+  <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
+  <script>
+    if ('addEventListener' in document) {
+      document.addEventListener('DOMContentLoaded', function() {
+        FastClick.attach(document.body);
+      }, false);
+    }
+    if(!window.Promise) {
+      document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');
+    }
+  </script>
   <title>BI 商业智能平台</title>
   <title>BI 商业智能平台</title>
 </head>
 </head>
 <body>
 <body>

+ 21 - 0
src/themes/default/base.less

@@ -253,3 +253,24 @@ input::-webkit-input-placeholder {
     border: 1px solid #e5e5e5;
     border: 1px solid #e5e5e5;
     border-radius: 4px;
     border-radius: 4px;
 }
 }
+.es-mobile {
+    &::-webkit-scrollbar,*::-webkit-scrollbar {
+        width: 4px;
+        height: 4px;
+    }
+    
+    &::-webkit-scrollbar,*::-webkit-scrollbar {
+        background: #a0dbf5;
+        border-radius: 0;
+      
+        &:hover {
+          background: #67c6f1;
+        }
+    }
+    
+    &::-webkit-scrollbar,*::-webkit-scrollbar {
+        background: #fff;
+        border: 1px solid #e5e5e5;
+        border-radius: 0;
+    }
+}