; }
50 |
51 | -dontwarn com.facebook.react.**
52 |
53 | # TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
54 | # See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
55 | -dontwarn android.text.StaticLayout
56 |
57 | # okhttp
58 |
59 | -keepattributes Signature
60 | -keepattributes *Annotation*
61 | -keep class okhttp3.** { *; }
62 | -keep interface okhttp3.** { *; }
63 | -dontwarn okhttp3.**
64 |
65 | # okio
66 |
67 | -keep class sun.misc.Unsafe { *; }
68 | -dontwarn java.nio.file.*
69 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
70 | -dontwarn okio.**
71 |
--------------------------------------------------------------------------------
/src/pages/detail/components/Info.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { StyleSheet, View, Image, Text, TouchableOpacity } from 'react-native'
4 |
5 | function Info({ data, navigate }) {
6 |
7 | return (
8 | Object.keys(data).length > 0 ?
9 |
10 |
11 | {data.title}
12 |
13 |
14 | { navigate('Center', { user: data.author.loginname }) }}>
15 |
16 |
17 |
18 | {data.author.loginname}
19 | 发布于: {data.create_at}
20 |
21 |
22 |
23 | {data.sort}
24 |
25 | {data.visit_count}次浏览
26 |
27 |
28 |
29 | : null
30 | )
31 | }
32 |
33 | const styles = StyleSheet.create({
34 | title: {
35 | padding: 5,
36 | margin: 15,
37 | backgroundColor: '#f0f0f0',
38 | borderRadius: 5,
39 | },
40 |
41 | h2: {
42 | color: '#2c3e50',
43 | fontSize: 18,
44 | lineHeight: 1.5 * 18,
45 | fontWeight: 'bold',
46 | },
47 |
48 | info: {
49 | alignItems: 'center',
50 | flexWrap: 'wrap',
51 | flexDirection: 'row',
52 | paddingLeft: 15,
53 | paddingRight: 15,
54 | },
55 |
56 | avatar: {
57 | width: 40,
58 | height: 40,
59 | borderRadius: 20,
60 | marginRight: 15,
61 | },
62 |
63 | col: {
64 | flex: 1,
65 | },
66 |
67 | h3: {
68 | flex: 1,
69 | overflow: 'hidden',
70 | fontSize: 16,
71 | fontWeight: 'bold',
72 | },
73 |
74 | tab: {
75 | paddingTop: 5,
76 | paddingLeft: 15,
77 | paddingBottom: 5,
78 | paddingRight: 15,
79 | borderRadius: 3,
80 | },
81 |
82 | tag: {
83 | textAlign: 'center',
84 | fontSize: 12,
85 | color: '#FFFFFF',
86 | fontWeight: 'bold',
87 | },
88 |
89 | span: {
90 | paddingTop: 5,
91 | paddingBottom: 5,
92 | color: '#34495e',
93 | fontSize: 12,
94 | },
95 |
96 | right: {
97 | alignItems: 'flex-end',
98 | },
99 |
100 | top: {
101 | backgroundColor: '#e74c3c',
102 | },
103 |
104 | ask: {
105 | backgroundColor: '#3498db',
106 | },
107 |
108 | good: {
109 | backgroundColor: '#e67e22',
110 | },
111 |
112 | share: {
113 | backgroundColor: '#1abc9c',
114 | },
115 |
116 | job: {
117 | backgroundColor: '#6A8FFF',
118 | },
119 |
120 | default: {
121 | backgroundColor: '#e7e7e7',
122 | },
123 | });
124 |
125 | export default Info
126 |
--------------------------------------------------------------------------------
/src/pages/detail/model.js:
--------------------------------------------------------------------------------
1 | import * as service from './service';
2 |
3 | export default {
4 | namespace: 'detail',
5 | state: {
6 | data: {},
7 | content: '',
8 | reply_id: '',
9 | replies: [],
10 | is_collect: false,
11 | loading: false,
12 | },
13 | effects: {
14 | *query({ payload = {} }, { call, put }) {
15 | yield put({ type: 'loading', payload: true });
16 | const { data, err } = yield call(service.queryTopic, payload);
17 | yield put({ type: 'loading', payload: false });
18 | if (err) return console.log(err)
19 | yield put({ type: 'query/success', payload: data });
20 | },
21 | *collect({ payload = {} }, { call, put }) {
22 | const { collect } = payload
23 | let query = 'de_collect'
24 | if (collect) query = 'collect'
25 | const { data, err } = yield call(service[query], payload);
26 | if (err) return console.log(err)
27 | yield put({ type: 'collect/success', payload: collect });
28 | },
29 | *ups({ payload = {} }, { call, put }) {
30 | const { reply_id } = payload
31 | const { data, err } = yield call(service.ups, payload);
32 | if (err) return console.log(err)
33 | yield put({ type: 'ups/success', payload: { reply_id, data } });
34 | },
35 | *comment({ payload = {} }, { call, put }) {
36 | const { user } = payload
37 | const { data, err } = yield call(service.postComment, payload);
38 | if (err) return console.log(err)
39 | yield put({ type: 'comment/success', payload: { data, user } });
40 | },
41 | },
42 | reducers: {
43 | 'query/success'(state, { payload }) {
44 | const [, data] = payload
45 | const topics = service.parseTopic(data.data)
46 | const { replies } = topics
47 | return { ...state, data: topics, is_collect: topics.is_collect, replies };
48 | },
49 | 'collect/success'(state, { payload }) {
50 | return { ...state, is_collect: payload };
51 | },
52 | 'ups/success'(state, { payload }) {
53 | const { reply_id, data } = payload
54 | const [, result] = data
55 | const params = { result, state, reply_id }
56 | const replies = service.parseUps(params)
57 | return { ...state, replies };
58 | },
59 | 'comment/success'(state, { payload }) {
60 | const { data, user } = payload
61 | const [, result] = data
62 | const params = { user, state }
63 | const replies = service.parseComment(params)
64 | return { ...state, replies };
65 | },
66 | 'loading'(state, { payload: data }) {
67 | return { ...state, loading: data };
68 | },
69 | 'content'(state, { payload: data }) {
70 | return { ...state, content: data };
71 | },
72 | 'set_reply_id'(state, { payload: data }) {
73 | return { ...state, reply_id: data };
74 | },
75 | 'clean'(state, { payload: data }) {
76 | return { ...state, data: {}, is_collect: false, replies: [], content: '' };
77 | },
78 | },
79 | subscriptions: {},
80 | };
81 |
--------------------------------------------------------------------------------
/src/pages/search/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import Card from './components/Card';
4 | import Seek from './components/Seek';
5 | import History from './components/History';
6 | import { StyleSheet, View, ScrollView, Text, TextInput, Button, Image, StatusBar, FlatList, Dimensions, TouchableOpacity } from 'react-native'
7 |
8 | class Search extends PureComponent {
9 | constructor(props) {
10 | super(props)
11 | this.state = {}
12 | }
13 |
14 | static navigationOptions = ({ navigation }) => {
15 | const { state, setParams } = navigation;
16 | return {
17 | headerRight: ()
18 | };
19 | };
20 |
21 | componentDidMount() {
22 | this.props.init();
23 | }
24 |
25 | componentWillUnmount() {
26 | this.props.clean()
27 | }
28 |
29 | _onEndReached = (pageSize) => {
30 | const page = pageSize + 1
31 | const { content } = this.props
32 | this.props.query({ page, content })
33 | }
34 |
35 | render() {
36 | const { data, page, content, visible, loading } = this.props
37 | const { navigate } = this.props.navigation;
38 | const { width } = Dimensions.get('window');
39 |
40 | return (
41 |
42 |
43 | {
44 | visible ?
45 |
46 |
47 |
48 | : index}
53 | renderItem={({ item }) => }
54 | onRefresh={() => { this.props.query({ content }) }}
55 | onEndReached={() => { this._onEndReached(page) }} // 如果直接 this.props.query() 会请求两次
56 | onEndReachedThreshold={0.5}
57 | refreshing={loading}
58 | />
59 | }
60 |
61 | );
62 | }
63 | }
64 |
65 | function mapStateToProps(state) {
66 | const { data, page, content, visible, loading } = state.search;
67 | return { data, page, content, visible, loading };
68 | }
69 |
70 | function mapDispatchToProps(dispatch) {
71 | return {
72 | init() {
73 | dispatch({
74 | type: 'search/init',
75 | });
76 | },
77 | query(params) {
78 | dispatch({
79 | type: 'search/query',
80 | payload: params,
81 | });
82 | },
83 | clean() {
84 | dispatch({
85 | type: 'search/clean',
86 | })
87 | },
88 | record(params) {
89 | dispatch({
90 | type: 'search/record',
91 | payload: params,
92 | });
93 | },
94 | }
95 | }
96 |
97 | const styles = StyleSheet.create({
98 | container: {
99 | flex: 1,
100 | backgroundColor: '#F8F8F8',
101 | },
102 |
103 | headerLeft: {
104 | width: 80,
105 | marginLeft: 15
106 | },
107 | });
108 |
109 | export default connect(mapStateToProps, mapDispatchToProps)(Search);
110 |
--------------------------------------------------------------------------------
/src/pages/notice/screen/Contact.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import { Tip } from '../../../components';
4 | import Message from '../components/Message';
5 | import { StyleSheet, View, ScrollView, Text, Button, Image, StatusBar, FlatList, Dimensions, TouchableOpacity } from 'react-native'
6 |
7 | const { width } = Dimensions.get('window');
8 |
9 | class Contact extends PureComponent {
10 | constructor(props) {
11 | super(props)
12 | this.state = {}
13 | }
14 |
15 | static navigationOptions = ({ navigation }) => {
16 | const { state, setParams } = navigation;
17 | return {
18 | headerTitle: '联系人',
19 | };
20 | };
21 |
22 | _renderRow(item) {
23 | const { navigate } = this.props.navigation;
24 |
25 | return (
26 | { navigate('Information', { user: item }) }}>
27 |
28 |
29 |
30 | {item.name}
31 |
32 |
33 |
34 | );
35 | }
36 |
37 | render() {
38 | const { contacts, loading } = this.props
39 | const { navigate } = this.props.navigation;
40 |
41 | return (
42 |
43 |
44 | {
45 | contacts.length > 0 ?
46 |
47 | index}
52 | renderItem={({ item }) => this._renderRow(item)}
53 | />
54 |
55 | :
56 | }
57 |
58 | );
59 | }
60 | }
61 |
62 | function mapStateToProps(state) {
63 | const { contacts, loading } = state.notice;
64 | return { contacts, loading };
65 | }
66 |
67 | function mapDispatchToProps(dispatch) {
68 | return {
69 | query(params) {
70 | dispatch({
71 | type: 'zone/query',
72 | payload: params,
73 | });
74 | },
75 | }
76 | }
77 |
78 | const styles = StyleSheet.create({
79 | container: {
80 | flex: 1,
81 | backgroundColor: '#F8F8F8',
82 | },
83 |
84 | rowList: {
85 | marginTop: 10,
86 | },
87 |
88 | row: {
89 | paddingLeft: 27,
90 | paddingRight: 27,
91 | alignItems: 'center',
92 | flexDirection: 'row',
93 | backgroundColor: '#FFFFFF',
94 | },
95 |
96 | rowImg: {
97 | width: 36,
98 | height: 36,
99 | borderRadius: 18,
100 | marginRight: 20,
101 | },
102 |
103 | rowInner: {
104 | flex: 1,
105 | paddingTop: 20,
106 | paddingBottom: 20,
107 | borderBottomWidth: 0.5,
108 | borderColor: '#F0F0F0',
109 | },
110 |
111 | rowText: {
112 | fontSize: 16,
113 | fontWeight: '400',
114 | }
115 | });
116 |
117 | export default connect(mapStateToProps, mapDispatchToProps)(Contact);
118 |
--------------------------------------------------------------------------------
/src/navigation.js:
--------------------------------------------------------------------------------
1 | import CardStackStyleInterpolator from 'react-navigation/src/views/CardStackStyleInterpolator';
2 | import { StackNavigator, TabNavigator, TabBarBottom } from 'react-navigation';
3 | import Home from './pages/home';
4 | import Detail from './pages/detail';
5 | import Search from './pages/search';
6 | import Publish from './pages/publish';
7 | import Recruit from './pages/recruit';
8 |
9 | // Notice Navigator
10 | import Notice from './pages/notice';
11 | import Read from './pages/notice/screen/Read';
12 | import Chat from './pages/notice/screen/Chat';
13 | import Roster from './pages/notice/screen/Roster';
14 | import System from './pages/notice/screen/System';
15 | import Contact from './pages/notice/screen/Contact';
16 | import AddFriend from './pages/notice/screen/AddFriend';
17 | import Information from './pages/notice/screen/Information';
18 | import ChatMessage from './pages/notice/screen/ChatMessage';
19 |
20 | // Zone Navigator
21 | import Zone from './pages/zone';
22 | import Login from './pages/zone/screen/Login';
23 | import Center from './pages/zone/screen/Center';
24 | import Github from './pages/zone/screen/Github';
25 | import Collect from './pages/zone/screen/Collect';
26 | import Dynamic from './pages/zone/screen/Dynamic';
27 | import Credits from './pages/zone/screen/Credits';
28 | import Setting from './pages/zone/screen/Setting';
29 | import Personal from './pages/zone/screen/Personal';
30 | import Password from './pages/zone/screen/Password';
31 |
32 | const Tabs = TabNavigator({
33 | Home: { screen: Home },
34 | Recruit: { screen: Recruit },
35 | Notice: { screen: Notice },
36 | Zone: { screen: Zone },
37 | }, {
38 | tabBarOptions: {
39 | activeTintColor: '#7a86a2',
40 | style: {
41 | backgroundColor: '#fff',
42 | },
43 | },
44 | lazy: true, //懒加载
45 | swipeEnabled: false,
46 | animationEnabled: false, //关闭安卓底栏动画
47 | tabBarPosition: 'bottom',
48 | tabBarComponent: TabBarBottom, //解决安卓底栏不显示图标问题
49 | });
50 |
51 | const Navigation = StackNavigator({
52 | Tabs: { screen: Tabs },
53 | Detail: { screen: Detail },
54 | Search: { screen: Search },
55 | Publish: { screen: Publish },
56 | // Notice Navigator
57 | Read: { screen: Read },
58 | Chat: { screen: Chat },
59 | System: { screen: System },
60 | Roster: { screen: Roster },
61 | Contact: { screen: Contact },
62 | AddFriend: { screen: AddFriend },
63 | Information: { screen: Information },
64 | ChatMessage: { screen: ChatMessage },
65 | // Zone Navigator
66 | Login: { screen: Login },
67 | Center: { screen: Center },
68 | Github: { screen: Github },
69 | Credits: { screen: Credits },
70 | Dynamic: { screen: Dynamic },
71 | Collect: { screen: Collect },
72 | Setting: { screen: Setting },
73 | Personal: { screen: Personal },
74 | Password: { screen: Password },
75 | }, {
76 | initialRouteName: 'Tabs',
77 | navigationOptions: {
78 | headerStyle: {
79 | backgroundColor: '#2D2D2D',
80 | },
81 | headerBackTitle: null,
82 | headerTintColor: '#FFFFFF',
83 | },
84 | transitionConfig: () => ({
85 | screenInterpolator: CardStackStyleInterpolator.forHorizontal, // 安卓导航进入 左右方式
86 | }),
87 | headerMode: 'screen'
88 | });
89 |
90 | export default Navigation;
--------------------------------------------------------------------------------
/src/pages/notice/screen/AddFriend.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import Seek from '../components/Seek';
4 | import { Tip } from '../../../components';
5 | import { StyleSheet, View, ScrollView, RefreshControl, Text, Button, Image, StatusBar, FlatList, Dimensions, TouchableOpacity } from 'react-native'
6 |
7 | class AddFriend extends PureComponent {
8 | constructor(props) {
9 | super(props)
10 | this.state = {}
11 | }
12 |
13 | static navigationOptions = ({ navigation }) => {
14 | const { state, setParams } = navigation;
15 | return {
16 | headerRight: ()
17 | };
18 | };
19 |
20 | componentWillUnmount() {
21 | this.props.clean()
22 | }
23 |
24 | render() {
25 | const { info, loading } = this.props
26 | const { navigate } = this.props.navigation;
27 |
28 | return (
29 | { this.props.information({ user: info.name }) }} refreshing={loading} />}>
30 |
31 | {
32 | Object.keys(info).length > 0 ?
33 |
34 | { navigate('Information', { user: info }) }}>
35 |
36 |
37 |
38 | {info.name}
39 |
40 |
41 |
42 |
43 | :
44 | }
45 |
46 | );
47 | }
48 | }
49 |
50 | function mapStateToProps(state) {
51 | const { info, loading } = state.zone;
52 | return { info, loading };
53 | }
54 |
55 | function mapDispatchToProps(dispatch) {
56 | return {
57 | query(params) {
58 | dispatch({
59 | type: 'notice/query',
60 | payload: params,
61 | });
62 | },
63 | information(params) {
64 | dispatch({
65 | type: 'zone/information',
66 | payload: params,
67 | });
68 | },
69 | clean() {
70 | dispatch({
71 | type: 'zone/cleanInfo',
72 | });
73 | },
74 | }
75 | }
76 |
77 | const styles = StyleSheet.create({
78 | container: {
79 | flex: 1,
80 | backgroundColor: '#F8F8F8',
81 | },
82 |
83 | rowList: {
84 | marginTop: 10,
85 | },
86 |
87 | row: {
88 | paddingLeft: 27,
89 | paddingRight: 27,
90 | alignItems: 'center',
91 | flexDirection: 'row',
92 | backgroundColor: '#FFFFFF',
93 | },
94 |
95 | rowImg: {
96 | width: 36,
97 | height: 36,
98 | borderRadius: 18,
99 | marginRight: 20,
100 | },
101 |
102 | rowInner: {
103 | flex: 1,
104 | paddingTop: 20,
105 | paddingBottom: 20,
106 | borderBottomWidth: 0.5,
107 | borderColor: '#F0F0F0',
108 | },
109 |
110 | rowText: {
111 | fontSize: 16,
112 | fontWeight: '400',
113 | }
114 | });
115 |
116 | export default connect(mapStateToProps, mapDispatchToProps)(AddFriend);
117 |
--------------------------------------------------------------------------------
/src/pages/notice/screen/System.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import { Tip } from '../../../components';
4 | import Message from '../components/Message';
5 | import { StyleSheet, View, ScrollView, Text, Button, Image, StatusBar, FlatList, Dimensions, TouchableOpacity } from 'react-native'
6 |
7 | const { width } = Dimensions.get('window');
8 |
9 | class System extends PureComponent {
10 | constructor(props) {
11 | super(props)
12 | this.state = {}
13 | }
14 |
15 | static navigationOptions = ({ navigation }) => {
16 | const { state, setParams } = navigation;
17 | return {
18 | headerTitle: '系统消息',
19 | };
20 | };
21 |
22 | render() {
23 | const { hasnot_read_messages, has_read_messages, loading } = this.props
24 | const { navigate } = this.props.navigation;
25 |
26 | return (
27 |
28 |
29 |
30 | { navigate('Read', { messages: has_read_messages }) }}>
31 |
32 |
33 |
34 | 已读消息
35 |
36 |
37 |
38 |
39 | {
40 | hasnot_read_messages.length > 0 ?
41 |
42 | index}
47 | renderItem={({ item }) => }
48 | />
49 |
50 | :
51 | }
52 |
53 | );
54 | }
55 | }
56 |
57 | function mapStateToProps(state) {
58 | const { hasnot_read_messages, has_read_messages, loading } = state.notice;
59 | return { hasnot_read_messages, has_read_messages, loading };
60 | }
61 |
62 | function mapDispatchToProps(dispatch) {
63 | return {
64 | query(params) {
65 | dispatch({
66 | type: 'zone/query',
67 | payload: params,
68 | });
69 | },
70 | }
71 | }
72 |
73 | const styles = StyleSheet.create({
74 | container: {
75 | flex: 1,
76 | backgroundColor: '#F8F8F8',
77 | },
78 |
79 | rowList: {
80 | marginTop: 10,
81 | },
82 |
83 | row: {
84 | paddingLeft: 27,
85 | paddingRight: 27,
86 | alignItems: 'center',
87 | flexDirection: 'row',
88 | backgroundColor: '#FFFFFF',
89 | },
90 |
91 | rowImg: {
92 | width: 20,
93 | height: 20,
94 | marginRight: 20,
95 | },
96 |
97 | rowInner: {
98 | flex: 1,
99 | paddingTop: 20,
100 | paddingBottom: 20,
101 | borderBottomWidth: 0.5,
102 | borderColor: '#F0F0F0',
103 | },
104 |
105 | rowText: {
106 | fontSize: 16,
107 | fontWeight: '400',
108 | }
109 | });
110 |
111 | export default connect(mapStateToProps, mapDispatchToProps)(System);
112 |
--------------------------------------------------------------------------------
/src/pages/home/model.js:
--------------------------------------------------------------------------------
1 | import * as service from './service';
2 | import { AsyncStorage } from 'react-native'
3 |
4 | export default {
5 | namespace: 'home',
6 | state: {
7 | page: 1,
8 | tab: 'all',
9 | data: [],
10 | user: {},
11 | accesstoken: '',
12 | webim_user: {},
13 | webim_accesstoken: '', // 因为home页面最先加载,因此把用户信息都存在home,其他页面从中提取
14 | loading: false,
15 | isLogin: false,
16 | },
17 | effects: {
18 | *init({ payload = {} }, { call, put }) {
19 | const user = yield AsyncStorage.getItem('user')
20 | const accesstoken = yield AsyncStorage.getItem('accesstoken')
21 | const webim_user = yield AsyncStorage.getItem('webim_user')
22 | const webim_accesstoken = yield AsyncStorage.getItem('webim_accesstoken')
23 | if (user) yield put({ type: 'user', payload: JSON.parse(user) })
24 | if (accesstoken) yield put({ type: 'token', payload: accesstoken })
25 | if (webim_user && webim_accesstoken) yield put({ type: 'webim_user', payload: { user: JSON.parse(webim_user), access_token: webim_accesstoken } })
26 | },
27 | *query({ payload = {} }, { call, put }) {
28 | const { page = 1, tab } = payload
29 | yield put({ type: 'tab', payload: tab });
30 | yield put({ type: 'loading', payload: true });
31 | const { data, err } = yield call(service.queryTopics, payload);
32 | yield put({ type: 'loading', payload: false });
33 | if (err) return console.log(err)
34 | yield put({ type: 'page', payload: page });
35 | if (page == 1) yield put({ type: 'query/success', payload: data });
36 | else yield put({ type: 'more/success', payload: data });
37 | },
38 | },
39 | reducers: {
40 | 'query/success'(state, { payload }) {
41 | const [, data] = payload
42 | const topics = service.parseTopics(data.data)
43 | return { ...state, data: topics };
44 | },
45 | 'more/success'(state, { payload }) {
46 | const [, data] = payload
47 | const topics = service.parseTopics(data.data)
48 | return { ...state, data: [...state.data, ...topics] };
49 | },
50 | 'tab'(state, { payload: data }) {
51 | return { ...state, tab: data };
52 | },
53 | 'page'(state, { payload: data }) {
54 | return { ...state, page: data };
55 | },
56 | 'user'(state, { payload: data }) {
57 | if (Object.keys(state.user).length == 0) AsyncStorage.setItem('user', JSON.stringify(data))
58 | return { ...state, user: data };
59 | },
60 | 'token'(state, { payload: data }) {
61 | if (!state.accesstoken) AsyncStorage.setItem('accesstoken', data);
62 | return { ...state, accesstoken: data };
63 | },
64 | 'webim_user'(state, { payload: data }) {
65 | const { user, access_token } = data
66 | if (Object.keys(state.webim_user).length == 0) AsyncStorage.setItem('webim_user', JSON.stringify(user));
67 | if (!state.webim_accesstoken) AsyncStorage.setItem('webim_accesstoken', access_token);
68 | return { ...state, webim_user: user, webim_accesstoken: access_token };
69 | },
70 | 'loading'(state, { payload: data }) {
71 | return { ...state, loading: data };
72 | },
73 | 'isLogin'(state, { payload: data }) {
74 | return { ...state, isLogin: data };
75 | },
76 | 'clean'(state, { payload: data }) {
77 | return { ...state, user: {}, accesstoken: '', webim_user: {}, webim_accesstoken: '' };
78 | },
79 | },
80 | subscriptions: {},
81 | };
82 |
--------------------------------------------------------------------------------
/src/pages/recruit/components/Wrap.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { StyleSheet, View, Image, Text, TouchableOpacity } from 'react-native'
4 |
5 | function Wrap({ item, navigate }) {
6 |
7 | return (
8 | { navigate('Detail', { topic_id: item.id }) }}>
9 |
10 |
11 |
12 | {item.sort}
13 |
14 | {item.title}
15 |
16 |
17 | { navigate('Center', { user: item.author.loginname }) }}>
18 |
19 |
20 |
21 |
22 | {item.author.loginname}
23 |
24 | {item.reply_count} /
25 | {item.visit_count}
26 |
27 |
28 |
29 | {item.create_at}
30 | {item.last_reply_at}
31 |
32 |
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | const styles = StyleSheet.create({
40 | list: {
41 | paddingTop: 10,
42 | paddingLeft: 15,
43 | paddingRight: 15,
44 | paddingBottom: 10,
45 | backgroundColor: '#FFFFFF',
46 | borderBottomWidth: 1,
47 | borderColor: '#F0F0F0'
48 | },
49 |
50 | header: {
51 | flex: 1,
52 | alignItems: 'center',
53 | flexWrap: 'wrap',
54 | flexDirection: 'row'
55 | },
56 |
57 | tab: {
58 | marginRight: 10,
59 | paddingTop: 5,
60 | paddingLeft: 6,
61 | paddingBottom: 5,
62 | paddingRight: 6,
63 | borderRadius: 3,
64 | },
65 |
66 | sort: {
67 | fontSize: 12,
68 | color: '#FFFFFF',
69 | fontWeight: 'bold',
70 | },
71 |
72 | h3: {
73 | flex: 1,
74 | overflow: 'hidden',
75 | fontSize: 16,
76 | fontWeight: 'bold',
77 | },
78 |
79 | one: {
80 | backgroundColor: '#1abc9c',
81 | },
82 |
83 | new: {
84 | backgroundColor: '#6A8FFF',
85 | },
86 |
87 | two: {
88 | backgroundColor: '#e67e22',
89 | },
90 |
91 | default: {
92 | backgroundColor: '#e7e7e7',
93 | },
94 |
95 | content: {
96 | paddingTop: 10,
97 | flexDirection: 'row'
98 | },
99 |
100 | avatar: {
101 | width: 40,
102 | height: 40,
103 | borderRadius: 20,
104 | marginRight: 10,
105 | },
106 |
107 | info: {
108 | flex: 1,
109 | },
110 |
111 | p: {
112 | flexDirection: 'row',
113 | justifyContent: 'space-between',
114 | padding: 3,
115 | },
116 |
117 | status: {
118 | flexDirection: 'row',
119 | },
120 |
121 | name: {
122 | fontSize: 12,
123 | },
124 |
125 | time: {
126 | fontSize: 12,
127 | },
128 |
129 | b: {
130 | fontSize: 12,
131 | fontWeight: 'bold',
132 | },
133 |
134 | reply: {
135 | color: '#42b983',
136 | }
137 | });
138 |
139 | export default Wrap
140 |
--------------------------------------------------------------------------------
/src/pages/notice/components/Seek.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import { StyleSheet, Text, View, Image, TextInput, Dimensions, Platform, TouchableOpacity } from 'react-native';
4 |
5 | const { width } = Dimensions.get('window')
6 | const inputWidth = width - (44 + 15) * 2 - 35 - 4 - 24 // 减去的宽度分别是 导航栏按钮+margin*2,关闭按钮
7 | const rightWidth = width - 44
8 |
9 | class Seek extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | text: "",
14 | visible: false
15 | }
16 | }
17 |
18 | _onSearch = (user) => {
19 | user = user.replace(/(^\s*)|(\s*$)/g, "")
20 | this.props.search({ user })
21 | }
22 |
23 | render() {
24 | const { loading } = this.props
25 | const height = Platform.OS == 'ios' ? 28 : 32
26 | const borderRadius = Platform.OS == 'ios' ? 14 : 16
27 |
28 | return (
29 |
30 |
31 |
32 | { this.setState({ text, visible: true }) }}
37 | />
38 | { this.setState({ text: '' }) }}>
39 | {this.state.visible ? : null}
40 |
41 |
42 | { this._onSearch(this.state.text) }}>
43 | 搜索
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | function mapStateToProps(state) {
51 | const { loading } = state.notice;
52 | return { loading };
53 | }
54 |
55 | function mapDispatchToProps(dispatch) {
56 | return {
57 | search(params) {
58 | dispatch({
59 | type: 'zone/information',
60 | payload: params,
61 | });
62 | },
63 | }
64 | }
65 |
66 | const styles = StyleSheet.create({
67 | headerRight: {
68 | flex: 1,
69 | width: rightWidth,
70 | flexDirection: 'row',
71 | alignItems: 'center',
72 | justifyContent: 'space-between',
73 | },
74 |
75 | headerTouch: {
76 | padding: 15,
77 | alignItems: 'center',
78 | },
79 |
80 | text: {
81 | fontSize: 16,
82 | textAlign: 'center',
83 | width: 44,
84 | fontWeight: 'bold',
85 | color: '#FFFFFF',
86 | marginRight: 15
87 | },
88 |
89 | inputView: {
90 | flexDirection: 'row',
91 | backgroundColor: '#FFFFFF',
92 | borderWidth: 1,
93 | alignItems: 'center',
94 | borderColor: '#FFFFFF',
95 | justifyContent: 'center',
96 | },
97 |
98 | input: {
99 | padding: 0,
100 | fontSize: 14,
101 | width: inputWidth,
102 | },
103 |
104 | headerBtn: {
105 | width: 20,
106 | height: 20,
107 | margin: 4,
108 | },
109 |
110 | closeImg: {
111 | width: 20,
112 | height: 20,
113 | },
114 |
115 | closeBtn: {
116 | width: 20,
117 | height: 20,
118 | margin: 4,
119 | }
120 | });
121 |
122 | export default connect(mapStateToProps, mapDispatchToProps)(Seek);
--------------------------------------------------------------------------------
/src/pages/zone/screen/Login.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import { StyleSheet, View, Text, TextInput, Button, Image, StatusBar, FlatList, Dimensions, TouchableOpacity } from 'react-native'
4 |
5 | class Login extends PureComponent {
6 | constructor(props) {
7 | super(props)
8 | this.state = { text: '' }
9 | }
10 |
11 | static navigationOptions = ({ navigation }) => {
12 | const { state, setParams, navigate } = navigation;
13 | return {
14 | headerTitle: '登录',
15 | };
16 | };
17 |
18 | componentWillReceiveProps(next) {
19 | const { data, navigation } = this.props;
20 | if (next.data && next.data !== data) {
21 | navigation.goBack()
22 | }
23 | }
24 |
25 | _onLogin = (accesstoken) => {
26 | if (!accesstoken) return
27 | this.props.login({ accesstoken })
28 | }
29 |
30 | render() {
31 | const { loading, navigation } = this.props
32 |
33 | return (
34 |
35 |
36 |
37 |
38 |
39 |
40 | { this.setState({ text }) }}
45 | />
46 |
47 | { this._onLogin(this.state.text) }}>
48 | 登录
49 |
50 |
51 | );
52 | }
53 | }
54 |
55 | function mapStateToProps(state) {
56 | const { data, loading } = state.zone;
57 | return { data, loading };
58 | }
59 |
60 | function mapDispatchToProps(dispatch) {
61 | return {
62 | login(params) {
63 | dispatch({
64 | type: 'zone/login',
65 | payload: params,
66 | });
67 | },
68 | }
69 | }
70 |
71 | const styles = StyleSheet.create({
72 | container: {
73 | flex: 1,
74 | backgroundColor: '#FFFFFF',
75 | },
76 |
77 | bgImageWrapper: {
78 | position: 'absolute',
79 | top: 0, bottom: 0, left: 0, right: 0
80 | },
81 |
82 | bgImage: {
83 | flex: 1,
84 | resizeMode: "stretch"
85 | },
86 |
87 | logoView: {
88 | alignItems: 'center',
89 | margin: 15,
90 | marginBottom: 0,
91 | borderRadius: 5,
92 | backgroundColor: '#282828',
93 | },
94 |
95 | logo: {
96 | width: 200,
97 | },
98 |
99 | inputView: {
100 | height: 44,
101 | margin: 15,
102 | marginBottom: 0,
103 | borderRadius: 5,
104 | borderWidth: 1,
105 | borderColor: '#FFFFFF',
106 | justifyContent: 'center',
107 | backgroundColor: '#F8F8F8',
108 | },
109 |
110 | input: {
111 | fontSize: 14,
112 | paddingLeft: 15,
113 | paddingRight: 15,
114 | },
115 |
116 | loginBtn: {
117 | padding: 15,
118 | margin: 15,
119 | borderRadius: 5,
120 | alignItems: 'center',
121 | justifyContent: 'center',
122 | backgroundColor: '#0079FD',
123 | },
124 |
125 | login: {
126 | color: '#FFF',
127 | fontSize: 16,
128 | fontWeight: 'bold',
129 | }
130 | });
131 |
132 | export default connect(mapStateToProps, mapDispatchToProps)(Login);
133 |
--------------------------------------------------------------------------------
/src/pages/notice/components/Message.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { Component, PureComponent } from 'react'
3 | import { connect } from 'dva/mobile';
4 | import { StyleSheet, View, Image, Text, TouchableOpacity } from 'react-native'
5 |
6 | class Message extends PureComponent {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | _onRead = (item) => {
13 | const { accesstoken, navigate } = this.props
14 | const { has_read } = item
15 | const params = { msg_id: item.id, accesstoken }
16 | if (!has_read) this.props.mark_one(params)
17 | navigate('Detail', { topic_id: item.topic.id })
18 | }
19 |
20 | render() {
21 | const { item } = this.props
22 |
23 | return (
24 | { this._onRead(item) }}>
25 |
26 |
27 |
28 | {item.topic.title}
29 |
30 |
31 |
32 |
33 |
34 | {item.author.loginname}
35 | {item.reply.create_at}
36 |
37 |
38 | {item.reply.content}
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 | }
47 |
48 | function mapStateToProps(state) {
49 | const { accesstoken } = state.home
50 | const { hasnot_read_messages } = state.notice;
51 | return { accesstoken, hasnot_read_messages };
52 | }
53 |
54 | function mapDispatchToProps(dispatch) {
55 | return {
56 | mark_one(params) {
57 | dispatch({
58 | type: 'notice/mark_one',
59 | payload: params,
60 | });
61 | },
62 | }
63 | }
64 |
65 | const styles = StyleSheet.create({
66 | list: {
67 | marginBottom: 5,
68 | padding: 15,
69 | borderBottomWidth: 0.5,
70 | borderColor: '#F0F0F0',
71 | backgroundColor: '#FFFFFF',
72 | },
73 |
74 | header: {
75 | flexDirection: 'row'
76 | },
77 |
78 | content: {
79 | flex: 1,
80 | paddingBottom: 15,
81 | alignItems: 'center',
82 | flexWrap: 'wrap',
83 | flexDirection: 'row'
84 | },
85 |
86 | row: {
87 | flex: 1,
88 | justifyContent: 'space-between',
89 | flexDirection: 'row'
90 | },
91 |
92 | tip: {
93 | width: 3,
94 | height: 20,
95 | marginRight: 15,
96 | backgroundColor: '#4B8CE2',
97 | },
98 |
99 | time: {
100 | fontSize: 12,
101 | color: '#999',
102 | },
103 |
104 | h3: {
105 | flex: 8,
106 | overflow: 'hidden',
107 | fontSize: 14,
108 | },
109 |
110 | avatar: {
111 | width: 40,
112 | height: 40,
113 | borderRadius: 20,
114 | marginRight: 10,
115 | },
116 |
117 | info: {
118 | flex: 1,
119 | },
120 |
121 | name: {
122 | flex: 1,
123 | color: '#999',
124 | fontSize: 12,
125 | },
126 |
127 | span: {
128 | flex: 1,
129 | fontSize: 14,
130 | },
131 |
132 | reply: {
133 | marginTop: 10,
134 | }
135 | });
136 |
137 | export default connect(mapStateToProps, mapDispatchToProps)(Message);
--------------------------------------------------------------------------------
/src/pages/detail/service.js:
--------------------------------------------------------------------------------
1 | import { get, post } from '../../utils/request';
2 | import { moment } from '../../utils/tool';
3 |
4 | export async function queryTopic(params) {
5 | const { topic_id, mdrender = true, accesstoken = null } = params
6 | return get(`/topic/${topic_id}?mdrender=${mdrender}&accesstoken=${accesstoken}`);
7 | }
8 |
9 | export async function collect(params) {
10 | const { topic_id, accesstoken } = params
11 | const body = { accesstoken, topic_id }
12 | return post('/topic_collect/collect', body);
13 | }
14 |
15 | export async function de_collect(params) {
16 | const { topic_id, accesstoken } = params
17 | const body = { accesstoken, topic_id }
18 | return post('/topic_collect/de_collect', body);
19 | }
20 |
21 | export async function ups(params) {
22 | const { reply_id, accesstoken } = params
23 | const body = { accesstoken }
24 | return post(`/reply/${reply_id}/ups`, body);
25 | }
26 |
27 | export async function postComment(params) {
28 | const { topic_id, accesstoken, content } = params
29 | const body = { accesstoken, content }
30 | return post(`/topic/${topic_id}/replies`, body);
31 | }
32 |
33 | export async function postReply(params) {
34 | const { topic_id, accesstoken, content, reply_id } = params
35 | const body = { accesstoken, content, reply_id }
36 | return post(`/topic/${topic_id}/replies`, body);
37 | }
38 |
39 | export function parseTopic(data) {
40 | const tabs = { 'top': '置顶', 'ask': '问答', 'good': '精华', 'share': '分享', 'job': '招聘', 'default': '暂无' }
41 | const create_at = moment(data.create_at).startOf('minute').fromNow()
42 | const last_reply_at = moment(data.last_reply_at).startOf('minute').fromNow()
43 | const avatar_url = data.author.avatar_url
44 | const content = data.content.replace(/[\r\n]/g, '')
45 | if (avatar_url && !avatar_url.startsWith('https')) data.author.avatar_url = 'https:' + avatar_url
46 | let tab = data.tab ? data.tab : 'default'
47 | if (data.top) tab = 'top'
48 | const sort = tabs[tab]
49 | const replies = data.replies.map(reply => {
50 | const create_at = moment(reply.create_at).startOf('minute').fromNow()
51 | const last_reply_at = moment(reply.last_reply_at).startOf('minute').fromNow()
52 | const avatar_url = reply.author.avatar_url
53 | if (avatar_url && !avatar_url.startsWith('https')) reply.author.avatar_url = 'https:' + avatar_url
54 | const content = reply.content.replace(/[\r\n]/g, '')
55 | return { ...reply, create_at, last_reply_at, content }
56 | })
57 | const topic = { ...data, create_at, last_reply_at, replies, sort, tab, content }
58 | return topic;
59 | }
60 |
61 | export function parseUps(payload) {
62 | const { result, state, reply_id } = payload
63 | const is_uped = result.action == "up" ? true : false
64 | const replies = state.replies.map((reply) => {
65 | if (reply.id == reply_id) {
66 | reply.is_uped = is_uped
67 | if (is_uped) reply.ups.push(reply_id)
68 | else reply.ups.pop()
69 | }
70 | return reply
71 | })
72 | return replies
73 | }
74 |
75 | export function parseComment(payload) {
76 | const { user, state } = payload
77 | const { loginname, avatar_url, id } = user
78 | const { content } = state
79 | const reply = { id, content, author: { loginname, avatar_url }, create_at: '刚刚', ups: [], reply_id: null, is_uped: false }
80 | const replies = state.replies.concat(reply)
81 | return replies
82 | }
83 |
84 | export function cacheControl(payload) {
85 | const { plate } = payload;
86 | const posts = JSON.parse(localStorage.getItem('posts')) || [];
87 | const [data] = posts.filter(post => post.plate === plate);
88 | if (data) return { posts, cache: data.lists };
89 | else return { posts, cache: null };
90 | }
91 |
--------------------------------------------------------------------------------
/src/pages/home/components/Wrap.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { StyleSheet, View, Image, Text, TouchableOpacity } from 'react-native'
4 |
5 | function Wrap({ item, navigate }) {
6 |
7 | return (
8 | { navigate('Detail', { topic_id: item.id }) }}>
9 |
10 |
11 |
12 | {item.sort}
13 |
14 | {item.title}
15 |
16 |
17 | { navigate('Center', { user: item.author.loginname }) }}>
18 |
19 |
20 |
21 |
22 | {item.author.loginname}
23 |
24 | {item.reply_count} /
25 | {item.visit_count}
26 |
27 |
28 |
29 | {item.create_at}
30 | {item.last_reply_at}
31 |
32 |
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | const styles = StyleSheet.create({
40 | list: {
41 | paddingTop: 10,
42 | paddingLeft: 15,
43 | paddingRight: 15,
44 | paddingBottom: 10,
45 | backgroundColor: '#FFFFFF',
46 | borderBottomWidth: 1,
47 | borderColor: '#F0F0F0'
48 | },
49 |
50 | header: {
51 | flex: 1,
52 | alignItems: 'center',
53 | flexWrap: 'wrap',
54 | flexDirection: 'row'
55 | },
56 |
57 | tab: {
58 | marginRight: 10,
59 | paddingTop: 5,
60 | paddingLeft: 6,
61 | paddingBottom: 5,
62 | paddingRight: 6,
63 | borderRadius: 3,
64 | },
65 |
66 | sort: {
67 | fontSize: 12,
68 | color: '#FFFFFF',
69 | fontWeight: 'bold',
70 | },
71 |
72 | h3: {
73 | flex: 1,
74 | overflow: 'hidden',
75 | fontSize: 16,
76 | fontWeight: 'bold',
77 | },
78 |
79 | top: {
80 | backgroundColor: '#e74c3c',
81 | },
82 |
83 | ask: {
84 | backgroundColor: '#3498db',
85 | },
86 |
87 | good: {
88 | backgroundColor: '#e67e22',
89 | },
90 |
91 | share: {
92 | backgroundColor: '#1abc9c',
93 | },
94 |
95 | job: {
96 | backgroundColor: '#6A8FFF',
97 | },
98 |
99 | dev: {
100 | backgroundColor: '#7A86A2',
101 | },
102 |
103 | default: {
104 | backgroundColor: '#e7e7e7',
105 | },
106 |
107 | content: {
108 | paddingTop: 10,
109 | flexDirection: 'row'
110 | },
111 |
112 | avatar: {
113 | width: 40,
114 | height: 40,
115 | borderRadius: 20,
116 | marginRight: 10,
117 | },
118 |
119 | info: {
120 | flex: 1,
121 | },
122 |
123 | p: {
124 | flexDirection: 'row',
125 | justifyContent: 'space-between',
126 | padding: 3,
127 | },
128 |
129 | status: {
130 | flexDirection: 'row',
131 | },
132 |
133 | name: {
134 | fontSize: 12,
135 | },
136 |
137 | time: {
138 | fontSize: 12,
139 | },
140 |
141 | b: {
142 | fontSize: 12,
143 | fontWeight: 'bold',
144 | },
145 |
146 | reply: {
147 | color: '#42b983',
148 | }
149 | });
150 |
151 | export default Wrap
152 |
--------------------------------------------------------------------------------
/src/pages/recruit/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import Wrap from './components/Wrap';
4 | import { StyleSheet, View, Text, Button, Image, StatusBar, FlatList, Dimensions, TouchableOpacity } from 'react-native'
5 |
6 | class Recruit extends PureComponent {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | static navigationOptions = ({ navigation }) => {
13 | const { state, setParams, navigate } = navigation;
14 | return {
15 | headerLeft: (
16 |
17 | ),
18 | headerRight: (
19 |
20 | { navigate('Publish') }}>
21 |
22 |
23 |
24 | ),
25 | tabBarIcon: ({ focused, tintColor }) => (
26 |
30 | ),
31 | tabBarLabel: '招聘',
32 | };
33 | };
34 |
35 | componentDidMount() {
36 | this.props.query()
37 | }
38 |
39 | componentWillReceiveProps(next) {
40 | const { params } = this.props;
41 | if (next.params !== params) {
42 |
43 | }
44 | }
45 |
46 | _onEndReached = (pageSize) => {
47 | const page = pageSize + 1
48 | this.props.query({ page })
49 | }
50 |
51 | render() {
52 | const { data, page, loading } = this.props
53 | const { navigate } = this.props.navigation;
54 | const { width } = Dimensions.get('window');
55 |
56 | return (
57 |
58 |
59 | item.id}
64 | renderItem={({ item }) => }
65 | onRefresh={() => { this.props.query() }}
66 | onEndReached={() => { this._onEndReached(page) }} // 如果直接 this.props.query() 会请求两次
67 | onEndReachedThreshold={0.5}
68 | refreshing={loading}
69 | />
70 |
71 | );
72 | }
73 | }
74 |
75 | function mapStateToProps(state) {
76 | const { data, page, loading } = state.recruit;
77 | return { data, page, loading };
78 | }
79 |
80 | function mapDispatchToProps(dispatch) {
81 | return {
82 | query(params) {
83 | dispatch({
84 | type: 'recruit/query',
85 | payload: params,
86 | });
87 | },
88 | }
89 | }
90 |
91 | const styles = StyleSheet.create({
92 | container: {
93 | flex: 1,
94 | backgroundColor: '#F8F8F8',
95 | },
96 |
97 | headerLeft: {
98 | height: 44,
99 | width: 80,
100 | marginLeft: 15
101 | },
102 |
103 | headerRight: {
104 | flex: 1,
105 | flexDirection: 'row',
106 | alignItems: 'center'
107 | },
108 |
109 | headerTouch: {
110 | height: 30
111 | },
112 |
113 | headerBtn: {
114 | flex: 1,
115 | width: 30,
116 | height: 30,
117 | marginRight: 15
118 | },
119 |
120 | headerImg: {
121 | borderRadius: 15,
122 | },
123 |
124 | iconBtn: {
125 | width: 25,
126 | height: 25,
127 | },
128 | });
129 |
130 | export default connect(mapStateToProps, mapDispatchToProps)(Recruit);
131 |
--------------------------------------------------------------------------------
/src/utils/webIM/Sdk/src/status.js:
--------------------------------------------------------------------------------
1 | ;
2 | (function () {
3 | var connIndex = 0,
4 | uploadIndex = 100,
5 | downloadIndex = 200,
6 | msgIndex = 300,
7 | statusIndex = 400
8 |
9 | exports.code = {
10 | WEBIM_CONNCTION_USER_NOT_ASSIGN_ERROR: connIndex++,
11 | WEBIM_CONNCTION_OPEN_ERROR: connIndex++,
12 | WEBIM_CONNCTION_AUTH_ERROR: connIndex++,
13 | WEBIM_CONNCTION_OPEN_USERGRID_ERROR: connIndex++,
14 | WEBIM_CONNCTION_ATTACH_ERROR: connIndex++,
15 | WEBIM_CONNCTION_ATTACH_USERGRID_ERROR: connIndex++,
16 | WEBIM_CONNCTION_REOPEN_ERROR: connIndex++,
17 | WEBIM_CONNCTION_SERVER_CLOSE_ERROR: connIndex++, // 7: client-side network offline (net::ERR_INTERNET_DISCONNECTED)
18 | WEBIM_CONNCTION_SERVER_ERROR: connIndex++, // 8: offline by multi login
19 | WEBIM_CONNCTION_IQ_ERROR: connIndex++,
20 |
21 | WEBIM_CONNCTION_PING_ERROR: connIndex++,
22 | WEBIM_CONNCTION_NOTIFYVERSION_ERROR: connIndex++,
23 | WEBIM_CONNCTION_GETROSTER_ERROR: connIndex++,
24 | WEBIM_CONNCTION_CROSSDOMAIN_ERROR: connIndex++,
25 | WEBIM_CONNCTION_LISTENING_OUTOF_MAXRETRIES: connIndex++,
26 | WEBIM_CONNCTION_RECEIVEMSG_CONTENTERROR: connIndex++,
27 | WEBIM_CONNCTION_DISCONNECTED: connIndex++, // 16: server-side close the websocket connection
28 | WEBIM_CONNCTION_AJAX_ERROR: connIndex++,
29 | WEBIM_CONNCTION_JOINROOM_ERROR: connIndex++,
30 | WEBIM_CONNCTION_GETROOM_ERROR: connIndex++,
31 |
32 | WEBIM_CONNCTION_GETROOMINFO_ERROR: connIndex++,
33 | WEBIM_CONNCTION_GETROOMMEMBER_ERROR: connIndex++,
34 | WEBIM_CONNCTION_GETROOMOCCUPANTS_ERROR: connIndex++,
35 | WEBIM_CONNCTION_LOAD_CHATROOM_ERROR: connIndex++,
36 | WEBIM_CONNCTION_NOT_SUPPORT_CHATROOM_ERROR: connIndex++,
37 | WEBIM_CONNCTION_JOINCHATROOM_ERROR: connIndex++,
38 | WEBIM_CONNCTION_QUITCHATROOM_ERROR: connIndex++,
39 | WEBIM_CONNCTION_APPKEY_NOT_ASSIGN_ERROR: connIndex++,
40 | WEBIM_CONNCTION_TOKEN_NOT_ASSIGN_ERROR: connIndex++,
41 | WEBIM_CONNCTION_SESSIONID_NOT_ASSIGN_ERROR: connIndex++,
42 |
43 | WEBIM_CONNCTION_RID_NOT_ASSIGN_ERROR: connIndex++,
44 | WEBIM_CONNCTION_CALLBACK_INNER_ERROR: connIndex++,
45 | WEBIM_CONNCTION_CLIENT_OFFLINE: connIndex++, // 32: client offline
46 | WEBIM_CONNCTION_CLIENT_LOGOUT: connIndex++, // 33: client logout
47 |
48 | WEBIM_UPLOADFILE_BROWSER_ERROR: uploadIndex++,
49 | WEBIM_UPLOADFILE_ERROR: uploadIndex++,
50 | WEBIM_UPLOADFILE_NO_LOGIN: uploadIndex++,
51 | WEBIM_UPLOADFILE_NO_FILE: uploadIndex++,
52 |
53 | WEBIM_DOWNLOADFILE_ERROR: downloadIndex++,
54 | WEBIM_DOWNLOADFILE_NO_LOGIN: downloadIndex++,
55 | WEBIM_DOWNLOADFILE_BROWSER_ERROR: downloadIndex++,
56 |
57 | WEBIM_MESSAGE_REC_TEXT: msgIndex++,
58 | WEBIM_MESSAGE_REC_TEXT_ERROR: msgIndex++,
59 | WEBIM_MESSAGE_REC_EMOTION: msgIndex++,
60 | WEBIM_MESSAGE_REC_PHOTO: msgIndex++,
61 | WEBIM_MESSAGE_REC_AUDIO: msgIndex++,
62 | WEBIM_MESSAGE_REC_AUDIO_FILE: msgIndex++,
63 | WEBIM_MESSAGE_REC_VEDIO: msgIndex++,
64 | WEBIM_MESSAGE_REC_VEDIO_FILE: msgIndex++,
65 | WEBIM_MESSAGE_REC_FILE: msgIndex++,
66 | WEBIM_MESSAGE_SED_TEXT: msgIndex++,
67 | WEBIM_MESSAGE_SED_EMOTION: msgIndex++,
68 | WEBIM_MESSAGE_SED_PHOTO: msgIndex++,
69 | WEBIM_MESSAGE_SED_AUDIO: msgIndex++,
70 | WEBIM_MESSAGE_SED_AUDIO_FILE: msgIndex++,
71 | WEBIM_MESSAGE_SED_VEDIO: msgIndex++,
72 | WEBIM_MESSAGE_SED_VEDIO_FILE: msgIndex++,
73 | WEBIM_MESSAGE_SED_FILE: msgIndex++,
74 |
75 | STATUS_INIT: statusIndex++,
76 | STATUS_DOLOGIN_USERGRID: statusIndex++,
77 | STATUS_DOLOGIN_IM: statusIndex++,
78 | STATUS_OPENED: statusIndex++,
79 | STATUS_CLOSING: statusIndex++,
80 | STATUS_CLOSED: statusIndex++,
81 | STATUS_ERROR: statusIndex++
82 | }
83 | }())
84 |
--------------------------------------------------------------------------------
/ios/cnodejs/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/utils/webIM/Sdk/src/queue.js:
--------------------------------------------------------------------------------
1 | ;(function () {
2 | function Array_h (length) {
3 | this.array = length === undefined ? [] : new Array(length)
4 | }
5 |
6 | Array_h.prototype = {
7 | /**
8 | * 返回数组长度
9 | *
10 | * @return {Number} length [数组长度]
11 | */
12 | length: function () {
13 | return this.array.length
14 | },
15 |
16 | at: function (index) {
17 | return this.array[index]
18 | },
19 |
20 | set: function (index, obj) {
21 | this.array[index] = obj
22 | },
23 |
24 | /**
25 | * 向数组的末尾添加一个或多个元素,并返回新的长度。
26 | *
27 | * @param {*} obj [description]
28 | * @return {Number} length [新数组的长度]
29 | */
30 | push: function (obj) {
31 | return this.array.push(obj)
32 | },
33 |
34 | /**
35 | * 返回数组中选定的元素
36 | *
37 | * @param {Number} start [开始索引值]
38 | * @param {Number} end [结束索引值]
39 | * @return {Array} newArray [新的数组]
40 | */
41 | slice: function (start, end) {
42 | return this.array = this.array.slice(start, end)
43 | },
44 |
45 | concat: function (array) {
46 | this.array = this.array.concat(array)
47 | },
48 |
49 | remove: function (index, count) {
50 | count = count === undefined ? 1 : count
51 | this.array.splice(index, count)
52 | },
53 |
54 | join: function (separator) {
55 | return this.array.join(separator)
56 | },
57 |
58 | clear: function () {
59 | this.array.length = 0
60 | }
61 | }
62 |
63 | /**
64 | * 先进先出队列 (First Input First Output)
65 | *
66 | * 一种先进先出的数据缓存器
67 | */
68 | var Queue = function () {
69 | this._array_h = new Array_h()
70 | }
71 |
72 | Queue.prototype = {
73 | _index: 0,
74 |
75 | /**
76 | * 排队
77 | *
78 | * @param {Object} obj [description]
79 | * @return {[type]} [description]
80 | */
81 | push: function (obj) {
82 | this._array_h.push(obj)
83 | },
84 |
85 | /**
86 | * 出队
87 | *
88 | * @return {Object} [description]
89 | */
90 | pop: function () {
91 | var ret = null
92 | if (this._array_h.length()) {
93 | ret = this._array_h.at(this._index)
94 | if (++this._index * 2 >= this._array_h.length()) {
95 | this._array_h.slice(this._index)
96 | this._index = 0
97 | }
98 | }
99 | return ret
100 | },
101 |
102 | /**
103 | * 返回队列中头部(即最新添加的)的动态对象
104 | *
105 | * @return {Object} [description]
106 | */
107 | head: function () {
108 | var ret = null, len = this._array_h.length()
109 | if (len) {
110 | ret = this._array_h.at(len - 1)
111 | }
112 | return ret
113 | },
114 |
115 | /**
116 | * 返回队列中尾部(即最早添加的)的动态对象
117 | *
118 | * @return {Object} [description]
119 | */
120 | tail: function () {
121 | var ret = null, len = this._array_h.length()
122 | if (len) {
123 | ret = this._array_h.at(this._index)
124 | }
125 | return ret
126 | },
127 |
128 | /**
129 | * 返回数据队列长度
130 | *
131 | * @return {Number} [description]
132 | */
133 | length: function () {
134 | return this._array_h.length() - this._index
135 | },
136 |
137 | /**
138 | * 队列是否为空
139 | *
140 | * @return {Boolean} [description]
141 | */
142 | empty: function () {
143 | return (this._array_h.length() === 0)
144 | },
145 |
146 | clear: function () {
147 | this._array_h.clear()
148 | }
149 | }
150 | exports.Queue = Queue
151 | }())
152 |
--------------------------------------------------------------------------------
/src/pages/zone/service.js:
--------------------------------------------------------------------------------
1 | import { get, post, requestHtml } from '../../utils/request';
2 | import WebIM from '../../utils/webIM';
3 | import { moment } from '../../utils/tool';
4 | import cheerio from 'cheerio-without-node-native';
5 |
6 | export async function queryUser(params) {
7 | const { user } = params
8 | return get(`/user/${user}`);
9 | }
10 |
11 | export async function queryInfo(params) {
12 | const { user } = params
13 | return requestHtml(`https://cnodejs.org/user/${user}`);
14 | }
15 |
16 | export async function getInfo(params) {
17 | const { user } = params
18 | const headers = { Cookie: `CNZZDATA1254020586=461688907-1503624954-https%253A%252F%252Fcnodejs.org%252F%7C1503887769; node_club=s%3A${user.id}%24%24%24%24.WFlKofPUPDLQuxA3BsCw66%2BtcGNqrUXgRCZ8s61yObc;` }
19 | return requestHtml('https://cnodejs.org/setting', headers);
20 | }
21 |
22 | export async function postToken(params) {
23 | return post('/accesstoken', params);
24 | }
25 |
26 | export async function queryCollects(params) {
27 | const { user } = params
28 | return get(`/topic_collect/${user}`);
29 | }
30 |
31 | export async function logout() {
32 | WebIM.conn.close();
33 | }
34 |
35 | export function parseUser(data) {
36 | const create_at = moment(data.create_at).startOf('minute').fromNow()
37 | let avatar_url = data.avatar_url
38 | if (avatar_url && !avatar_url.startsWith('https')) avatar_url = 'https:' + avatar_url
39 | const recent_topics = data.recent_topics.map(topic => {
40 | const last_reply_at = moment(topic.last_reply_at).startOf('minute').fromNow()
41 | return { ...topic, last_reply_at }
42 | })
43 | const recent_replies = data.recent_replies.map(topic => {
44 | const last_reply_at = moment(topic.last_reply_at).startOf('minute').fromNow()
45 | return { ...topic, last_reply_at }
46 | })
47 | const user = { ...data, avatar_url, create_at, recent_topics, recent_replies }
48 | return user
49 | }
50 |
51 | export function parseInfo(data) {
52 | const $ = cheerio.load(data);
53 | let avatar_url = $('.user_big_avatar img').attr('src')
54 | if (avatar_url && !avatar_url.startsWith('https')) avatar_url = 'https:' + avatar_url
55 | const name = $('.user_big_avatar img').attr('title')
56 | const home = $('.unstyled .fa-home').next().text()
57 | const location = $('.unstyled .fa-map-marker').next().text()
58 | const weibo = $('.unstyled .fa-twitter').next().text()
59 | const signature = $('.user_card .signature').text().replace(/[\r\n\s“”]/g, '')
60 | return { home, location, weibo, name, avatar_url, signature };
61 | }
62 |
63 | export function parseInformation(data) {
64 | const $ = cheerio.load(data);
65 | const controls = $('#setting_form .controls')
66 | const info = {};
67 | controls.each(function (i, elem) {
68 | const key = $(this).children().first().attr('id')
69 | const valueEle = $(this).children().first()
70 | const value = valueEle.attr('value') || valueEle.text()
71 | info[key] = value
72 | })
73 | let avatar_url = $('.user_avatar img').attr('src')
74 | if (avatar_url && !avatar_url.startsWith('https')) avatar_url = 'https:' + avatar_url
75 | return { ...info, avatar_url };
76 | }
77 |
78 | export function parseCollects(data) {
79 | const tabs = { 'top': '置顶', 'ask': '问答', 'good': '精华', 'share': '分享', 'job': '招聘', 'default': '暂无' }
80 | const topics = data.map(topic => {
81 | const create_at = moment(topic.create_at).startOf('minute').fromNow()
82 | const last_reply_at = moment(topic.last_reply_at).startOf('minute').fromNow()
83 | const avatar_url = topic.author.avatar_url
84 | if (avatar_url && !avatar_url.startsWith('https')) topic.author.avatar_url = 'https:' + avatar_url
85 | let tab = topic.tab ? topic.tab : 'default'
86 | if (topic.top) tab = 'top'
87 | const sort = tabs[tab]
88 | const title = topic.title.replace(/[\r\n]/g, '')
89 | return { ...topic, create_at, last_reply_at, tab, title, sort }
90 | })
91 | return topics
92 | }
--------------------------------------------------------------------------------
/src/pages/search/components/Seek.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import { StyleSheet, Text, View, Image, TextInput, Dimensions, Platform, TouchableOpacity } from 'react-native';
4 |
5 | const { width } = Dimensions.get('window')
6 | const inputWidth = width - (44 + 15) * 2 - 35 - 4 - 24 // 减去的宽度分别是 导航栏按钮+margin*2,关闭按钮
7 | const rightWidth = width - 44
8 |
9 | class Seek extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = { text: "" }
13 | }
14 |
15 | componentWillReceiveProps(next) {
16 | const { content } = this.props;
17 | if (next.content && next.content !== content) {
18 | this.setState({ text: next.content })
19 | }
20 | }
21 |
22 | _onSearch = (content) => {
23 | if (!content) return
24 | this.props.query({ content })
25 | }
26 |
27 | _onClean = () => {
28 | this.setState({ text: '' })
29 | this.props.clean()
30 | }
31 |
32 | render() {
33 | const { records, visible, loading } = this.props
34 | const height = Platform.OS == 'ios' ? 28 : 32
35 | const borderRadius = Platform.OS == 'ios' ? 14 : 16
36 |
37 | return (
38 |
39 |
40 |
41 | { this.setState({ text }) }}
46 | />
47 | { this._onClean() }}>
48 | {!visible ? : null}
49 |
50 |
51 | { this._onSearch(this.state.text) }}>
52 | 搜索
53 |
54 |
55 | );
56 | }
57 | }
58 |
59 | function mapStateToProps(state) {
60 | const { records, content, visible, loading } = state.search;
61 | return { records, content, visible, loading };
62 | }
63 |
64 | function mapDispatchToProps(dispatch) {
65 | return {
66 | query(params) {
67 | dispatch({
68 | type: 'search/query',
69 | payload: params,
70 | });
71 | },
72 | clean() {
73 | dispatch({
74 | type: 'search/clean',
75 | })
76 | },
77 | }
78 | }
79 |
80 | const styles = StyleSheet.create({
81 | headerRight: {
82 | flex: 1,
83 | width: rightWidth,
84 | flexDirection: 'row',
85 | alignItems: 'center',
86 | justifyContent: 'space-between',
87 | },
88 |
89 | headerTouch: {
90 | padding: 15,
91 | alignItems: 'center',
92 | },
93 |
94 | text: {
95 | fontSize: 16,
96 | textAlign: 'center',
97 | width: 44,
98 | fontWeight: 'bold',
99 | color: '#FFFFFF',
100 | marginRight: 15
101 | },
102 |
103 | inputView: {
104 | flexDirection: 'row',
105 | backgroundColor: '#FFFFFF',
106 | borderWidth: 1,
107 | alignItems: 'center',
108 | borderColor: '#FFFFFF',
109 | justifyContent: 'center',
110 | },
111 |
112 | input: {
113 | padding: 0,
114 | fontSize: 14,
115 | width: inputWidth,
116 | },
117 |
118 | headerBtn: {
119 | width: 20,
120 | height: 20,
121 | margin: 4,
122 | },
123 |
124 | closeImg: {
125 | width: 20,
126 | height: 20,
127 | },
128 |
129 | closeBtn: {
130 | width: 20,
131 | height: 20,
132 | margin: 4,
133 | }
134 | });
135 |
136 | export default connect(mapStateToProps, mapDispatchToProps)(Seek);
--------------------------------------------------------------------------------
/src/pages/notice/components/ChatRow.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { Component, PureComponent } from 'react'
3 | import { connect } from 'dva/mobile';
4 | import { StyleSheet, View, Image, Text, Alert, TouchableOpacity } from 'react-native'
5 |
6 | class ChatRow extends PureComponent {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | _onLongPress = (user) => {
13 | const { owner } = this.props
14 | Alert.alert(
15 | '删除信息?', null,
16 | [
17 | { text: '取消', onPress: () => console.log('cancle') },
18 | { text: '确定', onPress: () => this.props.delete({ user, owner }) },
19 | ]
20 | )
21 | }
22 |
23 | _renderWidth = ({ count }) => {
24 | if (count < 10) return 10
25 | else if (count > 10 && count < 100) return 10 * 2
26 | else if (count > 100 && count < 1000) return 10 * 3
27 | else if (count >= 1000) return 10 * 4
28 | }
29 |
30 | render() {
31 | const { item, navigate } = this.props
32 | const width = this._renderWidth(item)
33 | item.count = item.count <= 999 ? item.count : '+999'
34 |
35 | return (
36 | { navigate('Chat', { user: item }) }} onLongPress={() => { this._onLongPress(item) }}>
37 |
38 |
39 |
40 | {
41 | item.count !== 0 ?
42 |
43 | {item.count}
44 |
45 | : null
46 | }
47 |
48 |
49 |
50 | {item.name ? item.name : '未知'}
51 | {item.createdAt}
52 |
53 | {item.text}
54 |
55 |
56 |
57 | )
58 | }
59 | }
60 |
61 |
62 | function mapStateToProps(state) {
63 | const { chat_history, loading } = state.notice;
64 | const { user: owner } = state.home;
65 | return { owner, chat_history, loading };
66 | }
67 |
68 | function mapDispatchToProps(dispatch) {
69 | return {
70 | delete(params) {
71 | dispatch({
72 | type: 'notice/delete_sigle_chat',
73 | payload: params,
74 | });
75 | },
76 | }
77 | }
78 |
79 | const styles = StyleSheet.create({
80 | row: {
81 | paddingLeft: 17,
82 | paddingRight: 17,
83 | alignItems: 'center',
84 | flexDirection: 'row',
85 | backgroundColor: '#FFFFFF',
86 | },
87 |
88 | avatarBox: {
89 | width: 50,
90 | height: 50,
91 | justifyContent: 'center',
92 | },
93 |
94 | avatar: {
95 | width: 40,
96 | height: 40,
97 | borderRadius: 20,
98 | },
99 |
100 | rowInner: {
101 | flex: 1,
102 | paddingTop: 15,
103 | paddingBottom: 15,
104 | borderBottomWidth: 0.5,
105 | borderColor: '#F0F0F0',
106 | },
107 |
108 | info: {
109 | flexDirection: 'row',
110 | alignItems: 'center',
111 | justifyContent: 'space-between',
112 | },
113 |
114 | name: {
115 | fontSize: 16,
116 | fontWeight: '400',
117 | },
118 |
119 | content: {
120 | fontSize: 14,
121 | color: '#999',
122 | paddingTop: 3,
123 | },
124 |
125 | time: {
126 | fontSize: 12,
127 | color: '#999',
128 | },
129 |
130 | countBox: {
131 | position: 'absolute',
132 | top: 0,
133 | right: 0,
134 | height: 20,
135 | padding: 5,
136 | borderRadius: 10,
137 | alignItems: 'center',
138 | justifyContent: 'center',
139 | backgroundColor: '#F03737',
140 | },
141 |
142 | count: {
143 | fontSize: 12,
144 | textAlign: 'center',
145 | color: '#FFFFFF',
146 | }
147 | });
148 |
149 | export default connect(mapStateToProps, mapDispatchToProps)(ChatRow);
150 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native 实现cnodejs客户端
2 |
3 | 首页和详情页UI参考 https://github.com/shinygang/Vue-cnodejs
4 |
5 | API由 https://cnodejs.org/api 提供
6 |
7 | 在官方提供的API之外,增加搜索入口和个人资料入口。
8 |
9 | # 如何运行
10 |
11 | npm install 或者 yarn
12 | react-native start
13 | react-native run-android 或者 react-native run-ios
14 |
15 | 如果还没有安装 react-native 那么先 npm i react-native-cli -g
16 | 如果遇到无法启动问题,先尝试将 node 版本切换到8.1.0(推荐使用nvm安装)
17 | 如果npm安装有提示问题,那么尝试使用 yarn 安装
18 |
19 | # 主要功能
20 |
21 | token登录、退出;
22 | 查看、发布、编辑主题;
23 | 点赞评论、回复主题、收藏主题;
24 | 搜索、添加好友、实时聊天;
25 | 查看资料、查看收藏的主题、回复的主题;
26 |
27 | # 更新
28 |
29 | 17-9-25:
30 | 集成聊天,实现搜索好友,处理申请,实时聊天;
31 |
32 | 17-9-26:
33 | 修复安卓搜索框显示不全问题
34 | 修复安卓tabbar的icon不能显示问题
35 | 新增对评论进行回复
36 | 优化FlatList显示逻辑
37 | 修复搜索记录不能及时更新问题
38 | 修复android首页发布话题按钮不显示问题
39 | 修复点赞、收藏图标点击后不显示问题
40 |
41 | # 说明
42 |
43 | 这个项目之间断断续续花了差不多12天时间,之前有一点react基础,因此做react-native上手就稍微快了。
44 |
45 | 基本就是一边看文档一边做,不懂的Google,所以这个项目可能有些地方写的不好,有时间想到了改进下项目,但是期间学习到了很多知识。
46 |
47 | 比较复杂的功能还需要时间学习。
48 |
49 | 这只是一个练手的项目,希望大家多多交流
50 |
51 | # 部分演示
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | # 部分截图
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | # 等待修复问题
92 |
93 | - [ ] 上拉加载更多数据的时候,在android由于加载数据时间比较久,导致不能点击item,只有等待loading结束后才能正常点击(猜测是FlatList的问题,待观察)。
94 | - [ ] android端,点击主页上的tab,反应慢的问题(Android貌似都会比ios慢半拍 = = )。
95 | - [ ] 文章图片显示问题,可能需要换个解析html的框架。
96 | - [x] android如果图片设置 position:'absolute',不能显示问题,比如新建文章按钮。
97 | - [x] android如果图片是点击后才显示的,不显示问题,比如点赞和收藏按钮。
98 |
99 | # 待完善的功能
100 |
101 | - [ ] 实现扫码登录
102 | - [ ] 首页UI调整区分 首页和招聘板块
103 | - [x] 详情页面内容显示优化
104 | - [x] 添加回复评论
105 | - [ ] 话题发布优化
106 |
--------------------------------------------------------------------------------
/src/pages/zone/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { StyleSheet, Text, View, Image, Linking, Dimensions, TouchableOpacity } from 'react-native';
3 |
4 | class Header extends Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | render() {
10 | const { data, navigate } = this.props
11 |
12 | return (
13 | Object.keys(data).length == 0 ?
14 | { navigate('Login') }}>
15 |
16 |
17 |
18 |
19 | 未登录
20 |
21 |
22 |
23 |
24 | :
25 | { navigate('Personal', { user: data.loginname }) }}>
26 |
27 |
28 |
29 |
30 | {data.loginname}
31 | 注册于: {data.create_at}
32 |
33 |
34 |
35 |
36 |
37 | { navigate('Credits', { data }) }}>
38 |
39 |
40 |
41 | 论坛积分
42 | {data.score}
43 |
44 |
45 |
46 | { Linking.openURL(`https://github.com/${data.githubUsername}`) }}>
47 |
48 |
49 |
50 | 代码仓库
51 | {data.githubUsername}
52 |
53 |
54 |
55 |
56 |
57 | );
58 | }
59 | }
60 |
61 | const styles = StyleSheet.create({
62 | header: {
63 | backgroundColor: '#FFFFFF',
64 | },
65 |
66 | inner: {
67 | alignItems: 'center',
68 | flexDirection: 'row',
69 | marginLeft: 15,
70 | marginRight: 15,
71 | padding: 15,
72 | borderBottomWidth: 0.5,
73 | borderColor: '#F0F0F0',
74 | },
75 |
76 | avatar: {
77 | width: 50,
78 | height: 50,
79 | borderRadius: 25,
80 | marginRight: 15,
81 | },
82 |
83 | sub: {
84 | paddingTop: 5,
85 | paddingBottom: 5,
86 | color: '#999',
87 | fontSize: 12,
88 | },
89 |
90 | login: {
91 | fontSize: 18,
92 | marginLeft: 15,
93 | },
94 |
95 | name: {
96 | color: '#000000',
97 | fontSize: 16,
98 | },
99 |
100 | col: {
101 | flex: 1,
102 | },
103 |
104 | rowList: {
105 | marginTop: 10,
106 | },
107 |
108 | row: {
109 | paddingLeft: 27,
110 | paddingRight: 27,
111 | alignItems: 'center',
112 | flexDirection: 'row',
113 | backgroundColor: '#FFFFFF',
114 | },
115 |
116 | rowImg: {
117 | width: 20,
118 | height: 20,
119 | marginRight: 20,
120 | },
121 |
122 | rowInner: {
123 | flex: 1,
124 | paddingTop: 20,
125 | paddingBottom: 20,
126 | flexDirection: 'row',
127 | justifyContent: 'space-between',
128 | borderBottomWidth: 0.5,
129 | borderColor: '#F0F0F0',
130 | },
131 |
132 | rowText: {
133 | fontSize: 16,
134 | fontWeight: '400',
135 | },
136 |
137 | iconBtn: {
138 | width: 25,
139 | height: 25,
140 | },
141 |
142 | span: {
143 | color: '#999',
144 | fontSize: 14,
145 | },
146 | });
147 |
148 | export default Header
--------------------------------------------------------------------------------
/src/pages/zone/screen/Center.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import Card from '../components/Card';
4 | import Header from '../components/Header';
5 | import { StyleSheet, View, ScrollView, RefreshControl, Text, Button, Image, StatusBar, FlatList, Dimensions, TouchableOpacity } from 'react-native'
6 |
7 | const { width } = Dimensions.get('window');
8 |
9 | class Zone extends PureComponent {
10 | constructor(props) {
11 | super(props)
12 | this.state = {}
13 | }
14 |
15 | static navigationOptions = ({ navigation }) => {
16 | const { state, setParams } = navigation;
17 | return {
18 | headerTitle: '空间',
19 | };
20 | };
21 |
22 | componentDidMount() {
23 | const { params } = this.props.navigation.state;
24 | this.props.query(params)
25 | }
26 |
27 | render() {
28 | const { params } = this.props.navigation.state;
29 | const { other_user, other_data, loading } = this.props
30 | const { navigate } = this.props.navigation;
31 | const headerProps = { data: other_data, navigate }
32 |
33 | return (
34 | { this.props.query(params) }} refreshing={loading} />}>
35 |
36 |
37 |
38 | { navigate('Dynamic', { title: '最近回复', data: other_data.recent_replies }) }}>
39 |
40 |
41 |
42 | 最近回复
43 | {other_data.recent_replies ? other_data.recent_replies.length : '0'}
44 |
45 |
46 |
47 | { navigate('Dynamic', { title: '最新发布', data: other_data.recent_topics }) }}>
48 |
49 |
50 |
51 | 最新发布
52 | {other_data.recent_topics ? other_data.recent_topics.length : '0'}
53 |
54 |
55 |
56 | { navigate('Collect', { ...params }) }}>
57 |
58 |
59 |
60 | 话题收藏
61 |
62 |
63 |
64 |
65 |
66 | );
67 | }
68 | }
69 |
70 | function mapStateToProps(state) {
71 | const { other_data, loading } = state.zone;
72 | return { other_data, loading };
73 | }
74 |
75 | function mapDispatchToProps(dispatch) {
76 | return {
77 | query(params) {
78 | dispatch({
79 | type: 'zone/otherInfo',
80 | payload: params,
81 | });
82 | },
83 | }
84 | }
85 |
86 | const styles = StyleSheet.create({
87 | container: {
88 | flex: 1,
89 | backgroundColor: '#F8F8F8',
90 | },
91 |
92 | rowList: {
93 | marginTop: 10,
94 | },
95 |
96 | row: {
97 | paddingLeft: 27,
98 | paddingRight: 27,
99 | alignItems: 'center',
100 | flexDirection: 'row',
101 | backgroundColor: '#FFFFFF',
102 | },
103 |
104 | rowImg: {
105 | width: 20,
106 | height: 20,
107 | marginRight: 20,
108 | },
109 |
110 | rowInner: {
111 | flex: 1,
112 | paddingTop: 20,
113 | paddingBottom: 20,
114 | flexDirection: 'row',
115 | justifyContent: 'space-between',
116 | borderBottomWidth: 0.5,
117 | borderColor: '#F0F0F0',
118 | },
119 |
120 | rowText: {
121 | fontSize: 16,
122 | fontWeight: '400',
123 | },
124 |
125 | iconBtn: {
126 | width: 25,
127 | height: 25,
128 | },
129 |
130 | span: {
131 | color: '#999',
132 | fontSize: 14,
133 | }
134 | });
135 |
136 | export default connect(mapStateToProps, mapDispatchToProps)(Zone);
137 |
--------------------------------------------------------------------------------
/src/pages/publish/components/Title.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import { StyleSheet, Text, View, Image, TextInput, Modal, Dimensions, TouchableOpacity, TouchableWithoutFeedback } from 'react-native';
4 |
5 | const { width } = Dimensions.get('window');
6 | const defaultWidth = width - 90 * 2
7 |
8 | class Title extends Component {
9 | constructor(props) {
10 | super(props);
11 | this.state = { visible: false }
12 | }
13 |
14 | componentDidMount() {
15 | const { title = '' } = this.props.data
16 | this.props.setTitle(title)
17 | }
18 |
19 | _onSelect = (tab) => {
20 | this.props.setTab(tab)
21 | this.setState({ visible: false })
22 | }
23 |
24 | render() {
25 | const { tab, title } = this.props
26 | const tabs = [{ key: 'ask', value: '问答' }, { key: 'share', value: '分享' }, { key: 'dev', value: '测试' }]
27 | const tabDefault = { 'ask': '问答', 'share': '分享', 'dev': '测试' }
28 |
29 | return (
30 |
31 |
32 | { this.props.setTitle(title) }}
37 | />
38 | { this.setState({ visible: true }) }}>
39 |
40 | {tabDefault[tab]}
41 |
42 |
43 |
44 | null} //修复安卓modal的告警
49 | >
50 | { this.setState({ visible: false }) }}>
51 |
52 |
53 | {
54 | tabs.map((tab, index) => (
55 | { this._onSelect(tab.key) }}>
56 |
57 | {tab.value}
58 |
59 | {index != tabs.length - 1 ? : null}
60 |
61 | ))
62 | }
63 |
64 |
65 |
66 |
67 |
68 | );
69 | }
70 | }
71 |
72 | function mapStateToProps(state) {
73 | const { tab, title } = state.publish;
74 | const { data } = state.detail;
75 | return { tab, title, data };
76 | }
77 |
78 | function mapDispatchToProps(dispatch) {
79 | return {
80 | setTab(params) {
81 | dispatch({
82 | type: 'publish/tab',
83 | payload: params,
84 | });
85 | },
86 | setTitle(params) {
87 | dispatch({
88 | type: 'publish/title',
89 | payload: params,
90 | });
91 | },
92 | }
93 | }
94 |
95 | const styles = StyleSheet.create({
96 | modalContainer: {
97 | flex: 1,
98 | alignItems: 'center',
99 | justifyContent: 'center',
100 | backgroundColor: 'rgba(0, 0, 0, 0.5)',
101 | },
102 |
103 | titleView: {
104 | height: 44,
105 | borderRadius: 5,
106 | borderWidth: 1,
107 | margin: 15,
108 | marginBottom: 0,
109 | flexDirection: 'row',
110 | borderColor: '#FFFFFF',
111 | justifyContent: 'center',
112 | backgroundColor: '#F8F8F8',
113 | },
114 |
115 | input: {
116 | flex: 8,
117 | fontSize: 16,
118 | paddingLeft: 15,
119 | paddingRight: 15,
120 | },
121 |
122 | tabView: {
123 | flex: 2,
124 | margin: 3,
125 | paddingLeft: 15,
126 | paddingRight: 15,
127 | borderRadius: 5,
128 | alignItems: 'center',
129 | justifyContent: 'center',
130 | backgroundColor: '#FFFFFF',
131 | },
132 |
133 | modal: {
134 | width: defaultWidth,
135 | borderRadius: 5,
136 | backgroundColor: '#FFF',
137 | justifyContent: 'center',
138 | },
139 |
140 | rowView: {
141 | padding: 16,
142 | },
143 |
144 | rowLine: {
145 | height: 1,
146 | backgroundColor: '#F0F0F0',
147 | },
148 |
149 | rowText: {
150 | textAlign: 'center',
151 | }
152 | });
153 |
154 | export default connect(mapStateToProps, mapDispatchToProps)(Title);
--------------------------------------------------------------------------------
/.vscode/.react/debuggerWorker.js:
--------------------------------------------------------------------------------
1 |
2 | // Initialize some variables before react-native code would access them
3 | var onmessage=null, self=global;
4 | // Cache Node's original require as __debug__.require
5 | global.__debug__={require: require};
6 | // avoid Node's GLOBAL deprecation warning
7 | Object.defineProperty(global, "GLOBAL", {
8 | configurable: true,
9 | writable: true,
10 | enumerable: true,
11 | value: global
12 | });
13 |
14 | var vscodeHandlers = {
15 | 'vscode_reloadApp': function () {
16 | try {
17 | global.require('NativeModules').DevMenu.reload();
18 | } catch (err) {
19 | // ignore
20 | }
21 | },
22 | 'vscode_showDevMenu': function () {
23 | try {
24 | var DevMenu = global.require('NativeModules').DevMenu.show();
25 | } catch (err) {
26 | // ignore
27 | }
28 | }
29 | };
30 |
31 | process.on("message", function (message) {
32 | if (message.data && vscodeHandlers[message.data.method]) {
33 | vscodeHandlers[message.data.method]();
34 | } else if(onmessage) {
35 | onmessage(message);
36 | }
37 | });
38 |
39 | var postMessage = function(message){
40 | process.send(message);
41 | };
42 |
43 | if (!self.postMessage) {
44 | self.postMessage = postMessage;
45 | }
46 |
47 | var importScripts = (function(){
48 | var fs=require('fs'), vm=require('vm');
49 | return function(scriptUrl){
50 | var scriptCode = fs.readFileSync(scriptUrl, "utf8");
51 | vm.runInThisContext(scriptCode, {filename: scriptUrl});
52 | };
53 | })();
54 |
55 | /**
56 | * Copyright (c) 2015-present, Facebook, Inc.
57 | * All rights reserved.
58 | *
59 | * This source code is licensed under the BSD-style license found in the
60 | * LICENSE file in the root directory of this source tree. An additional grant
61 | * of patent rights can be found in the PATENTS file in the same directory.
62 | */
63 |
64 | /* global __fbBatchedBridge, self, importScripts, postMessage, onmessage: true */
65 | /* eslint no-unused-vars: 0 */
66 |
67 | 'use strict';
68 |
69 | onmessage = (function() {
70 | var visibilityState;
71 | var showVisibilityWarning = (function() {
72 | var hasWarned = false;
73 | return function() {
74 | // Wait until `YellowBox` gets initialized before displaying the warning.
75 | if (hasWarned || console.warn.toString().includes('[native code]')) {
76 | return;
77 | }
78 | hasWarned = true;
79 | console.warn(
80 | 'Remote debugger is in a background tab which may cause apps to ' +
81 | 'perform slowly. Fix this by foregrounding the tab (or opening it in ' +
82 | 'a separate window).'
83 | );
84 | };
85 | })();
86 |
87 | var messageHandlers = {
88 | 'executeApplicationScript': function(message, sendReply) {
89 | for (var key in message.inject) {
90 | self[key] = JSON.parse(message.inject[key]);
91 | }
92 | var error;
93 | try {
94 | importScripts(message.url);
95 | } catch (err) {
96 | error = err.message;
97 | }
98 | sendReply(null /* result */, error);
99 | },
100 | 'setDebuggerVisibility': function(message) {
101 | visibilityState = message.visibilityState;
102 | },
103 | };
104 |
105 | return function(message) {
106 | if (visibilityState === 'hidden') {
107 | showVisibilityWarning();
108 | }
109 |
110 | var object = message.data;
111 |
112 | var sendReply = function(result, error) {
113 | postMessage({replyID: object.id, result: result, error: error});
114 | };
115 |
116 | var handler = messageHandlers[object.method];
117 | if (handler) {
118 | // Special cased handlers
119 | handler(object, sendReply);
120 | } else {
121 | // Other methods get called on the bridge
122 | var returnValue = [[], [], [], 0];
123 | var error;
124 | try {
125 | if (typeof __fbBatchedBridge === 'object') {
126 | returnValue = __fbBatchedBridge[object.method].apply(null, object.arguments);
127 | } else {
128 | error = 'Failed to call function, __fbBatchedBridge is undefined';
129 | }
130 | } catch (err) {
131 | error = err.message;
132 | } finally {
133 | sendReply(JSON.stringify(returnValue), error);
134 | }
135 | }
136 | };
137 | })();
138 |
139 | // Notify debugger that we're done with loading
140 | // and started listening for IPC messages
141 | postMessage({workerLoaded:true});
--------------------------------------------------------------------------------
/src/pages/search/components/History.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import { StyleSheet, Text, View, Image, TextInput, FlatList, Dimensions, TouchableOpacity } from 'react-native';
4 |
5 | const { height } = Dimensions.get('window')
6 |
7 | class History extends Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | _onSearch = (text) => {
13 | const params = { content: text }
14 | this.props.query(params)
15 | }
16 |
17 | _onDelet = (text) => {
18 | const { records } = this.props
19 | const datas = records.filter(history => history !== text);
20 | this.props.delet(datas);
21 | }
22 |
23 | render() {
24 | const hosts = ['NodeJs', 'Web', 'ReactJs', 'Vuejs', 'Mysql', 'JavaScript', 'Express', 'ES6']
25 | const { records, visible, loading } = this.props
26 |
27 | return (
28 |
29 |
30 |
31 | 热门搜索
32 |
33 |
34 | {
35 | hosts.map((host, index) => (
36 | { this._onSearch(host) }}>
37 | {host}
38 |
39 | ))
40 | }
41 |
42 |
43 | {
44 | records.length > 0 ?
45 |
46 |
47 | 搜索记录
48 |
49 | {
50 | records.map((record, index) => (
51 | { this._onSearch(record) }}>
52 |
53 |
54 | {record}
55 |
56 | { this._onDelet(record) }}>
57 |
58 |
59 |
60 | ))
61 | }
62 | : null
63 | }
64 |
65 | );
66 | }
67 | }
68 |
69 | function mapStateToProps(state) {
70 | const { records, loading } = state.search;
71 | return { records, loading };
72 | }
73 |
74 | function mapDispatchToProps(dispatch) {
75 | return {
76 | query(params) {
77 | dispatch({
78 | type: 'search/query',
79 | payload: params,
80 | });
81 | },
82 | delet(params) {
83 | dispatch({
84 | type: 'search/records',
85 | payload: params,
86 | });
87 | },
88 | }
89 | }
90 |
91 | const styles = StyleSheet.create({
92 | container: {
93 | backgroundColor: '#FFFFFF',
94 | },
95 |
96 | hots: {
97 | marginLeft: 15,
98 | marginTop: 15,
99 | },
100 |
101 | titleView: {
102 | marginBottom: 15,
103 | },
104 |
105 | title: {
106 | color: '#999999',
107 | fontSize: 12,
108 | },
109 |
110 | hotsRow: {
111 | flexDirection: 'row',
112 | flexWrap: 'wrap',
113 | justifyContent: 'space-between',
114 | },
115 |
116 | hotsBtn: {
117 | paddingTop: 5,
118 | paddingBottom: 5,
119 | paddingRight: 12,
120 | paddingLeft: 12,
121 | borderRadius: 3,
122 | marginRight: 15,
123 | marginBottom: 15,
124 | backgroundColor: '#7A86A2',
125 | },
126 |
127 | hotsText: {
128 | color: '#FFFFFF',
129 | },
130 |
131 | records: {
132 | marginLeft: 15,
133 | marginRight: 15,
134 | },
135 |
136 | recordRow: {
137 | flexDirection: 'row',
138 | justifyContent: 'space-between',
139 | borderTopWidth: 0.5,
140 | borderColor: '#eee',
141 | paddingTop: 15,
142 | paddingBottom: 15,
143 | },
144 |
145 | left: {
146 | flexDirection: 'row',
147 | alignItems: 'center',
148 | justifyContent: 'space-between',
149 | },
150 |
151 | icon: {
152 | width: 20,
153 | height: 20,
154 | marginLeft: 6,
155 | marginRight: 12,
156 | },
157 |
158 | recordText: {
159 | fontSize: 14,
160 | },
161 | });
162 |
163 | export default connect(mapStateToProps, mapDispatchToProps)(History);
--------------------------------------------------------------------------------
/src/pages/notice/screen/Chat.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import { GiftedChat, Actions, Bubble } from 'react-native-gifted-chat';
4 | import { StyleSheet, View, Text, TextInput, Button, Image, StatusBar, FlatList, Dimensions, TouchableOpacity } from 'react-native'
5 |
6 | const { width } = Dimensions.get('window')
7 | const defaultInputWidth = width - 40
8 |
9 | class Chat extends PureComponent {
10 | constructor(props) {
11 | super(props)
12 | this.state = {
13 | messages: [],
14 | }
15 | }
16 |
17 | static navigationOptions = ({ navigation }) => {
18 | const { state, setParams, navigate } = navigation;
19 | const { user } = state.params;
20 | return {
21 | headerTitle: `${user.name}`,
22 | headerRight: (
23 |
24 | { navigate('ChatMessage', { user }) }}>
25 |
26 |
27 |
28 | ),
29 | };
30 | };
31 |
32 | componentDidMount() {
33 | const { user } = this.props.navigation.state.params;
34 | const { owner } = this.props;
35 | this.props.fetchMessage({ user })
36 | this.props.cleanCount({ user, owner })
37 | }
38 |
39 | componentWillUnmount() {
40 | const { user } = this.props.navigation.state.params;
41 | const { owner } = this.props;
42 | this.props.cleanCount({ user, owner })
43 | }
44 |
45 | _onSend = (messages = []) => {
46 | const [message] = messages
47 | const { user } = this.props.navigation.state.params;
48 | const { owner } = this.props
49 | const params = { to: user.name, msg: message.text }
50 | this.props.sendMessage(params)
51 | this.props.saveMessage({ user, owner, message })
52 | }
53 |
54 | _renderBubble(props) {
55 | return ();
56 | }
57 |
58 | _renderFooter(props) {
59 | return (null);
60 | }
61 |
62 | render() {
63 | const { messages, owner, loading } = this.props
64 | const { navigate } = this.props.navigation;
65 |
66 | return (
67 |
68 |
69 | this._onSend(messages)}
75 | renderBubble={this._renderBubble.bind(this)}
76 | renderFooter={this._renderFooter.bind(this)}
77 | user={{ _id: owner.loginname, name: owner.loginname, avatar: owner.avatar_url }}
78 | />
79 |
80 | );
81 | }
82 | }
83 |
84 | function mapStateToProps(state) {
85 | const { messages, loading } = state.notice;
86 | const { user: owner } = state.home;
87 | return { messages, owner, loading };
88 | }
89 |
90 | function mapDispatchToProps(dispatch) {
91 | return {
92 | sendMessage(params) {
93 | dispatch({
94 | type: 'notice/send_message',
95 | payload: params,
96 | });
97 | },
98 | fetchMessage(params) {
99 | dispatch({
100 | type: 'notice/fetch_message',
101 | payload: params,
102 | });
103 | },
104 | saveMessage(params) {
105 | dispatch({
106 | type: 'notice/save_message',
107 | payload: params,
108 | });
109 | },
110 | cleanCount(params) {
111 | dispatch({
112 | type: 'notice/clean_count',
113 | payload: params,
114 | });
115 | },
116 | }
117 | }
118 |
119 | const styles = StyleSheet.create({
120 | container: {
121 | flex: 1,
122 | backgroundColor: '#F8F8F8',
123 | },
124 |
125 | headerRight: {
126 | flex: 1,
127 | flexDirection: 'row',
128 | alignItems: 'center'
129 | },
130 |
131 | headerTouch: {
132 | height: 30
133 | },
134 |
135 | headerBtn: {
136 | flex: 1,
137 | width: 30,
138 | height: 30,
139 | marginRight: 15
140 | },
141 |
142 | inputView: {
143 | position: 'absolute',
144 | bottom: 0,
145 | },
146 |
147 | contentTouch: {
148 | padding: 10,
149 | alignItems: 'center',
150 | justifyContent: 'center',
151 | },
152 |
153 | contentImg: {
154 | width: 24,
155 | height: 24,
156 | },
157 |
158 | contentView: {
159 | height: 44,
160 | flexDirection: 'row',
161 | justifyContent: 'center',
162 | backgroundColor: '#FFF',
163 | },
164 |
165 | input: {
166 | width: defaultInputWidth,
167 | fontSize: 16,
168 | paddingLeft: 15,
169 | paddingRight: 15,
170 | },
171 |
172 | commentTouch: {
173 | height: 30,
174 | },
175 | });
176 |
177 | export default connect(mapStateToProps, mapDispatchToProps)(Chat);
178 |
--------------------------------------------------------------------------------
/src/pages/notice/screen/Roster.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import { Tip } from '../../../components';
4 | import { StyleSheet, View, Text, Button, Image, StatusBar, FlatList, Alert, Dimensions, TouchableOpacity } from 'react-native'
5 |
6 | const { width } = Dimensions.get('window');
7 |
8 | class Roster extends PureComponent {
9 | constructor(props) {
10 | super(props)
11 | this.state = {}
12 | }
13 |
14 | static navigationOptions = ({ navigation }) => {
15 | const { state, setParams } = navigation;
16 | return {
17 | headerTitle: '好友申请',
18 | };
19 | };
20 |
21 | componentDidMount() {
22 | const { params } = this.props.navigation.state;
23 | // this.props.query(params)
24 | }
25 |
26 | _onLongPress = (user) => {
27 | const { owner } = this.props
28 | Alert.alert(
29 | '是否删除?', null,
30 | [
31 | { text: '取消', onPress: () => console.log('cancle') },
32 | { text: '确定', onPress: () => this.props.delete({ user, owner }) },
33 | ]
34 | )
35 | }
36 |
37 | _renderRow(item) {
38 | const { navigate } = this.props.navigation;
39 |
40 | return (
41 | { navigate('Information', { user: item }) }} onLongPress={() => { this._onLongPress(item) }}>
42 |
43 |
44 |
45 | {item.from}
46 | {
47 | item.type === 'subscribe' ?
48 | {item.status}
49 | : 对方已经是您的好友
50 | }
51 |
52 | {
53 | item.type === 'subscribe' ?
54 | { this.props.on_subscribe({ user: item }) }}>
55 | 同意
56 |
57 | :
58 | 已添加
59 |
60 | }
61 |
62 |
63 | );
64 | }
65 |
66 | render() {
67 | const { strangers, loading } = this.props
68 | const { navigate } = this.props.navigation;
69 |
70 | return (
71 |
72 |
73 | {
74 | strangers.length > 0 ?
75 |
76 | index}
81 | renderItem={({ item }) => this._renderRow(item)}
82 | />
83 |
84 | :
85 | }
86 |
87 | );
88 | }
89 | }
90 |
91 | function mapStateToProps(state) {
92 | const { strangers, loading } = state.notice;
93 | const { user: owner } = state.home;
94 | return { strangers, owner, loading };
95 | }
96 |
97 | function mapDispatchToProps(dispatch) {
98 | return {
99 | on_subscribe(params) {
100 | dispatch({
101 | type: 'notice/on_subscribe',
102 | payload: params,
103 | });
104 | },
105 | delete(params) {
106 | dispatch({
107 | type: 'notice/delete_application',
108 | payload: params,
109 | });
110 | },
111 | }
112 | }
113 |
114 | const styles = StyleSheet.create({
115 | container: {
116 | flex: 1,
117 | backgroundColor: '#F8F8F8',
118 | },
119 |
120 | rowList: {
121 | marginTop: 10,
122 | },
123 |
124 | row: {
125 | paddingLeft: 27,
126 | paddingRight: 27,
127 | alignItems: 'center',
128 | flexDirection: 'row',
129 | backgroundColor: '#FFFFFF',
130 | },
131 |
132 | rowImg: {
133 | width: 36,
134 | height: 36,
135 | borderRadius: 18,
136 | marginRight: 20,
137 | },
138 |
139 | rowInner: {
140 | flex: 1,
141 | paddingTop: 15,
142 | paddingBottom: 15,
143 | borderBottomWidth: 0.5,
144 | borderColor: '#F0F0F0',
145 | },
146 |
147 | rowText: {
148 | fontSize: 16,
149 | fontWeight: '400',
150 | },
151 |
152 | options: {
153 | paddingTop: 8,
154 | paddingBottom: 8,
155 | paddingLeft: 16,
156 | paddingRight: 16,
157 | backgroundColor: '#0079FD',
158 | borderRadius: 3,
159 | },
160 |
161 | optionsText: {
162 | fontSize: 12,
163 | color: '#FFFFFF',
164 | },
165 |
166 | rowContent: {
167 | fontSize: 14,
168 | color: '#999',
169 | lineHeight: 18,
170 | }
171 | });
172 |
173 | export default connect(mapStateToProps, mapDispatchToProps)(Roster);
174 |
--------------------------------------------------------------------------------
/src/pages/zone/model.js:
--------------------------------------------------------------------------------
1 | import * as service from './service';
2 | import { AsyncStorage } from 'react-native'
3 |
4 | export default {
5 | namespace: 'zone',
6 | state: {
7 | data: {},
8 | info: {},
9 | collects: [],
10 | other_data: {},
11 | setting: { draft: true, notic: true },
12 | loading: false,
13 | },
14 | effects: {
15 | *init({ payload = {} }, { select, call, put }) {
16 | const user = yield select(state => state.home.user);
17 | const accesstoken = yield select(state => state.home.accesstoken);
18 | if (Object.keys(user).length > 0) yield put({ type: 'query', payload: { user } })
19 | var setting = yield AsyncStorage.getItem('setting')
20 | if (setting) yield put({ type: 'config', payload: JSON.parse(setting) })
21 | },
22 | *login({ payload = {} }, { call, put }) {
23 | const { accesstoken } = payload
24 | yield put({ type: 'loading', payload: true });
25 | const { data, err } = yield call(service.postToken, payload);
26 | yield put({ type: 'loading', payload: false });
27 | if (err) return console.log(err)
28 | yield put({ type: 'login/success', payload: data });
29 | yield put({ type: 'home/token', payload: accesstoken });
30 | const [, user] = data
31 | yield put({ type: 'query', payload: { user, login: true } });
32 | },
33 | *query({ payload = {} }, { call, put }) {
34 | const { user, login } = payload
35 | yield put({ type: 'loading', payload: true });
36 | yield put({ type: 'home/user', payload: user });
37 | const { data, err } = yield call(service.queryUser, { user: user.loginname });
38 | yield put({ type: 'loading', payload: false });
39 | if (err) return console.log(err)
40 | if (login) yield put({ type: 'notice/init', payload: true }); //重新登录聊天
41 | yield put({ type: 'home/isLogin', payload: true })
42 | yield put({ type: 'query/success', payload: data });
43 | },
44 | *otherInfo({ payload = {} }, { call, put }) {
45 | yield put({ type: 'loading', payload: true });
46 | const { data, err } = yield call(service.queryUser, payload);
47 | yield put({ type: 'loading', payload: false });
48 | if (err) return console.log(err)
49 | yield put({ type: 'otherInfo/success', payload: data });
50 | },
51 | *information({ payload = {} }, { call, put }) {
52 | yield put({ type: 'loading', payload: true });
53 | const { data, err } = yield call(service.queryInfo, payload);
54 | yield put({ type: 'loading', payload: false });
55 | if (err) return console.log(err)
56 | yield put({ type: 'information/success', payload: data });
57 | },
58 | *collects({ payload = {} }, { call, put }) {
59 | yield put({ type: 'loading', payload: true });
60 | const { data, err } = yield call(service.queryCollects, payload);
61 | yield put({ type: 'loading', payload: false });
62 | if (err) return console.log(err)
63 | yield put({ type: 'collects/success', payload: data });
64 | },
65 | *logout({ payload = {} }, { call, put }) {
66 | AsyncStorage.removeItem('user')
67 | AsyncStorage.removeItem('accesstoken')
68 | AsyncStorage.removeItem('webim_user')
69 | AsyncStorage.removeItem('webim_accesstoken')
70 | yield put({ type: 'home/isLogin', payload: false })
71 | yield put({ type: 'home/clean', payload: true });
72 | yield put({ type: 'notice/clean', payload: true });
73 | yield put({ type: 'clean', payload: true });
74 | yield call(service.logout);
75 | },
76 | },
77 | reducers: {
78 | 'login/success'(state, { payload }) {
79 | const [, data] = payload
80 | return { ...state, user: data };
81 | },
82 | 'query/success'(state, { payload }) {
83 | const [, result] = payload
84 | const data = service.parseUser(result.data)
85 | return { ...state, data };
86 | },
87 | 'otherInfo/success'(state, { payload }) {
88 | const [, result] = payload
89 | const data = service.parseUser(result.data)
90 | return { ...state, other_data: data };
91 | },
92 | 'information/success'(state, { payload }) {
93 | const [, data] = payload
94 | const info = service.parseInfo(data)
95 | return { ...state, info };
96 | },
97 | 'collects/success'(state, { payload }) {
98 | const [, data] = payload
99 | const collects = service.parseCollects(data.data)
100 | return { ...state, collects };
101 | },
102 | 'de_collect'(state, { payload }) {
103 | const collects = state.collects.filter(collect => collect.id !== payload);
104 | return { ...state, collects };
105 | },
106 | 'loading'(state, { payload: data }) {
107 | return { ...state, loading: data };
108 | },
109 | 'config'(state, { payload: data = {} }) {
110 | AsyncStorage.setItem('setting', JSON.stringify(data));
111 | return { ...state, setting: data };
112 | },
113 | 'clean'(state, { payload: data }) {
114 | return { ...state, data: {} };
115 | },
116 | 'cleanInfo'(state) {
117 | return { ...state, info: {} };
118 | },
119 | },
120 | subscriptions: {},
121 | };
122 |
--------------------------------------------------------------------------------
/src/pages/zone/screen/Credits.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { connect } from 'dva/mobile';
3 | import { StyleSheet, View, Text, Button, Image, StatusBar, FlatList, Dimensions, TouchableOpacity } from 'react-native'
4 |
5 | class Credits extends PureComponent {
6 | constructor(props) {
7 | super(props)
8 | this.state = {}
9 | }
10 |
11 | static navigationOptions = ({ navigation }) => {
12 | const { state, setParams } = navigation;
13 | return {
14 | headerTitle: '论坛积分',
15 | };
16 | };
17 |
18 | render() {
19 | const { data } = this.props.navigation.state.params;
20 |
21 | return (
22 |
23 |
24 |
25 | 当前积分
26 |
27 | {data.score}
28 |
29 |
30 |
31 | 如何获取积分
32 |
33 |
34 |
35 |
36 |
37 |
38 | 登录论坛
39 |
40 | 每天一次
41 |
42 |
43 | +1
44 |
45 |
46 |
47 |
48 |
49 |
50 | 发布话题
51 |
52 | 每天最多5次
53 |
54 |
55 | +10
56 |
57 |
58 |
59 |
60 |
61 |
62 | 发表评论
63 |
64 | 每天最多10次
65 |
66 |
67 | +5
68 |
69 |
70 |
71 |
72 | );
73 | }
74 | }
75 |
76 | function mapStateToProps(state) {
77 | const { loading } = state.zone;
78 | return { loading };
79 | }
80 |
81 | function mapDispatchToProps(dispatch) {
82 | return {
83 | query(params) {
84 | dispatch({
85 | type: 'zone/query',
86 | payload: params,
87 | });
88 | },
89 | }
90 | }
91 |
92 | const styles = StyleSheet.create({
93 | container: {
94 | flex: 1,
95 | backgroundColor: '#F8F8F8',
96 | },
97 |
98 | scoreView: {
99 | padding: 30,
100 | backgroundColor: '#7A86A2',
101 | justifyContent: 'center',
102 | },
103 |
104 | scoreImg: {
105 | marginTop: 15,
106 | width: 30,
107 | height: 30,
108 | },
109 |
110 | scoreSub: {
111 | color: '#eee',
112 | fontSize: 14,
113 | },
114 |
115 | TextView: {
116 | marginTop: 15,
117 | },
118 |
119 | scoreText: {
120 | color: '#FFF',
121 | fontSize: 50,
122 | },
123 |
124 | subTitle: {
125 | paddingTop: 15,
126 | paddingLeft: 15,
127 | },
128 |
129 | subText: {
130 | fontSize: 14,
131 | color: '#999',
132 | },
133 |
134 | rowList: {
135 | marginTop: 15,
136 | },
137 |
138 | subView: {
139 | marginTop: 8,
140 | },
141 |
142 | sub: {
143 | color: '#999',
144 | fontSize: 12,
145 | },
146 |
147 | left: {
148 | flex: 1
149 | },
150 |
151 | row: {
152 | paddingLeft: 27,
153 | paddingRight: 27,
154 | alignItems: 'center',
155 | flexDirection: 'row',
156 | backgroundColor: '#FFFFFF',
157 | },
158 |
159 | rowImg: {
160 | width: 20,
161 | height: 20,
162 | marginRight: 20,
163 | },
164 |
165 | rowInner: {
166 | flex: 1,
167 | paddingTop: 20,
168 | paddingBottom: 20,
169 | flexDirection: 'row',
170 | alignItems: 'center',
171 | justifyContent: 'space-between',
172 | borderBottomWidth: 0.5,
173 | borderColor: '#F0F0F0',
174 | },
175 |
176 | rowText: {
177 | fontSize: 16,
178 | fontWeight: '400',
179 | },
180 |
181 | iconBtn: {
182 | width: 25,
183 | height: 25,
184 | },
185 |
186 | span: {
187 | color: '#4E8EDF',
188 | fontSize: 16,
189 | }
190 | });
191 |
192 | export default connect(mapStateToProps, mapDispatchToProps)(Credits);
193 |
--------------------------------------------------------------------------------