Skip to content

Commit a06e2a3

Browse files
committed
feat(platform): add detail route
1 parent dbf9c5d commit a06e2a3

File tree

30 files changed

+656
-253
lines changed

30 files changed

+656
-253
lines changed

packages/hooks/src/useImmer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ export type DraftFunction<S> = (draft: S) => void;
77
export type Updater<S> = (arg: S | DraftFunction<S>) => void;
88
export type ImmerHook<S> = [S, Updater<S>];
99

10+
export function useImmer<S>(): ImmerHook<S | undefined>;
1011
export function useImmer<S = any>(initialValue: S | (() => S)): ImmerHook<S>;
11-
export function useImmer(initialValue: any) {
12+
export function useImmer(initialValue?: any) {
1213
const [val, updateValue] = useState(() => freeze(typeof initialValue === 'function' ? initialValue() : initialValue, true));
1314
const setValue = useCallback((updater: any) => {
1415
if (typeof updater === 'function') updateValue(produce(updater));

packages/platform/src/app/Routes.tsx

Lines changed: 45 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ import AppExceptionRoute from './routes/exception/Exception';
1616
import AppLayout from './routes/layout/Layout';
1717
import AppLoginRoute from './routes/login/Login';
1818

19-
const AppAMapRoute = React.lazy(() => import('./routes/dashboard/amap/AMap'));
20-
const AppEChartsRoute = React.lazy(() => import('./routes/dashboard/echarts/ECharts'));
21-
22-
const AppStandardTableRoute = React.lazy(() => import('./routes/list/standard-table/StandardTable'));
23-
24-
const AppACLRoute = React.lazy(() => import('./routes/test/acl/ACL'));
25-
const AppHttpRoute = React.lazy(() => import('./routes/test/http/Http'));
19+
const ROUTES = {
20+
'/dashboard/amap': React.lazy(() => import('./routes/dashboard/amap/AMap')),
21+
'/dashboard/echarts': React.lazy(() => import('./routes/dashboard/echarts/ECharts')),
22+
'/list/standard-table': React.lazy(() => import('./routes/list/standard-table/StandardTable')),
23+
'/list/standard-table/:id': React.lazy(() => import('./routes/list/standard-table/detail/Detail')),
24+
'/test/acl': React.lazy(() => import('./routes/test/acl/ACL')),
25+
'/test/http': React.lazy(() => import('./routes/test/http/Http')),
26+
};
2627

2728
export interface RouteStateContextData {
2829
matchRoutes: RouteMatch<string, RouteItem>[] | null;
@@ -35,13 +36,6 @@ export type CanActivateFn = (route: RouteItem) => true | React.ReactElement;
3536

3637
export interface RouteData {
3738
title?: string;
38-
breadcrumb?:
39-
| {
40-
title?: string;
41-
link?: boolean;
42-
separator?: React.ReactNode;
43-
}
44-
| false;
4539
acl?:
4640
| {
4741
control: Control | Control[];
@@ -76,17 +70,13 @@ export function AppRoutes() {
7670
path: LOGIN_PATH,
7771
element: <AppLoginRoute />,
7872
data: {
79-
title: t('login', { ns: 'title' }),
73+
title: t('Login', { ns: 'title' }),
8074
},
8175
},
8276
{
8377
path: '/',
8478
element: <AppLayout />,
8579
data: {
86-
breadcrumb: {
87-
title: t('home', { ns: 'title' }),
88-
link: true,
89-
},
9080
canActivate: [tokenGuard],
9181
canActivateChild: [tokenGuard],
9282
},
@@ -97,103 +87,82 @@ export function AppRoutes() {
9787
},
9888
{
9989
path: 'dashboard',
100-
data: {
101-
breadcrumb: {
102-
title: t('dashboard.', { ns: 'title' }),
103-
},
104-
},
10590
children: [
91+
{
92+
index: true,
93+
element: <Navigate to="/exception/404" replace />,
94+
},
10695
{
10796
path: 'amap',
108-
element: (
109-
<React.Suspense fallback={<AppFCPLoader />}>
110-
<AppAMapRoute />
111-
</React.Suspense>
112-
),
97+
element: <React.Suspense fallback={<AppFCPLoader />}>{React.createElement(ROUTES['/dashboard/amap'])}</React.Suspense>,
11398
data: {
114-
title: t('dashboard.amap', { ns: 'title' }),
115-
breadcrumb: {
116-
link: true,
117-
},
118-
acl: ROUTES_ACL.dashboard.amap,
99+
title: t('AMap', { ns: 'title' }),
100+
acl: ROUTES_ACL['/dashboard/amap'],
119101
canActivate: [ACLGuard],
120102
},
121103
},
122104
{
123105
path: 'echarts',
124-
element: (
125-
<React.Suspense fallback={<AppFCPLoader />}>
126-
<AppEChartsRoute />
127-
</React.Suspense>
128-
),
106+
element: <React.Suspense fallback={<AppFCPLoader />}>{React.createElement(ROUTES['/dashboard/echarts'])}</React.Suspense>,
129107
data: {
130-
title: t('dashboard.echarts', { ns: 'title' }),
131-
breadcrumb: {
132-
link: true,
133-
},
134-
acl: ROUTES_ACL.dashboard.echarts,
108+
title: t('ECharts', { ns: 'title' }),
109+
acl: ROUTES_ACL['/dashboard/echarts'],
135110
canActivate: [ACLGuard],
136111
},
137112
},
138113
],
139114
},
140115
{
141116
path: 'list',
142-
data: {
143-
breadcrumb: {
144-
title: t('list.', { ns: 'title' }),
145-
},
146-
},
147117
children: [
118+
{
119+
index: true,
120+
element: <Navigate to="/exception/404" replace />,
121+
},
148122
{
149123
path: 'standard-table',
124+
element: <React.Suspense fallback={<AppFCPLoader />}>{React.createElement(ROUTES['/list/standard-table'])}</React.Suspense>,
125+
data: {
126+
title: t('Standard Table', { ns: 'title' }),
127+
acl: ROUTES_ACL['/list/standard-table'],
128+
canActivate: [ACLGuard],
129+
},
130+
},
131+
{
132+
path: 'standard-table/:id',
150133
element: (
151-
<React.Suspense fallback={<AppFCPLoader />}>
152-
<AppStandardTableRoute />
153-
</React.Suspense>
134+
<React.Suspense fallback={<AppFCPLoader />}>{React.createElement(ROUTES['/list/standard-table/:id'])}</React.Suspense>
154135
),
155136
data: {
156-
title: t('list.standard-table', { ns: 'title' }),
157-
breadcrumb: {
158-
link: true,
159-
},
160-
acl: ROUTES_ACL.list['standard-table'],
137+
title: t('Device Detail', { ns: 'title' }),
138+
acl: ROUTES_ACL['/list/standard-table/:id'],
161139
canActivate: [ACLGuard],
162140
},
163141
},
164142
],
165143
},
166144
{
167145
path: 'test',
168-
data: {
169-
breadcrumb: {
170-
title: t('test.', { ns: 'title' }),
171-
},
172-
},
173146
children: [
147+
{
148+
index: true,
149+
element: <Navigate to="/exception/404" replace />,
150+
},
174151
{
175152
path: 'acl',
176-
element: (
177-
<React.Suspense fallback={<AppFCPLoader />}>
178-
<AppACLRoute />
179-
</React.Suspense>
180-
),
153+
element: <React.Suspense fallback={<AppFCPLoader />}>{React.createElement(ROUTES['/test/acl'])}</React.Suspense>,
181154
data: {
182-
title: t('test.acl', { ns: 'title' }),
183-
acl: ROUTES_ACL.test.acl,
155+
title: t('ACL', { ns: 'title' }),
156+
acl: ROUTES_ACL['/test/acl'],
184157
canActivate: [ACLGuard],
185158
},
186159
},
187160
{
188161
path: 'http',
189-
element: (
190-
<React.Suspense fallback={<AppFCPLoader />}>
191-
<AppHttpRoute />
192-
</React.Suspense>
193-
),
162+
element: <React.Suspense fallback={<AppFCPLoader />}>{React.createElement(ROUTES['/test/http'])}</React.Suspense>,
194163
data: {
195-
title: t('test.http', { ns: 'title' }),
196-
acl: ROUTES_ACL.test.http,
164+
title: t('Http', { ns: 'title' }),
165+
acl: ROUTES_ACL['/test/http'],
197166
canActivate: [ACLGuard],
198167
},
199168
},
Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,49 @@
11
import type { DBreadcrumbItem } from '@react-devui/ui/components/breadcrumb';
22

3-
import { isUndefined } from 'lodash';
4-
import { useContext } from 'react';
3+
import { useTranslation } from 'react-i18next';
54
import { Link } from 'react-router-dom';
65

76
import { DBreadcrumb } from '@react-devui/ui';
87
import { getClassName } from '@react-devui/utils';
98

10-
import { RouteStateContext } from '../../Routes';
11-
129
export interface AppRouteHeaderBreadcrumbProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
10+
aList: DBreadcrumbItem<string>[];
11+
aHome?: DBreadcrumbItem<string>;
1312
aSeparator?: React.ReactNode;
1413
}
1514

1615
export function AppRouteHeaderBreadcrumb(props: AppRouteHeaderBreadcrumbProps): JSX.Element | null {
1716
const {
17+
aList,
18+
aHome,
1819
aSeparator,
1920

2021
...restProps
2122
} = props;
2223

23-
const { matchRoutes } = useContext(RouteStateContext);
24-
25-
const breadcrumb = (() => {
26-
if (matchRoutes) {
27-
const list: DBreadcrumbItem<string>[] = [];
28-
for (const match of matchRoutes) {
29-
if (match.route.data) {
30-
const { title: _title, breadcrumb = {} } = match.route.data;
31-
if (breadcrumb) {
32-
const title = breadcrumb.title ?? _title;
33-
if (!isUndefined(title)) {
34-
const link = breadcrumb.link ?? false;
35-
list.push({
36-
id: title,
37-
title: link ? (
38-
<Link className="app-route-header__breadcrumb-link" to={match.pathname}>
39-
{title}
40-
</Link>
41-
) : (
42-
title
43-
),
44-
link,
45-
separator: breadcrumb.separator,
46-
});
47-
}
48-
}
49-
}
50-
}
51-
return list;
52-
}
53-
})();
24+
const { t } = useTranslation();
25+
26+
const home: DBreadcrumbItem<string> = aHome ?? {
27+
id: '/',
28+
title: t('Home', { ns: 'title' }),
29+
link: true,
30+
};
5431

5532
return (
5633
<div {...restProps} className={getClassName(restProps.className, 'app-route-header__breadcrumb')}>
57-
{breadcrumb && <DBreadcrumb dList={breadcrumb} dSeparator={aSeparator} />}
34+
<DBreadcrumb
35+
dList={[home].concat(aList).map((item) => ({
36+
...item,
37+
title: item.link ? (
38+
<Link className="app-route-header__breadcrumb-link" to={item.id}>
39+
{item.title}
40+
</Link>
41+
) : (
42+
item.title
43+
),
44+
}))}
45+
dSeparator={aSeparator}
46+
/>
5847
</div>
5948
);
6049
}
Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
11
import { nth } from 'lodash';
2-
import { useContext } from 'react';
2+
import React, { useContext } from 'react';
3+
import { useNavigate } from 'react-router-dom';
34

5+
import { ArrowLeftOutlined } from '@react-devui/icons';
46
import { getClassName } from '@react-devui/utils';
57

68
import { RouteStateContext } from '../../Routes';
79

810
export interface AppRouteHeaderHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
11+
aBack?: boolean;
912
aActions?: React.ReactNode[];
1013
}
1114

1215
export function AppRouteHeaderHeader(props: AppRouteHeaderHeaderProps): JSX.Element | null {
1316
const {
1417
children,
18+
aBack = false,
1519
aActions,
1620

1721
...restProps
1822
} = props;
1923

2024
const { matchRoutes } = useContext(RouteStateContext);
2125

26+
const navigate = useNavigate();
27+
2228
const title = (() => {
2329
if (matchRoutes) {
2430
const { title } = nth(matchRoutes, -1)!.route.data ?? {};
@@ -29,8 +35,20 @@ export function AppRouteHeaderHeader(props: AppRouteHeaderHeaderProps): JSX.Elem
2935

3036
return (
3137
<div {...restProps} className={getClassName(restProps.className, 'app-route-header__header')}>
32-
<div className="app-route-header__header-title">{children ?? title}</div>
33-
<div className="app-route-header__header-actions">{aActions}</div>
38+
<div className="app-route-header__header-title-container">
39+
{aBack && (
40+
<button className="app-route-header__header-back">
41+
<ArrowLeftOutlined
42+
onClick={() => {
43+
navigate(-1);
44+
}}
45+
dSize={20}
46+
/>
47+
</button>
48+
)}
49+
<div className="app-route-header__header-title">{children ?? title}</div>
50+
</div>
51+
<div className="app-route-header__header-actions">{React.Children.map(aActions, (c) => c)}</div>
3452
</div>
3553
);
3654
}

packages/platform/src/app/components/table-filter/TableFilter.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export function AppTableFilter(props: AppTableFilterProps): JSX.Element | null {
6666
{t('components.table-filter.Reset')}
6767
</DButton>
6868
{aFilterList && (
69-
<div className="d-flex align-items-center">
69+
<div className="d-flex align-items-center" style={{ width: 120 }}>
7070
<DButton
7171
className="me-2"
7272
dType="link"
@@ -90,7 +90,9 @@ export function AppTableFilter(props: AppTableFilterProps): JSX.Element | null {
9090
{aFilterList.map(({ label, node }) => (
9191
<div key={label} className="app-table-filter__filter">
9292
<label className="app-table-filter__filter-label">
93-
<div style={{ width: labelWidth }}>{label}:</div>
93+
<div className="app-colon" style={{ width: labelWidth }}>
94+
{label}
95+
</div>
9496
</label>
9597
{React.cloneElement(node, {
9698
style: { ...node.props.style, maxWidth: `calc(100% - ${isNumber(labelWidth) ? labelWidth + 'px' : labelWidth})` },

packages/platform/src/app/routes/dashboard/echarts/ECharts.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState } from 'react';
2+
import { useTranslation } from 'react-i18next';
23

34
import { useMount } from '@react-devui/hooks';
45
import { DCard } from '@react-devui/ui';
@@ -9,6 +10,7 @@ import { barOptions, lineOptions, nightingaleOptions, pieOptions, scatterOptions
910

1011
export default function ECharts(): JSX.Element | null {
1112
const [options, setOptions] = useState<echarts.EChartsOption[]>([]);
13+
const { t } = useTranslation();
1214

1315
useMount(() => {
1416
setOptions([lineOptions, stackedLineOptions, barOptions, stackedBarOptions, pieOptions, nightingaleOptions, scatterOptions]);
@@ -17,7 +19,12 @@ export default function ECharts(): JSX.Element | null {
1719
return (
1820
<>
1921
<AppRouteHeader>
20-
<AppRouteHeader.Breadcrumb />
22+
<AppRouteHeader.Breadcrumb
23+
aList={[
24+
{ id: '/dashboard', title: t('Dashboard', { ns: 'title' }) },
25+
{ id: '/dashboard/echarts', title: t('ECharts', { ns: 'title' }) },
26+
]}
27+
/>
2128
<AppRouteHeader.Header />
2229
</AppRouteHeader>
2330
<div className={styles['app-echarts']}>

0 commit comments

Comments
 (0)