其他章节请看:

react实战 系列

My Dashboard

上一篇我们在 spug 项目中模仿”任务计划“模块实现一个类似的一级导航页面(”My任务计划“),本篇,我们将模仿“Dashboard”来实现一个仪表盘“My Dashboard”。

主要涉及 antd 的 GridCardDescriptions等组件、bizcharts 的使用、moment 日期库和页面适配。

:实现的代码在上一篇的基础上展开。

Dashboard

界面如下:

里面用到了:

  • antd 的 GridCardDescriptions 描述列表 (文字长度不同,有时会感觉没对齐)
  • bizcharts 中的折线图、柱状图
  • moment(日期相关的库),比如按天、按月、最近 30 天都很方便

My Dashboard

最终效果

无需权限即可访问:

全屏效果:

实现的代码

安装两个依赖包:

  • @antv/data-set,柱状图和饼状图需要使用
  • bx-tooltip,自定义 bizcharts 中的 tooltip。折线图和柱状图的 tooltip 都使用了。
spug-study> npm i @antv/data-set

added 31 packages, and audited 1820 packages in 26s

107 packages are looking for funding
run `npm fund` for details 33 vulnerabilities (1 low, 16 moderate, 15 high, 1 critical) To address issues that do not require attention, run:
npm audit fix To address all issues (including breaking changes), run:
npm audit fix --force Run `npm audit` for details.
spug-study> npm i -D bx-tooltip

added 1 package, and audited 1821 packages in 9s

107 packages are looking for funding
run `npm fund` for details 33 vulnerabilities (1 low, 16 moderate, 15 high, 1 critical) To address issues that do not require attention, run:
npm audit fix To address all issues (including breaking changes), run:
npm audit fix --force Run `npm audit` for details.

package.json 变动如下:

"dependencies": {
"@antv/data-set": "^0.11.8",
}
"devDependencies": {
"bx-tooltip": "^0.1.6",
}

增强表格组件

spug 中封装的表格组件,不支持 style和 size。替换一行,以及增加一行:

// src/components/TableCard.js

- <div ref={rootRef} className={styles.tableCard}>
+ <div ref={rootRef} className={styles.tableCard} style={{...props.customStyles}}> <Table
+ size={props.size}

准备 mock 数据

将 mydashboard 模块的的 mock 专门放入一个文件,并在 mock/index.js 中引入。

// src\mock\index.js

+ import './mydashboard'

// src\mock\mydashboard.js

import Mock from 'mockjs'

// 开发环境引入 mock
if (process.env.NODE_ENV === 'development') { Mock.mock('/api/mdashboard/occupancy_rate/', 'get', () => (
{"data": [ {
month: "2022-01-01",
city: "城市-名字很长很长很长",
happiness: 10,
per: 90,
msg1: '信息xxx'
},
{
month: "2022-01-01",
city: "城市B",
per: 30,
happiness: 50,
msg1: '信息xxx'
},
{
month: "2022-02-01",
city: "城市-名字很长很长很长",
happiness: 20,
per: 40,
msg1: '信息xxx'
}, {
month: "2022-02-01",
city: "城市B",
happiness: 20,
per: 60,
msg1: '信息xxx'
},
{
month: "2022-03-01",
city: "城市-名字很长很长很长",
happiness: 30,
per: 80,
msg1: '信息xxx'
},], "error": ""} )) let mIdSeed = 1;
Mock.mock('/api/mdashboard/table', 'get', () => ({
"data": [{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
{ "id": mIdSeed++, "name": "苹果" + mIdSeed, address: '场地' +mIdSeed, time: new Date().toLocaleTimeString() },
]
}))
}

路由配置

配置 /mdashboard/mydashboard 两个路由:

// src\App.js

+ import MDashboard from './pages/mdashboard/tIndex';

class App extends Component {
render() {
return (
<Switch>
// 无需权限
+ <Route path="/mdashboard" exact component={MDashboard} />
<Route path="/" exact component={Login} />
<Route path="/ssh" exact component={WebSSH} />
<Route component={Layout} />
</Switch>
);
}
}
// src\routes.js

+ import MyDashboardIndex from './pages/mdashboard';

export default [
{icon: <DesktopOutlined/>, title: '工作台', path: '/home', component: HomeIndex},
{
icon: <DashboardOutlined/>,
title: 'Dashboard',
auth: 'dashboard.dashboard.view',
path: '/dashboard',
component: DashboardIndex
},
+ // 我的仪表盘
+ {
+ icon: <DashboardOutlined />,
+ title: 'MyDashboard',
+ auth: 'mydashboard.mydashboard.view',
+ path: '/mydashboard',
+ component: MyDashboardIndex
+ },

新建仪表盘组件。一个需要权限访问,另一个无需权限即可访问,故将仪表盘提取成一个单独的文件:

// src\pages\mdashboard\Dashboard.js

import React from 'react';
export default function () {
return (
<div>仪表盘</div>
)
}
// src\pages\mdashboard\index.js

import React from 'react';
import { AuthDiv } from 'components';
import Dashboard from './Dashboard'; export default function () {
return (
<section>
// AuthDiv 是 spug 封装的与权限相关的组件
<AuthDiv auth="testdashboard.testdashboard.view">
<p>需要权限才能访问</p>
<Dashboard />
</AuthDiv>
</section>
)
}
// src\pages\mdashboard\tIndex.js

import React from 'react';
import Dashboard from './Dashboard'; export default function () {
return (
<section>
<p>无需权限也能访问</p>
<Dashboard />
</section>
)
}

重启服务,倘若能访问,说明一切就绪,只差仪表盘核心代码。

访问 /mydashboard

访问 /mdashboard

仪表盘的核心代码

样式
// src\pages\mdashboard\index.module.less

.tdashboardBox {
.react{
width: 10px;
height: 10px;
display: inline-block;
background: #52c41a; /* #00000040 */
margin-left: 30px;
margin-right: 10px;
} // 参考:src\components\index.module.less 中 global
:global(.trendBox .ant-card-head-wrapper) {
width: 100%;
}
}
表格(水果信息)
// src\pages\mdashboard\Table.js

import React from 'react';
import { observer } from 'mobx-react';
import { Descriptions } from 'antd';
import { TableCard } from 'components';
import store from './store'; @observer
class ComTable extends React.Component {
// 默认值
static defaultProps = {
tableHeight: 353
} // scrollY 以外的高度
excludeScrollY = 120;
componentDidMount() {
store.fetchRecords()
} columns = [{
title: 'id',
dataIndex: 'id',
},{
title: '名称',
dataIndex: 'name',
}, {
title: '生产地',
dataIndex: 'address',
}, {
title: '时间',
dataIndex: 'time',
}]; handleExpand = record => {
return <Descriptions>
<Descriptions.Item label="真数据">{record.time}</Descriptions.Item>
<Descriptions.Item label="假数据">xxx</Descriptions.Item>
<Descriptions.Item label="假数据xxx">xxxxxx</Descriptions.Item>
<Descriptions.Item label="假数据xx">xxxxxxxxxxxxxxx</Descriptions.Item>
<Descriptions.Item label="假数据xx">xxx</Descriptions.Item>
<Descriptions.Item label="假数据xxxxxx">
xxxxx xxxxx xxxxxxxxxx xxxxxxxxx
</Descriptions.Item>
</Descriptions>
} render() {
console.log('this.props.tableHeight', this.props.tableHeight, 'y', this.props.tableHeight * this.scrollRadio)
return (
<TableCard
customStyles={{height: this.props.tableHeight}}
title="水果信息"
tKey="mt"
rowKey="id"
loading={store.isFetching}
dataSource={store.dataSource}
onReload={store.fetchRecords}
actions={[]}
scroll={{ y: this.props.tableHeight - this.excludeScrollY }}
expandable={{
expandedRowRender: this.handleExpand,
expandRowByClick: true
}}
size={'middle'}
// 设为 false 时不展示和进行分页
pagination={false}
columns={this.columns} />
)
}
} export default ComTable
折线图(居住趋势)
// src\pages\mdashboard\Trend.js

import React, { useState, useEffect } from 'react';
import { Card, DatePicker, Modal } from 'antd';
import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts';
import { http } from 'libs';
import styles from './index.module.less'
// 日期相关的库,比如最近30天等
import moment from 'moment'; /*
bizcharts 官网:
通过bx-tooltip插件自定义
为了满足更灵活多变的Tooltip自定义需求,提供bx-tooltip插件来实现ReactNode渲染,摆脱HTML模板的繁琐和死板
*/
import useCustTooltip from 'bx-tooltip';
import { Typography, Space } from 'antd';
import store from './store' export default function (props = { cardBodyHeight: 450 }) {
// chart 高度占比
const chartHeightRatio = 0.888 const { Text, Link, Title } = Typography;
const [loading, setLoading] = useState(true);
// 本月第一天 —— 本月最后一天
// const [duration, setDuration] = useState([moment().startOf('month'), moment().endOf('month')]);
// 最近三十天
const [duration, setDuration] = useState([moment().subtract(29, 'days'), moment()]);
const [res, setRes] = useState([]); useEffect(() => {
const strDuration = duration.map(x => x.format('YYYY-MM-DD')) setLoading(true);
http.get('/api/mdashboard/occupancy_rate/', { duration: strDuration })
.then(res => {
setRes(res)
})
.finally(() => setLoading(false))
}, [duration]) // bx-tooltip插件的使用
const [BxChart, CustTooltip] = useCustTooltip.create(Chart, Tooltip); return (
// headStyle、bodyStyle 在这里都是用于适配(响应式)
<Card className="trendBox" loading={loading} title="居住趋势" headStyle={store.cardTitleStyle} bodyStyle={{ height: props.cardBodyHeight }} extra={(
<div>
<DatePicker.RangePicker allowClear={false} style={{ width: 250 }} value={duration} onChange={val => setDuration(val)} />
</div>
)}> <BxChart height={props.cardBodyHeight * chartHeightRatio} data={res} padding={[30, 120, 20, 60]}
// 坐标轴展示不完整
scale={{ month: { range: [0.05, 0.99] }, per: { alias: '居住率', range: [0, 0.95], minTickInterval: 10, max: 100, min: 0 } }}
// 强制适应(PS:只会对宽度有响应式,高度没有)
forceFit
>
<Legend position="right-center" allowAllCanceled={true} itemFormatter={val => {
const maxNum = 10
return val.length > maxNum ? val.split('').slice(0, maxNum - 3).join('') + '...' : val
}} />
{/* x 坐标格式化 */}
<Axis name="month" label={{
formatter(text, item, index) {
// 格式化:2022-01-01 -> 0101
return `${text.split('-').slice(1).join('')}`;
}
}} /> <Axis name="per" title /> {/* 自定义 tooltip */}
<CustTooltip enterable >
{(title, items) => {
return <div>
{
items.map((x, i) => {
let oData = x.point._origin
return <div>
{Object.is(i, 0) && <Title level={5}>{oData.month}</Title>}
<section style={{ marginTop: '20px' }}>
<Title style={{ color: x.color, fontWeight: 'bold' }} level={5}>{oData.city}</Title>
<Space direction="vertical" size={2}>
<Text>幸福指数:{oData.happiness}</Text>
<Link href="hello" target="_blank">
跳转
</Link>
<Link onClick={() => {
Modal.info({
title: 'title',
content: oData.msg1
});
}}>
详情
</Link>
</Space>
</section>
</div>
})
}
</div>
}}
</CustTooltip> <Geom type="line" position="month*per"
// 两条线
size={2}
// 使线条平滑
// shape={"smooth"}
color={"city"}
/>
</BxChart>
</Card>
)
}
饼状图(统计苹果和梨子)
// src\pages\mdashboard\PieChart.js

import React from 'react';
import { Typography} from 'antd';
import {
Chart,
Geom,
Axis,
Tooltip,
Coord,
Label,
Legend
} from 'bizcharts';
import DataSet from '@antv/data-set'; // chartHeight 默认高度 250px ,用于适配
export default function (props = {chartHeight: 250}) {
const { Text } = Typography; const { DataView } = DataSet;
const data = [
{
item: '苹果',
count: 10,
},
{
item: '梨子',
count: 20,
},
];
const dv = new DataView();
dv.source(data).transform({
type: 'percent',
field: 'count',
dimension: 'item',
as: 'percent',
});
const cols = {
percent: {
formatter: val => {
val = val * 100 + '%';
return val;
},
},
};
function getXY(c, { index: idx = 0, field = 'percent', radius = 0.5 }) {
const d = c.get('data');
if (idx > d.length) return;
const scales = c.get('scales');
let sum = 0;
for (let i = 0; i < idx + 1; i++) {
let val = d[i][field];
if (i === idx) {
val = val / 2;
}
sum += val;
}
const pt = {
y: scales[field].scale(sum),
x: radius,
};
const coord = c.get('coord');
let xy = coord.convert(pt);
return xy;
}
return (
<section>
<Text>统计苹果和梨子</Text>
<Chart
height={props.chartHeight}
// 内容显示不完整(见 bizcharts 实战部分)
padding={[20, 150, 20, 40]}
data={dv}
scale={cols}
forceFit
onGetG2Instance={c => {
const xy = getXY(c, { index: 0 });
c.showTooltip(xy);
}}
>
<Legend position="right-center" />
<Coord type="theta" radius={1} />
<Axis name="percent" />
<Tooltip
showTitle={false}
itemTpl='<li><span style="background-color:{color};" class="g2-tooltip-marker"></span>{name}: {value}</li>'
/>
<Geom
type="intervalStack"
position="percent"
color="item"
tooltip={[
'item*percent',
(item, percent) => {
// 处理 33.33333333% -> 33.33
percent = (percent * 100).toFixed(2) + '%';
return {
name: item,
value: percent,
};
},
]}
style={{
lineWidth: 1,
stroke: '#fff',
}}
>
<Label
content="count"
formatter={(val, item) => {
return item.point.item + ': ' + val;
}}
/>
</Geom>
</Chart>
</section>
);
}
柱状图(堆叠柱状图)
// src\pages\mdashboard\BarChart.js

import React from "react";
import { Typography, Space } from 'antd'
import {
Chart,
Geom,
Axis,
Tooltip,
Coord,
Legend,
} from "bizcharts";
import useCustTooltip from 'bx-tooltip';
import DataSet from "@antv/data-set"; export default function (props = {barHeight: 240}) {
const [BxChart, CustTooltip] = useCustTooltip.create(Chart, Tooltip);
const { Text,Title } = Typography;
const retains = ["State", '总比例', 'bad', 'good', 'Total']
const fields = ["好的比例", "坏的比例"]
const data = [
{
State: "苹果(红富士、糖心苹果)",
good: 50,
bad: 150,
Total: 200,
好的比例: 25,
坏的比例: 75,
总比例: 100
},
{
State: "梨子(香梨)",
good: 75,
bad: 125,
Total: 200,
好的比例: 37.5,
坏的比例: 62.5,
总比例: 100
},
]; const ds = new DataSet();
const dv = ds.createView().source(data); dv.transform({
type: "fold",
fields: fields,
key: "比例",
value: "百分总计",
retains: retains // 保留字段集,默认为除fields以外的所有字段
}); return (
<section>
<Text>堆叠柱状图</Text>
<BxChart height={props.barHeight} data={dv} padding={[30, 80, 20, 40]} forceFit>
<Legend position="right-center" />
<Coord />
<Axis
name="State"
label={{
offset: 12,
formatter(text, item, index) {
// 最多显示 10 个,多余省略。详细的在 tooltip 中显示
const maxNum = 10
return text.length > maxNum ? text.split('').slice(0, maxNum - 3).join('') + '...' : text
}
}}
/>
<CustTooltip enterable >
{(title, items) => {
return <div>
{
items.map((x, i) => {
// 取得原始数据
let oData = x.point._origin
return <div>
{Object.is(i, 0) && <Title level={5}>{oData.State}</Title>}
<section style={{ marginTop: '20px' }}>
<Space direction="vertical" size={2}>
<Text style={{ color: x.color, fontWeight: 'bold' }}>{oData['比例']}:{oData['百分总计']}%</Text>
<Text>good数量:{oData['good']}</Text>
<Text>bad数量:{oData['bad']}</Text>
<Text>总数量:{oData['Total']}</Text>
</Space>
</section>
</div>
})
}
</div>
}}
</CustTooltip>
<Geom
type="intervalStack"
position="State*百分总计"
color={"比例"}
>
</Geom>
</BxChart>
</section>
);
}
store.js
// src\pages\mdashboard\store.js

import { observable, computed } from 'mobx';
import http from 'libs/http'; const PADDING = 16
class Store {
// 表格数据
@observable records = []; // 是否正在请求数据
@observable isFetching = false; // 数据源
@computed get dataSource() {
return this.records
} fetchRecords = () => {
this.isFetching = true;
http.get('/api/mdashboard/table')
// todo 接口格式或许会调整
.then(res => this.records = res)
.finally(() => this.isFetching = false)
}; /* 适配相关 */
// 盒子高度,padding 用于给顶部和底部留点空隙。
// 由于笔者没有设计,所以先用 px 实现,之后在在将固定高度改为响应式,937 是固定高度实现后测量出的高度。
@observable baseBoxHeight = 937 - PADDING
@observable padding = PADDING
// 需要用 this 调用 padding 变量,即 `this.padding`
@observable boxHeight = window.innerHeight - this.padding * 2 // 饼图高度比例
@observable pieBoxRatio = 0.20 // 柱状图高度比例
@observable barBoxRatio = 0.23 // “My Dashboard 我的仪表盘”
@computed get TitleHeight() {
const ratio = 80 / this.baseBoxHeight
return this.boxHeight * ratio
}
// 运行card高度
@computed get todayCardHeight() {
const ratio = 75 / this.baseBoxHeight
return this.boxHeight * ratio
} // “饼图+描述列表+柱状图” body 高度
@computed get statisticBodyHeight() {
const ratio = 660 / this.baseBoxHeight
return this.boxHeight * ratio
} // 居住趋势 body 的
@computed get trendBodyBodyHeight() {
const ratio = 385 / this.baseBoxHeight
return this.boxHeight * ratio
} // 水果信息高度
@computed get configTableHeight() {
const ratio = 353 / this.baseBoxHeight
return this.boxHeight * ratio
} // xys16 得用 computed 才会联动。下面这种写法不会联动
// @observable xys16 = (16 / this.baseBoxHeight) * this.boxHeight
@computed get xys16() {
return (16 / this.baseBoxHeight) * this.boxHeight
} @computed get xys12() {
return (12 / this.baseBoxHeight) * this.boxHeight
} @computed get xys36() {
return (36 / this.baseBoxHeight) * this.boxHeight
} @computed get xys24() {
return (24 / this.baseBoxHeight) * this.boxHeight
} @computed get xys78() {
return (78 / this.baseBoxHeight) * this.boxHeight
} @computed get pieBoxHeight() {
return this.pieBoxRatio * this.boxHeight
} @computed get barBoxHeight() {
return this.barBoxRatio * this.boxHeight
} // card 的 header
@computed get cardTitleStyle() {
const cardTitleRatio = 57 / this.baseBoxHeight
return { display: 'flex', height: this.boxHeight * cardTitleRatio, alignItems: 'center', justifyContent: 'center' }
}
/* /适配相关 */
} export default new Store()
Dashboard.js
// src\pages\mdashboard\Dashboard.js

import React, {useEffect, Fragment} from 'react';
import { Row, Col, Card, Descriptions, Typography, Divider } from 'antd';
import AlarmTrend from './Trend';
import Piechart from './PieChart'
import CusTable from './Table';
import CusBarChart from './BarChart';
import Styles from './index.module.less'
import { observer } from 'mobx-react';
import store from './store' export default observer(function () {
// Typography排版
const { Text } = Typography; useEffect(() => {
// 响应式
window.addEventListener("resize", function(){
// padding,用于留点间距出来
store.boxHeight = window.innerHeight - store.padding * 2
}, false);
}, []) return (
// Fragment 用于包裹多个元素,却不会被渲染到 dom
<Fragment>
{/* 使用单一的一组 Row 和 Col 栅格组件,就可以创建一个基本的栅格系统,所有列(Col)必须放在 Row 内。 */}
<Row style={{ marginBottom: store.xys16 }}>
<Col span={24}>
{/* 可以省略 px */}
{/* 如果将字体和padding 改为响应式,height 设置或不设置还是有差别的,设置 height 会更准确 */}
<Card bodyStyle={{display: 'flex', height: store.TitleHeight, justifyContent: 'center', padding: store.xys12, fontSize: store.xys36, fontWeight: 700, }}>
<Text>My Dashboard 我的仪表盘</Text>
</Card>
</Col>
</Row>
<Row gutter={16}>
<Col span={8}>
{/* gutter:水平垂直间距都是 响应式 16 */}
<Row gutter={[store.xys16, store.xys16]}>
{/* 24 栅格系统。 */}
<Col span={24}>
{/* 垂直居中 */}
<Card bodyStyle={{ display: 'flex', height: store.todayCardHeight, alignItems: 'center'}}>
{/* 文字大小 */}
<span>
<Text style={{ fontSize: store.xys16}}>
运行为绿色,否则为灰色:
<span className={Styles.react}></span>
<span>运行</span>
</Text>
</span>
</Card>
</Col>
<Col span={24}>
<Card title="饼图+描述列表+柱状图" headStyle={store.cardTitleStyle} bodyStyle={{height: store.statisticBodyHeight}}>
<Piechart chartHeight={store.pieBoxHeight}/>
<Divider style={{margin: `${store.xys12}px 0`}}/>
{/* Descriptions描述列表,常见于详情页的信息展示。这里总是显示两列。 */}
{/* spug 中“Dashboard”的“最近30天登录”是用的就是Descriptions,缺点是不像 table 对齐。当文字长度不同,会看起来错乱。 */}
{/* 样式,用于适配,即垂直居中 */}
<Descriptions column={2} style={{display: 'flex', alignItems: 'center', minHeight: store.xys78}}>
<Descriptions.Item label="Descriptions">描述列表</Descriptions.Item>
<Descriptions.Item label="梨子">5个</Descriptions.Item>
<Descriptions.Item label="购买时间">2022-04-21</Descriptions.Item>
<Descriptions.Item label="购买途径">
<Text
style={{ width: 100 }}
ellipsis={{ tooltip: '看不完整就将鼠标移上来' }}>
看不完整就将鼠标移上来
{/* 超A、超B、超C、超D, */}
</Text>
</Descriptions.Item>
</Descriptions>
<Divider style={{margin: `${store.xys12}px 0`}}/>
<CusBarChart barHeight={store.barBoxHeight}/>
</Card>
</Col>
</Row>
</Col>
<Col span={16} >
<Row gutter={[store.xys16, store.xys16]}>
<Col span={24}>
<AlarmTrend cardBodyHeight={store.trendBodyBodyHeight}/>
</Col>
<Col span={24}>
<CusTable tableHeight={store.configTableHeight}/>
</Col>
</Row>
</Col>
</Row>
</Fragment>
)
})
index.js
// src\pages\mdashboard\index.js

import React from 'react';
import { AuthDiv } from 'components';
import Dashboard from './Dashboard';
import styles from './index.module.less' export default function () {
return (
<section className={styles.tdashboardBox}>
<AuthDiv auth="testdashboard.testdashboard.view">
<Dashboard />
</AuthDiv>
</section>
)
}
tIndex.js
// src\pages\mdashboard\tIndex.js

// 无需权限即可访问

import React from 'react';
import Dashboard from './Dashboard';
import store from './store';
import styles from './index.module.less' export default function () {
return (
<section className={styles.tdashboardBox} style={{padding: `${store.padding}px 16px`, backgroundColor: 'rgb(125 164 222)', height: '100vh'}}>
<Dashboard/>
</section>
)
}

重启服务,效果如下:

bizcharts

bizcharts 是阿里的一个图表组件库。

:spug 项目中使用的版本是 3.x。参考文档时不要搞错。

API文档

上面我们安装的其中一个依赖包 bx-tooltip 就来自这里。

实战

实战其实就是一些 bizcharts 使用上的一些答疑。例如“内容显示不完整”,有可能就是因为 padding 的原因。

图表示例

例如我们使用的堆叠柱状图的用法示例就参考这里:

点击进入示例,修改左边源码,右侧显示也会同步,非常方便我们在线研究和学习:

高度自适应

bizcharts 有宽度自适应,但没有实现高度的自适应。

笔者高度自适应的做法:将高度全部改为百分比。

具体做法如下:

  1. 由于没有设计,故先用固定像素实现界面
  2. 取得浏览器的窗口高度 window.innerHeight,笔者这里是 937
  3. 将“标签盒子”、“卡片头部高度”、卡片 body 部分等全部改为百分比

核心代码如下:

// src\pages\mdashboard\store.js

const PADDING = 16
class Store { @observable baseBoxHeight = 937 - PADDING @observable padding = PADDING // 仪表盘盒子高度
@observable boxHeight = window.innerHeight - this.padding * 2 // 饼图高度比例。根据之前的效果算出来的
@observable pieBoxRatio = 0.20 // 柱状图高度比例
@observable barBoxRatio = 0.23 // “My Dashboard 我的仪表盘” 高度
@computed get TitleHeight() {
const ratio = 80 / this.baseBoxHeight
return this.boxHeight * ratio
}
// 运行card高度
@computed get todayCardHeight() {
const ratio = 75 / this.baseBoxHeight
return this.boxHeight * ratio
} // “饼图+描述列表+柱状图” body 高度
@computed get statisticBodyHeight() {
const ratio = 660 / this.baseBoxHeight
return this.boxHeight * ratio
} // 居住趋势 body 的高度
@computed get trendBodyBodyHeight() {
const ratio = 385 / this.baseBoxHeight
return this.boxHeight * ratio
} // xys16 得用 computed 才会联动。下面这种写法不会联动
@computed get xys16() {
return (16 / this.baseBoxHeight) * this.boxHeight
} // 饼状图盒子高度
@computed get pieBoxHeight() {
return this.pieBoxRatio * this.boxHeight
} // card 的 header 比例
@computed get cardTitleStyle() {
const cardTitleRatio = 57 / this.baseBoxHeight
return { display: 'flex', height: this.boxHeight * cardTitleRatio, alignItems: 'center', justifyContent: 'center' }
}
}

问题

实现过程中出现如下两个问题:一个是折线图的 Y 轴乱序,一个是堆叠柱状图有一节空白

原因是不小心弄成了字符串,改为数字类型即可。

其他章节请看:

react实战 系列

react实战系列 —— 我的仪表盘(bizcharts、antd、moment)的更多相关文章

  1. react实战系列 —— 起步(mockjs、第一个模块、docusaurus)

    其他章节请看: react实战 系列 起步 本篇我们首先引入 mockjs ,然后进入 spug 系统,接着模仿"任务计划"模块实现一个类似的一级导航页面("My任务计划 ...

  2. react实战 系列 —— React 的数据流和生命周期

    其他章节请看: react实战 系列 数据流和生命周期 如何处理 React 中的数据,组件之间如何通信,数据在 React 中如何流动? 常用的 React 生命周期方法以及开源项目 spug 中使 ...

  3. react实战系列 —— React 中的表单和路由的原理

    其他章节请看: react实战 系列 React 中的表单和路由的原理 React 中的表单是否简单好用,受控组件和非受控是指什么? React 中的路由原理是什么,如何更好的理解 React 应用的 ...

  4. React 实战系列:模块化

    本系列以实战为主,通过一个 TODO 应用来学习深入 React. 学习无捷径,唯一的办法就是 coding!coding!coding! 如有不足之处,欢迎大家批评指正,共同进步! 言毕,开始撸

  5. react实战系列 —— react 的第一个组件

    react 的第一个组件 写了 react 有一个半月,现在又有半个月没写了,感觉对其仍旧比较陌生. 本文分两部分,首先聊一下 react 的相关概念,然后不使用任何语法糖(包括 jsx)或可能隐藏底 ...

  6. 七天接手react项目 系列 —— 尾篇(antd 和 mobx)

    其他章节请看: 七天接手react项目 系列 尾篇 前面我们依次学习了 react 基础知识.react 脚手架创建项目.react 路由,已经花费了不少时间,但距离接手 spug_web 项目还有一 ...

  7. React Native实战系列教程之自定义原生UI组件和VideoView视频播放器开发

    React Native实战系列教程之自定义原生UI组件和VideoView视频播放器开发   2016/09/23 |  React Native技术文章 |  Sky丶清|  4 条评论 |  1 ...

  8. 从零开始react实战:云书签-1 react环境搭建

    总览篇:react 实战之云书签 本篇是实战系列的第一篇,主要是搭建 react 开发环境,在create-react-app的基础上加上如下功能: antd 组件库按需引入 ,支持主题定制 支持 l ...

  9. Spark入门实战系列--1.Spark及其生态圈简介

    [注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .简介 1.1 Spark简介 年6月进入Apache成为孵化项目,8个月后成为Apache ...

随机推荐

  1. SpringDataJdbc多数据源

    代码基于 SpringBoot + SpringDataJDBC + Mybatis 架构 介绍使用多数据源的关键的一些类:AbstractRoutingDataSource继承实现determine ...

  2. 解释Spring框架中bean的生命周期?

    Spring容器 从XML 文件中读取bean的定义,并实例化bean. Spring根据bean的定义填充所有的属性. 如果bean实现了BeanNameAware 接口,Spring 传递bean ...

  3. java-iov概念

    Ioc-Inversion of Control 即"控制反转",不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象 ...

  4. 全方位讲解 Nebula Graph 索引原理和使用

    本文首发于 Nebula Graph Community 公众号 index not found?找不到索引?为什么我要创建 Nebula Graph 索引?什么时候要用到 Nebula Graph ...

  5. iView 一周年了,同时发布了 2.0 正式版,但这只是开始...

    两年前,我开始接触 Vue.js 框架,当时就被它的轻量.组件化和友好的 API 所吸引.之后我将 Vue.js 和 Webpack 技术栈引入我的公司(TalkingData)可视化团队,并经过一年 ...

  6. 一个模仿微信群聊的H5页面

    开始 上半年小米Max发布的时候,做了一个在朋友圈传播的模仿微信的群聊界面H5页面:一群公司的大咖在群里聊小米Max,用户可以向大咖们提问,以此了解产品. 页面的主体是群聊对话,同时在对话中包含了很多 ...

  7. js 简易模块加载器 示例分析

    前端模块化 关注前端技术发展的各位亲们,肯定对模块化开发这个名词不陌生.随着前端工程越来越复杂,代码越来越多,模块化成了必不可免的趋势. 各种标准 由于javascript本身并没有制定相关标准(当然 ...

  8. 《深入理解ES6》笔记—— JavaScript中的类class(9)

    ES5中的近类结构 ES5以及之前的版本,没有类的概念,但是聪明的JavaScript开发者,为了实现面向对象,创建了特殊的近类结构. ES5中创建类的方法:新建一个构造函数,定义一个方法并且赋值给构 ...

  9. 快速安装 kafka 集群

    前言 最近因为工作原因,需要安装一个 kafka 集群,目前网络上有很多相关的教程,按着步骤来也能完成安装,只是这些教程都显得略微繁琐.因此,我写了这篇文章帮助大家快速完成 kafka 集群安装.   ...

  10. MySQL数据库设置编码格式和时区

    MySQL数据库设置编码格式和时区 MySQL5版本: url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8 MySQL6版本及以上 ...