feat: basic dashboard view

This commit is contained in:
moonrailgun 2023-09-02 00:52:43 +08:00
parent bf6484c07f
commit ca90003467
9 changed files with 2813 additions and 25 deletions

View File

@ -8,10 +8,12 @@
"build": "vite build" "build": "vite build"
}, },
"dependencies": { "dependencies": {
"@ant-design/charts": "^1.4.2",
"@ant-design/icons": "^5.2.5", "@ant-design/icons": "^5.2.5",
"antd": "^5.8.5", "antd": "^5.8.5",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"express": "^4.18.2", "express": "^4.18.2",
"lodash-es": "^4.17.21",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router": "^6.15.0", "react-router": "^6.15.0",
@ -22,6 +24,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/lodash-es": "^4.17.9",
"@types/node": "^18.17.12", "@types/node": "^18.17.12",
"@types/react": "^18.2.21", "@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
import React from 'react';
import { Select } from 'antd';
import { compact } from 'lodash-es';
export const DateFilter: React.FC<{
showAllTime?: boolean;
}> = React.memo((props) => {
const options = compact([
{ label: 'Today', value: '1day' },
{
label: 'Last 24 hours',
value: '24hour',
},
{
label: 'Yesterday',
value: '-1day',
},
{
label: 'This Week',
value: '1week',
},
{
label: 'Last 7 days',
value: '7day',
},
{
label: 'This Month',
value: '1month',
},
{
label: 'Last 30 days',
value: '30day',
},
{
label: 'Last 90 days',
value: '90day',
},
{ label: 'This year', value: '1year' },
props.showAllTime === true && {
label: 'All time',
value: 'all',
},
{
label: 'Custom range',
value: 'custom',
},
]);
return (
<div>
<Select
className="min-w-[10rem]"
size="large"
options={options}
defaultValue="24hour"
/>
</div>
);
});
DateFilter.displayName = 'DateFilter';

View File

@ -0,0 +1,29 @@
import clsx from 'clsx';
import React from 'react';
type HealthStatus = 'health' | 'error' | 'warning' | 'none';
interface HealthBarProps {
beats: { title?: string; status: HealthStatus }[];
}
export const HealthBar: React.FC<HealthBarProps> = React.memo((props) => {
return (
<div className="flex">
{props.beats.map((beat) => (
<div
title={beat.title}
className={clsx(
'rounded-full w-1 h-4 m-0.5 hover:scale-150 transition-transform',
{
'bg-green-500': beat.status === 'health',
'bg-red-600': beat.status === 'error',
'bg-yellow-400': beat.status === 'warning',
'bg-gray-400': beat.status === 'none',
}
)}
/>
))}
</div>
);
});
HealthBar.displayName = 'HealthBar';

View File

@ -0,0 +1,147 @@
import { Button, Tag } from 'antd';
import React from 'react';
import { Column } from '@ant-design/charts';
import { ArrowRightOutlined, SyncOutlined } from '@ant-design/icons';
import { DateFilter } from './DateFilter';
import { HealthBar } from './HealthBar';
export const WebsiteOverview: React.FC = React.memo(() => {
return (
<div>
<div className="flex">
<div className="flex flex-1 text-2xl font-bold items-center">
<span className="mr-2">Tianji</span>
<HealthBar
beats={Array.from({ length: 13 }).map(() => ({ status: 'health' }))}
/>
</div>
<div>
<Button type="primary" size="large">
View Details <ArrowRightOutlined />
</Button>
</div>
</div>
<div className="flex mb-10 flex-wrap">
<div className="flex gap-5 flex-wrap w-full lg:w-2/3">
<MetricCard label="Views" value={20} diff={20} />
<MetricCard label="Visitors" value={20} diff={20} />
<MetricCard label="Bounce rate" value={20} diff={-20} unit="%" />
<MetricCard
label="Average visit time"
value={20}
diff={-20}
unit="s"
/>
</div>
<div className="flex items-center gap-2 justify-end w-full lg:w-1/3">
<Button size="large" icon={<SyncOutlined />} />
<DateFilter />
</div>
</div>
<div>
<DemoChart />
</div>
</div>
);
});
WebsiteOverview.displayName = 'WebsiteOverview';
const MetricCard: React.FC<{
label: string;
value: number;
diff: number;
unit?: string;
}> = React.memo((props) => {
const unit = props.unit ?? '';
return (
<div className="flex flex-col justify-center min-w-[140px] min-h-[90px]">
<div className="flex items-center whitespace-nowrap font-bold text-4xl">
{String(props.value)}
{unit}
</div>
<div className="flex items-center whitespace-nowrap font-bold">
<span className="mr-2">{props.label}</span>
<Tag color={props.diff >= 0 ? 'green' : 'red'}>
{props.diff >= 0 ? `+${props.diff}${unit}` : `${props.diff}${unit}`}
</Tag>
</div>
</div>
);
});
MetricCard.displayName = 'MetricCard';
export const DemoChart: React.FC = React.memo(() => {
const data = [
{
type: '家具家电',
sales: 38,
},
{
type: '粮油副食',
sales: 52,
},
{
type: '生鲜水果',
sales: 61,
},
{
type: '美容洗护',
sales: 145,
},
{
type: '母婴用品',
sales: 48,
},
{
type: '进口食品',
sales: 38,
},
{
type: '食品饮料',
sales: 38,
},
{
type: '家庭清洁',
sales: 38,
},
];
const config = {
data,
xField: 'type',
yField: 'sales',
label: {
// 可手动配置 label 数据标签位置
position: 'middle' as const,
// 'top', 'bottom', 'middle',
// 配置样式
style: {
fill: '#FFFFFF',
opacity: 0.6,
},
},
xAxis: {
label: {
autoHide: true,
autoRotate: false,
},
},
meta: {
type: {
alias: '类别',
},
sales: {
alias: '销售额',
},
},
};
return <Column {...config} />;
});
DemoChart.displayName = 'DemoChart';

View File

@ -1,3 +1,24 @@
@import 'antd/dist/reset.css';
/* fix Tailwind CSS border styles,form Tailwind CSS's preflight */
*,
::before,
::after {
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: theme('borderColor.DEFAULT', currentColor); /* 2 */
}
::before,
::after {
--tw-content: '';
}
a {
text-decoration: none;
}
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;

View File

@ -1,6 +1,23 @@
import React from 'react'; import React from 'react';
import { EditOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import { WebsiteOverview } from '../components/WebsiteOverview';
export const Dashboard: React.FC = React.memo(() => { export const Dashboard: React.FC = React.memo(() => {
return <div>Dashboard</div>; return (
<div>
<div className="h-24 flex items-center">
<div className="text-2xl flex-1">Dashboard</div>
<div>
<Button icon={<EditOutlined />} size="large">
Edit
</Button>
</div>
</div>
<div>
<WebsiteOverview />
</div>
</div>
);
}); });
Dashboard.displayName = 'Dashboard'; Dashboard.displayName = 'Dashboard';

View File

@ -9,7 +9,7 @@ export const Layout: React.FC = React.memo(() => {
<div> <div>
<div className="flex items-center bg-gray-100 px-4"> <div className="flex items-center bg-gray-100 px-4">
<div className="px-2 mr-10 font-bold">Tianji</div> <div className="px-2 mr-10 font-bold">Tianji</div>
<div className="flex space-x-4"> <div className="flex gap-8">
<NavItem to="/dashboard" label="Dashboard" /> <NavItem to="/dashboard" label="Dashboard" />
<NavItem to="/monitor" label="Monitor" /> <NavItem to="/monitor" label="Monitor" />
<NavItem to="/website" label="Website" /> <NavItem to="/website" label="Website" />
@ -31,15 +31,11 @@ export const Layout: React.FC = React.memo(() => {
], ],
}} }}
> >
<Button <Button shape="circle" size="large" icon={<UserOutlined />} />
shape="circle"
size="large"
icon={<UserOutlined />}
></Button>
</Dropdown> </Dropdown>
</div> </div>
</div> </div>
<div> <div className="max-w-7xl m-auto px-4">
<Outlet /> <Outlet />
</div> </div>
</div> </div>

View File

@ -4,5 +4,8 @@ module.exports = {
theme: { theme: {
extend: {}, extend: {},
}, },
corePlugins: {
preflight: false,
},
plugins: [], plugins: [],
} }