Skip to content

Commit a9a07a6

Browse files
committed
新增专题详情页面
1 parent 8a7ebc8 commit a9a07a6

File tree

10 files changed

+459
-6
lines changed

10 files changed

+459
-6
lines changed

.env

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
API_URL=http://baobab.kaiyanapp.com/api/
22
APP_NAME=Eyepetizer
3-
VERSIONCODE=1
4-
VERSIONNAME=1.0.0
3+
VERSIONCODE=2
4+
VERSIONNAME=1.0.1

ts/components/TopicDetailItem.tsx

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import IconCommentlinesFill from '@/assets/iconfont/IconCommentlinesFill';
2+
import IconLove from '@/assets/iconfont/IconLove';
3+
import IconShare1 from '@/assets/iconfont/IconShare1';
4+
import IconStar from '@/assets/iconfont/IconStar';
5+
import {Item, Tag} from '@/model/Daily';
6+
import {ITopicDetailItem} from '@/model/TopicDetail';
7+
import {FeedHeight, formatDateMsByYMD, navigate, share} from '@/utils/Utils';
8+
import React from 'react';
9+
import {StyleSheet, Text, View} from 'react-native';
10+
import FastImage from 'react-native-fast-image';
11+
import {TouchableWithoutFeedback} from 'react-native-gesture-handler';
12+
13+
interface IProps {
14+
item: ITopicDetailItem;
15+
}
16+
17+
class TopicDetailItem extends React.Component<IProps> {
18+
go2VideoDetail = (item: Item) => {
19+
navigate('VideoDetail', {item: item});
20+
};
21+
22+
tagItem = (value: Tag) => {
23+
return (
24+
<View key={value.name} style={styles.tag}>
25+
<Text style={styles.tagText}>{value.name}</Text>
26+
</View>
27+
);
28+
};
29+
30+
render() {
31+
const {item} = this.props;
32+
return (
33+
<View style={styles.container}>
34+
<View style={styles.headContainer}>
35+
<FastImage
36+
style={styles.icon}
37+
source={{
38+
uri: item.data.header.icon,
39+
}}
40+
/>
41+
<View style={styles.headLeftContainer}>
42+
<Text style={styles.issuerName}>{item.data.header.issuerName}</Text>
43+
<View style={styles.headBottomContainer}>
44+
<Text style={styles.time}>
45+
{formatDateMsByYMD(item.data.header.time)}发布:
46+
</Text>
47+
<Text numberOfLines={1} style={styles.title}>
48+
{item.data.content.data.title}
49+
</Text>
50+
</View>
51+
</View>
52+
</View>
53+
<Text numberOfLines={2} style={styles.description}>
54+
{item.data.content.data.description}
55+
</Text>
56+
57+
<View style={styles.tagContainer}>
58+
{item.data.content.data.tags.length > 3
59+
? item.data.content.data.tags
60+
.slice(0, 3)
61+
.map((value) => this.tagItem(value))
62+
: item.data.content.data.tags.map((value) => this.tagItem(value))}
63+
</View>
64+
<TouchableWithoutFeedback
65+
onPress={() => this.go2VideoDetail(item.data.content)}>
66+
<FastImage
67+
style={styles.feed}
68+
source={{
69+
uri: item.data.content.data.cover.feed,
70+
}}
71+
/>
72+
</TouchableWithoutFeedback>
73+
<View style={styles.consumeContainer}>
74+
<View style={styles.consumeItem}>
75+
<IconLove size={22} color="#9a9a9a" />
76+
<Text style={styles.consumeText}>
77+
{item.data.content.data.consumption.collectionCount}
78+
</Text>
79+
</View>
80+
<View style={styles.consumeItem}>
81+
<IconCommentlinesFill size={16} color="#9a9a9a" />
82+
<Text style={styles.consumeText}>
83+
{item.data.content.data.consumption.replyCount}
84+
</Text>
85+
</View>
86+
<View style={styles.consumeItem}>
87+
<IconStar color="#9a9a9a" />
88+
<Text style={styles.consumeText}>收藏</Text>
89+
</View>
90+
<IconShare1
91+
color="#9a9a9a"
92+
onPress={() =>
93+
share(
94+
item.data.content.data.title,
95+
item.data.content.data.playUrl,
96+
)
97+
}
98+
/>
99+
</View>
100+
<View style={styles.line} />
101+
</View>
102+
);
103+
}
104+
}
105+
106+
const styles = StyleSheet.create({
107+
container: {
108+
marginLeft: 20,
109+
marginRight: 20,
110+
},
111+
headContainer: {
112+
flexDirection: 'row',
113+
justifyContent: 'center',
114+
alignItems: 'center',
115+
},
116+
icon: {
117+
width: 44,
118+
height: 44,
119+
borderRadius: 22,
120+
marginTop: 20,
121+
marginRight: 10,
122+
},
123+
headLeftContainer: {
124+
flex: 1,
125+
marginTop: 20,
126+
},
127+
issuerName: {
128+
color: '#000',
129+
fontSize: 14,
130+
fontWeight: 'bold',
131+
},
132+
headBottomContainer: {
133+
flexDirection: 'row',
134+
marginTop: 3,
135+
},
136+
time: {
137+
fontSize: 12,
138+
color: '#9a9a9a',
139+
},
140+
title: {
141+
flex: 1,
142+
color: '#000',
143+
fontSize: 12,
144+
marginStart: 3,
145+
},
146+
description: {
147+
fontSize: 14,
148+
color: '#333333',
149+
marginTop: 10,
150+
},
151+
tagContainer: {
152+
flexDirection: 'row',
153+
marginTop: 10,
154+
},
155+
tag: {
156+
borderRadius: 4,
157+
backgroundColor: '#89b1e933',
158+
justifyContent: 'center',
159+
alignItems: 'center',
160+
marginRight: 5,
161+
},
162+
tagText: {
163+
color: '#42A5F5',
164+
fontSize: 12,
165+
padding: 5,
166+
},
167+
feed: {
168+
width: '100%',
169+
height: FeedHeight,
170+
borderRadius: 4,
171+
marginTop: 10,
172+
},
173+
consumeContainer: {
174+
flexDirection: 'row',
175+
marginTop: 15,
176+
},
177+
consumeItem: {
178+
flexDirection: 'row',
179+
alignContent: 'flex-start',
180+
flex: 1,
181+
alignItems: 'center',
182+
},
183+
consumeText: {
184+
color: '#9a9a9a',
185+
fontSize: 12,
186+
marginLeft: 10,
187+
},
188+
line: {
189+
marginTop: 15,
190+
height: StyleSheet.hairlineWidth,
191+
backgroundColor: '#0000001F',
192+
},
193+
});
194+
195+
export default TopicDetailItem;

