Skip to content

Commit 0620c2c

Browse files
committed
feat: topic list & detail
1 parent 7622c05 commit 0620c2c

File tree

20 files changed

+406
-95
lines changed

20 files changed

+406
-95
lines changed

.vscode/react.code-snippets

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"React-TypeScript Function Component": {
3+
"prefix": "rfct",
4+
"body": [
5+
"import React from 'react';",
6+
"",
7+
"const ${0:${TM_FILENAME_BASE/(.*)/${1:/capitalize}/}}: React.FC<Props> = (props) => {",
8+
" return null;",
9+
"};",
10+
"",
11+
"export default ${0:${TM_FILENAME_BASE/(.*)/${1:/capitalize}/}};",
12+
"",
13+
"interface Props {",
14+
"};"
15+
],
16+
"description": "React-TypeScript Function Component"
17+
}
18+
}

config/config.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ export default defineConfig({
2222
immer: true,
2323
},
2424

25-
layout: {
26-
name: config.title,
27-
},
25+
layout: {},
2826

2927
qiankun: {
3028
master: {

config/routes.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,31 @@ import { IRoute } from 'umi';
33
const routes: IRoute[] = [
44
{
55
path: '/',
6-
exact: true,
7-
icon: 'home',
8-
name: '主页',
9-
component: '@/page/home',
10-
wrappers: ['@/layout/index'],
11-
hideInNav: false,
12-
hideInMenu: false,
6+
exact: false,
7+
component: '@/layout/index',
8+
routes: [
9+
{
10+
path: '/',
11+
exact: true,
12+
icon: 'home',
13+
name: '主页',
14+
component: '@/page/home',
15+
},
16+
{
17+
path: '/api',
18+
exact: true,
19+
icon: 'api',
20+
name: 'API',
21+
component: '@/page/api',
22+
},
23+
{
24+
path: '/topic/:id',
25+
exact: true,
26+
component: '@/page/topic',
27+
},
28+
],
1329
},
30+
{ component: '@/page/404' },
1431
];
1532

1633
export default routes;

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@ant-design/pro-list": "^1.21.12",
3232
"@ant-design/pro-table": "^2.61.9",
3333
"@types/dotenv": "^8.2.0",
34+
"@types/markdown-it": "^12.2.3",
3435
"@types/react": "^17.0.0",
3536
"@types/react-dom": "^17.0.0",
3637
"@umijs/plugin-esbuild": "^1.4.1",
@@ -40,7 +41,9 @@
4041
"ahooks": "^3.1.3",
4142
"dayjs": "^1.10.7",
4243
"lint-staged": "^10.0.7",
44+
"markdown-it": "^12.3.0",
4345
"prettier": "^2.2.0",
46+
"react-markdown-editor-lite": "^1.3.2",
4447
"typescript": "^4.1.2",
4548
"umi": "^3.5.20",
4649
"yorkie": "^2.0.0"

src/app.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import 'dayjs/locale/zh';
66

77
import { MicroApp, IRoute, request as requestClient, RequestConfig } from 'umi';
88
import { PageContainer } from '@ant-design/pro-layout';
9-
import { BASE_URL } from './constants';
10-
import proLayout from './proLayout';
9+
// import { BASE_URL } from './constants';
10+
import proLayout from './layout';
1111

1212
dayjs.locale('zh');
1313
dayjs.extend(relativeTime);
1414

1515
const qiankunApps: Array<QiankunApp> = [];
1616

17-
export async function getInitialState() {
17+
export async function getInitialState(): Promise<InitialState> {
1818
return {
1919
currentUser: 'Suyi',
2020
};
@@ -88,3 +88,7 @@ interface QiankunApp {
8888
remark?: string;
8989
locale?: string;
9090
}
91+
92+
export interface InitialState {
93+
currentUser: string;
94+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.container {
2-
display: flex;
2+
display: inline-flex;
33
align-items: baseline;
44
}
55

src/component/Brand/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import * as styles from './index.module.less';
2+
import * as styles from './index.less';
33

44
const Brand: React.FC<Props> = ({ logo, title, description }) => (
55
<div className={styles.container}>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,10 @@
88
}
99
}
1010
}
11+
12+
.footer {
13+
display: flex;
14+
justify-content: center;
15+
16+
color: lightgray;
17+
}

src/component/Topic/index.tsx

Lines changed: 144 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,114 @@
1-
import React from 'react';
1+
import React, { useEffect } from 'react';
22
import dayjs from 'dayjs';
33

4-
import { Avatar, Tag, Space } from 'antd';
4+
import { useHistory } from 'umi';
5+
import { Avatar, Tag, Space, Divider, Button } from 'antd';
56
import { useRequest, useReactive } from 'ahooks';
67
import { ProListMetas } from '@ant-design/pro-list';
78
import ProList from '@ant-design/pro-list';
9+
import { ReloadOutlined } from '@ant-design/icons';
810

911
import { TABS_MAP } from '@/constants';
1012
import type { TabType } from '@/constants';
1113

1214
import * as API from '@/service/topic';
13-
import * as styles from './index.module.less';
15+
import * as styles from './index.less';
1416

15-
interface Props {
16-
tab?: TabType;
17-
}
17+
interface Props {}
1818

1919
const Topics: React.FC<Props> = (props) => {
20-
const { tab = 'share' } = props;
20+
const history = useHistory();
2121

2222
const state = useReactive({
23+
tab: 'share',
2324
page: 1,
24-
limit: 20,
25+
limit: 25,
26+
data: [],
27+
hasNext: true,
2528
});
2629

27-
const { page, limit } = state;
30+
const { tab, page, limit, data, hasNext } = state;
2831

29-
const { data, loading, refresh } = useRequest(
32+
const { loading, refresh } = useRequest(
3033
async () => {
31-
const res = await API.queryTopics({
34+
const res = await API.queryTopicList({
3235
tab,
3336
page,
3437
limit,
3538
});
3639

40+
if (res.data.lenght < limit) {
41+
state.hasNext = false;
42+
}
43+
44+
state.data = state.data.concat(res.data);
45+
3746
return res.data;
3847
},
3948
{
4049
refreshDeps: [tab, page, limit],
50+
debounceWait: 300,
4151
},
4252
);
4353

54+
const onRefresh = () => {
55+
state.page = 1;
56+
state.data = [];
57+
state.hasNext = true;
58+
refresh();
59+
};
60+
61+
const onReachEnd = () => {
62+
if (!hasNext) {
63+
return;
64+
}
65+
66+
state.page = state.page + 1;
67+
};
68+
69+
useEffect(() => {
70+
if (loading) {
71+
return;
72+
}
73+
74+
const onScroll = () => {
75+
const scrollHeight = document.body.scrollHeight;
76+
const offsetHeight = document.body.offsetHeight;
77+
const pageYOffset = window.pageYOffset;
78+
79+
console.debug('===pageYOffset', pageYOffset);
80+
console.debug('===offsetHeight', offsetHeight);
81+
console.debug('===scrollHeight', scrollHeight);
82+
83+
// if(pageYOffset) {
84+
// console.log('===onReachStart', hasNext, loading);
85+
// onReachStart();
86+
// return;
87+
// }
88+
89+
if (pageYOffset + offsetHeight === scrollHeight) {
90+
console.log('===onReachEnd', hasNext, loading);
91+
onReachEnd();
92+
}
93+
};
94+
95+
document.addEventListener('scroll', onScroll, false);
96+
97+
return () => {
98+
document.removeEventListener('scroll', onScroll, false);
99+
};
100+
}, [loading]);
101+
44102
const metas: ProListMetas = {
45103
avatar: {
46104
dataIndex: 'author.avatar_url',
47105
render: (_, entity) => {
48106
const { tab: _tab, author, reply_count, visit_count } = entity;
49107

50-
const category = TABS_MAP[_tab as keyof typeof TABS_MAP];
108+
const category = TABS_MAP[_tab as keyof typeof TABS_MAP] || {
109+
color: '#777',
110+
name: '未知',
111+
};
51112

52113
return (
53114
<Space>
@@ -84,26 +145,78 @@ const Topics: React.FC<Props> = (props) => {
84145
},
85146
};
86147

148+
const renderFooter = () => {
149+
return (
150+
<div className={styles.footer}>
151+
{' '}
152+
{hasNext ? '加载更多...' : '我是有底线的!'}
153+
</div>
154+
);
155+
};
156+
157+
const onChangeTabKey = (key: React.Key | undefined) => {
158+
if (!key) {
159+
return;
160+
}
161+
162+
state.tab = key as TabType;
163+
state.page = 1;
164+
state.data = [];
165+
state.hasNext = true;
166+
refresh();
167+
};
168+
87169
return (
88-
<ProList
89-
rowKey="id"
90-
showActions="always"
91-
dataSource={data}
92-
loading={loading}
93-
metas={metas}
94-
className={styles.list}
95-
request={async (params) => {
96-
state.page = params.current || page;
97-
state.limit = params.pageSize || limit;
98-
return Promise.resolve({});
99-
}}
100-
pagination={{
101-
total: 100,
102-
current: page,
103-
pageSize: limit,
104-
responsive: true,
105-
}}
106-
/>
170+
<div>
171+
<ProList
172+
rowKey="id"
173+
showActions="always"
174+
dataSource={data}
175+
loading={loading}
176+
metas={metas}
177+
className={styles.list}
178+
toolbar={{
179+
menu: {
180+
type: 'tab',
181+
activeKey: tab,
182+
items: Object.entries(TABS_MAP).map(([tab, currentTab]) => {
183+
return {
184+
key: tab,
185+
label: <span>{currentTab.name}</span>,
186+
};
187+
}),
188+
onChange: onChangeTabKey,
189+
},
190+
actions: [
191+
<Button key="refresh" type="primary" onClick={onRefresh}>
192+
<ReloadOutlined />
193+
刷新
194+
</Button>,
195+
],
196+
}}
197+
onRow={(record) => {
198+
return {
199+
onClick: () => {
200+
console.log('onClick', record);
201+
history.push(`/topic/${record.id}`);
202+
},
203+
};
204+
}}
205+
// request={async (params) => {
206+
// state.page = params.current || page;
207+
// state.limit = params.pageSize || limit;
208+
// return Promise.resolve({});
209+
// }}
210+
// pagination={{
211+
// total: 100,
212+
// current: page,
213+
// pageSize: limit,
214+
// responsive: true,
215+
// }}
216+
/>
217+
<Divider type="horizontal" />
218+
{renderFooter()}
219+
</div>
107220
);
108221
};
109222

src/global.less

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,17 @@
55
.ant-input-number {
66
width: 100% !important;
77
}
8+
9+
.cnode-header {
10+
display: flex;
11+
align-items: center;
12+
}
13+
14+
.cnode-header-right {
15+
margin-right: 24px;
16+
}
17+
18+
.top-nav-menu {
19+
justify-content: flex-end;
20+
padding: 0 48px;
21+
}

0 commit comments

Comments
 (0)