其他章節請看:
My Dashboard
上一篇我們在 spug 專案中模仿”任務計劃“模組實現一個類似的一級導航頁面(”My任務計劃“),本篇,我們將模仿“Dashboard”來實現一個儀表盤“My Dashboard”。
主要涉及 antd 的 Grid
、Card
、Descriptions
等元件、bizcharts
的使用、moment
日期庫和頁面適配。
注:實現的程式碼在上一篇的基礎上展開。
Dashboard
介面如下:
裡面用到了:
- antd 的
Grid
、Card
、Descriptions
描述列表 (文字長度不同,有時會感覺沒對齊) 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 有寬度自適應,但沒有實現高度的自適應。
筆者高度自適應的做法:將高度全部改為百分比。
具體做法如下:
- 由於沒有設計,故先用固定畫素實現介面
- 取得瀏覽器的視窗高度 window.innerHeight,筆者這裡是 937
- 將“標籤盒子”、“卡片頭部高度”、卡片 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 軸亂序
,一個是堆疊柱狀圖有一節空白
。
原因是值不小心弄成了字串
,改為數字
型別即可。
其他章節請看: