perf: improve mobile display
This commit is contained in:
parent
6606b253d8
commit
b2fb1832e1
@ -4,12 +4,22 @@ import { DateUnit } from '@tianji/shared';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { formatDate, formatDateWithUnit } from '../utils/date';
|
import { formatDate, formatDateWithUnit } from '../utils/date';
|
||||||
import { Column, ColumnConfig } from '@ant-design/charts';
|
import { Column, ColumnConfig } from '@ant-design/charts';
|
||||||
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const TimeEventChart: React.FC<{
|
export const TimeEventChart: React.FC<{
|
||||||
|
labelMapping?: Record<string, string>;
|
||||||
data: { x: string; y: number; type: string }[];
|
data: { x: string; y: number; type: string }[];
|
||||||
unit: DateUnit;
|
unit: DateUnit;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const labelMapping = props.labelMapping ?? {
|
||||||
|
pageview: t('pageview'),
|
||||||
|
session: t('session'),
|
||||||
|
};
|
||||||
|
|
||||||
const config = useMemo(
|
const config = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -30,6 +40,13 @@ export const TimeEventChart: React.FC<{
|
|||||||
title: (t) => formatDate(t),
|
title: (t) => formatDate(t),
|
||||||
},
|
},
|
||||||
color: [colors.chart.pv, colors.chart.uv],
|
color: [colors.chart.pv, colors.chart.uv],
|
||||||
|
legend: isMobile
|
||||||
|
? false
|
||||||
|
: {
|
||||||
|
itemName: {
|
||||||
|
formatter: (text) => labelMapping[text] ?? text,
|
||||||
|
},
|
||||||
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
label: {
|
label: {
|
||||||
autoHide: true,
|
autoHide: true,
|
||||||
@ -38,7 +55,7 @@ export const TimeEventChart: React.FC<{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}) satisfies ColumnConfig,
|
}) satisfies ColumnConfig,
|
||||||
[props.data, props.unit]
|
[props.data, props.unit, props.labelMapping]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Column {...config} />;
|
return <Column {...config} />;
|
||||||
|
@ -177,7 +177,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
<div className="h-full w-full overflow-auto">
|
<div className="h-full w-full overflow-auto">
|
||||||
<Spin spinning={isLoading}>
|
<Spin spinning={isLoading}>
|
||||||
<Space className="w-full" direction="vertical">
|
<Space className="w-full" direction="vertical">
|
||||||
<div className="flex justify-between">
|
<div className="flex flex-col-reverse sm:flex-row sm:justify-between">
|
||||||
<Space direction="vertical">
|
<Space direction="vertical">
|
||||||
<div>
|
<div>
|
||||||
<ColorTag label={monitorInfo.type} />
|
<ColorTag label={monitorInfo.type} />
|
||||||
|
@ -11,7 +11,7 @@ export const MonitorStatsBlock: React.FC<MonitorStatsBlockProps> = React.memo(
|
|||||||
(props) => {
|
(props) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-0.5 font-bold">
|
<div className="mb-0.5 text-xs font-bold sm:text-base">
|
||||||
{props.title}
|
{props.title}
|
||||||
{props.tooltip && (
|
{props.tooltip && (
|
||||||
<TipIcon className="ml-1" content={props.tooltip} />
|
<TipIcon className="ml-1" content={props.tooltip} />
|
||||||
|
@ -17,7 +17,7 @@ export const WebsiteOnlineCount: React.FC<{
|
|||||||
if (typeof count === 'number' && count > 0) {
|
if (typeof count === 'number' && count > 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="h-2.5 w-2.5 rounded-full bg-green-500" />
|
<div className="h-2.5 w-2.5 flex-shrink-0 rounded-full bg-green-500" />
|
||||||
<span>
|
<span>
|
||||||
{count} {t('current visitor')}
|
{count} {t('current visitor')}
|
||||||
</span>
|
</span>
|
||||||
|
@ -83,8 +83,8 @@ export const WebsiteOverview: React.FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<div className="flex">
|
<div className="flex flex-col-reverse sm:flex-row">
|
||||||
<div className="flex flex-1 items-center text-2xl font-bold">
|
<div className="flex flex-1 flex-col gap-2 text-2xl font-bold sm:flex-row sm:items-center">
|
||||||
<span className="mr-2" title={website.domain ?? ''}>
|
<span className="mr-2" title={website.domain ?? ''}>
|
||||||
{website.name}
|
{website.name}
|
||||||
</span>
|
</span>
|
||||||
@ -102,7 +102,7 @@ export const WebsiteOverview: React.FC<{
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="ml-4 text-base font-normal">
|
<div className="text-base font-normal">
|
||||||
<WebsiteOnlineCount
|
<WebsiteOnlineCount
|
||||||
workspaceId={website.workspaceId}
|
workspaceId={website.workspaceId}
|
||||||
websiteId={website.id}
|
websiteId={website.id}
|
||||||
|
34
src/client/pages/Layout/Menu.tsx
Normal file
34
src/client/pages/Layout/Menu.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||||
|
import { LuMenu } from 'react-icons/lu';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import { useGlobalEventSubscribe } from '@/utils/event';
|
||||||
|
|
||||||
|
export const MobileLayoutMenu: React.FC<{
|
||||||
|
list?: React.ReactNode;
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useGlobalEventSubscribe('commonListSelected', () => {
|
||||||
|
setOpen(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!props.list) {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet open={open} onOpenChange={setOpen}>
|
||||||
|
<SheetTrigger>
|
||||||
|
<Button variant="outline" size="icon">
|
||||||
|
<LuMenu />
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent side="left" className="w-11/12">
|
||||||
|
<ScrollArea className="h-full">{props.list}</ScrollArea>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
MobileLayoutMenu.displayName = 'MobileLayoutMenu';
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||||||
import {
|
import {
|
||||||
LuAreaChart,
|
LuAreaChart,
|
||||||
LuFilePieChart,
|
LuFilePieChart,
|
||||||
LuMenu,
|
|
||||||
LuMonitorDot,
|
LuMonitorDot,
|
||||||
LuMoreVertical,
|
LuMoreVertical,
|
||||||
LuServer,
|
LuServer,
|
||||||
@ -16,19 +15,8 @@ import { cn } from '@/utils/style';
|
|||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { LayoutProps } from './types';
|
import { LayoutProps } from './types';
|
||||||
import { UserConfig } from './UserConfig';
|
import { UserConfig } from './UserConfig';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer';
|
||||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
import { MobileLayoutMenu } from './Menu';
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Drawer,
|
|
||||||
DrawerClose,
|
|
||||||
DrawerContent,
|
|
||||||
DrawerDescription,
|
|
||||||
DrawerFooter,
|
|
||||||
DrawerHeader,
|
|
||||||
DrawerTitle,
|
|
||||||
DrawerTrigger,
|
|
||||||
} from '@/components/ui/drawer';
|
|
||||||
|
|
||||||
export const MobileLayout: React.FC<LayoutProps> = React.memo((props) => {
|
export const MobileLayout: React.FC<LayoutProps> = React.memo((props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -36,20 +24,7 @@ export const MobileLayout: React.FC<LayoutProps> = React.memo((props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-svh flex-col">
|
<div className="flex h-svh flex-col">
|
||||||
<div className="flex h-[52px] items-center justify-between px-2">
|
<div className="flex h-[52px] items-center justify-between px-2">
|
||||||
<Sheet>
|
<MobileLayoutMenu list={props.list} />
|
||||||
<SheetTrigger disabled={!Boolean(props.list)}>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
disabled={!Boolean(props.list)}
|
|
||||||
>
|
|
||||||
<LuMenu />
|
|
||||||
</Button>
|
|
||||||
</SheetTrigger>
|
|
||||||
<SheetContent side="left" className="w-11/12">
|
|
||||||
<ScrollArea className="h-full">{props.list}</ScrollArea>
|
|
||||||
</SheetContent>
|
|
||||||
</Sheet>
|
|
||||||
|
|
||||||
<div className="rounded-md dark:bg-white/10">
|
<div className="rounded-md dark:bg-white/10">
|
||||||
<img className="m-auto h-8 w-8" src="/icon.svg" />
|
<img className="m-auto h-8 w-8" src="/icon.svg" />
|
||||||
|
@ -172,7 +172,10 @@ function TelemetryDetailComponent() {
|
|||||||
/>
|
/>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
|
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="min-h-[470px] !w-full sm:!w-1/3"
|
||||||
|
>
|
||||||
<TelemetryMetricsTable
|
<TelemetryMetricsTable
|
||||||
telemetryId={telemetryId}
|
telemetryId={telemetryId}
|
||||||
type="source"
|
type="source"
|
||||||
@ -182,7 +185,10 @@ function TelemetryDetailComponent() {
|
|||||||
/>
|
/>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
|
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="min-h-[470px] !w-full sm:!w-1/3"
|
||||||
|
>
|
||||||
<TelemetryMetricsTable
|
<TelemetryMetricsTable
|
||||||
telemetryId={telemetryId}
|
telemetryId={telemetryId}
|
||||||
type="event"
|
type="event"
|
||||||
@ -192,7 +198,10 @@ function TelemetryDetailComponent() {
|
|||||||
/>
|
/>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
|
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="min-h-[470px] !w-full sm:!w-1/3"
|
||||||
|
>
|
||||||
<TelemetryMetricsTable
|
<TelemetryMetricsTable
|
||||||
telemetryId={telemetryId}
|
telemetryId={telemetryId}
|
||||||
type="country"
|
type="country"
|
||||||
|
@ -83,7 +83,10 @@ function WebsiteDetailComponent() {
|
|||||||
<Card.Grid hoverable={false} className="!w-full">
|
<Card.Grid hoverable={false} className="!w-full">
|
||||||
<WebsiteOverview website={website} showDateFilter={true} />
|
<WebsiteOverview website={website} showDateFilter={true} />
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/2">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="!w-full sm:min-h-[470px] sm:!w-1/2"
|
||||||
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="url"
|
type="url"
|
||||||
@ -92,7 +95,10 @@ function WebsiteDetailComponent() {
|
|||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/2">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="!w-full sm:min-h-[470px] sm:!w-1/2"
|
||||||
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="referrer"
|
type="referrer"
|
||||||
@ -101,7 +107,10 @@ function WebsiteDetailComponent() {
|
|||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||||
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="browser"
|
type="browser"
|
||||||
@ -110,7 +119,10 @@ function WebsiteDetailComponent() {
|
|||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||||
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="os"
|
type="os"
|
||||||
@ -119,7 +131,10 @@ function WebsiteDetailComponent() {
|
|||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||||
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="device"
|
type="device"
|
||||||
@ -128,7 +143,10 @@ function WebsiteDetailComponent() {
|
|||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||||
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="title"
|
type="title"
|
||||||
@ -137,7 +155,10 @@ function WebsiteDetailComponent() {
|
|||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||||
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="country"
|
type="country"
|
||||||
@ -150,7 +171,10 @@ function WebsiteDetailComponent() {
|
|||||||
<WebsiteVisitorMapBtn websiteId={websiteId} />
|
<WebsiteVisitorMapBtn websiteId={websiteId} />
|
||||||
</div>
|
</div>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
<Card.Grid
|
||||||
|
hoverable={false}
|
||||||
|
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||||
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="event"
|
type="event"
|
||||||
|
24
src/client/utils/event.ts
Normal file
24
src/client/utils/event.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { EventEmitter } from 'eventemitter-strict';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
export interface GlobalEventMap {
|
||||||
|
commonListSelected: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const globalEventBus = new EventEmitter<GlobalEventMap>();
|
||||||
|
|
||||||
|
export function useGlobalEventSubscribe<T extends keyof GlobalEventMap>(
|
||||||
|
eventName: T,
|
||||||
|
callback: GlobalEventMap[T]
|
||||||
|
) {
|
||||||
|
const fn = useEvent(callback);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
globalEventBus.on(eventName, fn);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
globalEventBus.off(eventName, fn);
|
||||||
|
};
|
||||||
|
}, [eventName]);
|
||||||
|
}
|
@ -82,7 +82,7 @@ function HomepageMain() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="rounded-lg border-8 border-solid border-gray-200 shadow-lg">
|
<div className="rounded-lg border-8 border-solid border-gray-200 shadow-lg dark:border-gray-800">
|
||||||
<Carousel
|
<Carousel
|
||||||
className="cursor-move"
|
className="cursor-move"
|
||||||
showThumbs={false}
|
showThumbs={false}
|
||||||
|
Loading…
Reference in New Issue
Block a user