ts/components/VideoRelateItem.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class VideoRelateItem extends React.Component<IProps> {
2727
<View style={styles.videoInfo}>
2828
<Text style={styles.title}>{item.data.title}</Text>
2929
<Text style={styles.category}>
30-
#{item.data.category} / {item.data.author.name}
30+
#{item.data.category} /{' '}
31+
{item.data.author == null ? '' : item.data.author.name}
3132
</Text>
3233
</View>
3334
</View>

ts/model/TopicDetail.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {Item} from './Daily';
2+
3+
export interface ITopicDetail {
4+
headerImage: string;
5+
brief: string;
6+
text: string;
7+
itemList: ITopicDetailItem[];
8+
}
9+
10+
export interface ITopicDetailItem {
11+
data: ITopicDetailData;
12+
}
13+
14+
export interface ITopicDetailData {
15+
header: ITopicHeaderDetail;
16+
content: Item;
17+
}
18+
19+
export interface ITopicHeaderDetail {
20+
icon: string;
21+
issuerName: string;
22+
time: number;
23+
}

ts/model/dva/Models.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import video from './VideoModel';
88
import hot from './HotModel';
99
import hotTab from './HotTabModel';
1010
import search from './SearchModel';
11+
import topicDetail from './TopicDetailModel';
1112

1213
const models = [
1314
daily,
@@ -20,6 +21,7 @@ const models = [
2021
hot,
2122
hotTab,
2223
search,
24+
topicDetail,
2325
];
2426

2527
export type RootState = {
@@ -32,6 +34,7 @@ export type RootState = {
3234
video: typeof video.state;
3335
hot: typeof hot.state;
3436
search: typeof search.state;
37+
topicDetail: typeof topicDetail.state;
3538
} & {
3639
//联合类型
3740
[key: string]: typeof hotTab.state;

ts/model/dva/TopicDetailModel.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {Effect, Model} from 'dva-core-ts';
2+
import {Reducer} from 'redux';
3+
import axios from 'axios';
4+
import Toast from 'react-native-root-toast';
5+
import {ITopicDetail} from '@/model/TopicDetail';
6+
7+
const TOPISC_DETAIL_URL = 'v3/lightTopics/internal/';
8+
9+
export interface ITopicDetailModelState {
10+
topDetail: ITopicDetail | null;
11+
refreshing: boolean;
12+
}
13+
14+
export interface TopicDetailModel extends Model {
15+
namespace: 'topicDetail';
16+
state: ITopicDetailModelState;
17+
reducers: {
18+
setState: Reducer<ITopicDetailModelState>;
19+
};
20+
effects: {
21+
onRefresh: Effect;
22+
};
23+
}
24+
25+
const initialState: ITopicDetailModelState = {
26+
topDetail: null,
27+
refreshing: true,
28+
};
29+
30+
const topicDetailModel: TopicDetailModel = {
31+
namespace: 'topicDetail',
32+
state: initialState,
33+
reducers: {
34+
setState(state = initialState, {payload}) {
35+
return {
36+
...state,
37+
...payload,
38+
};
39+
},
40+
},
41+
effects: {
42+
*onRefresh({payload}, {call, put}) {
43+
try {
44+
const url = `${TOPISC_DETAIL_URL}${payload.id}`;
45+
const {data} = yield call(axios.get, url);
46+
47+
yield put({
48+
type: 'setState',
49+
payload: {
50+
topDetail: data,
51+
refreshing: false,
52+
},
53+
});
54+
} catch (error) {
55+
Toast.show(error.message);
56+
yield put({
57+
type: 'setState',
58+
payload: {
59+
refreshing: false,
60+
},
61+
});
62+
}
63+
},
64+
},
65+
};
66+
67+
export default topicDetailModel;

ts/navigator/Router.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@ import RecommendImageGallery, {
1717
} from '@/page/discover/RecommendImageGallery';
1818
import {IMasonry} from '@/model/Masonry';
1919
import RecommendVideoPage from '@/page/discover/RecommendVideoPage';
20+
import TopicDetailPage from '@/page/discover/TopicDetailPage';
2021

2122
//定义每一个页面的名称以及进入页面传递参数的类型
2223
export type RootStackParamList = {
2324
BottomTabs: undefined;
2425
SearchPage: undefined;
26+
TopicDetail: {
27+
id: number;
28+
};
2529
NewsDetail: {
2630
url: string;
2731
};
@@ -65,6 +69,7 @@ function RootStackScreen() {
6569
component={SearchPage}
6670
options={{headerShown: false}}
6771
/>
72+
<Stack.Screen name="TopicDetail" component={TopicDetailPage} />
6873
<Stack.Screen
6974
name="NewsDetail"
7075
component={NewsDetailPage}

0 commit comments

Comments
 (0)