├── .watchmanconfig ├── src ├── services │ ├── index.js │ ├── api │ │ └── index.js │ └── leanclound │ │ ├── index.js │ │ ├── common.js │ │ ├── groupOrder.js │ │ ├── resumeOrder.js │ │ ├── warOrder.js │ │ ├── recruitOrder.js │ │ └── user.js ├── components │ ├── Container │ │ ├── index.js │ │ ├── styles.js │ │ └── Container.js │ ├── CustomStyles │ │ ├── index.js │ │ ├── Card.js │ │ └── ImagePicker.js │ ├── index.js │ ├── HomeTeamList │ │ └── index.js │ ├── HomeWarOrderList │ │ └── index.js │ ├── HomeUserInfoList │ │ └── index.js │ ├── HomeGroupOrderList │ │ └── index.js │ ├── HomeRecruitOrderList │ │ └── index.js │ ├── HomeResumeOrderList │ │ └── index.js │ ├── AccountWarOrderList │ │ └── index.js │ ├── AccountGroupOrderList │ │ └── index.js │ ├── AccountResumeOrderList │ │ └── index.js │ ├── HomeResumeCard │ │ └── index.js │ ├── HomeGroupCard │ │ └── index.js │ ├── HomeWarCard │ │ └── index.js │ ├── AccountRecruitOrderList │ │ └── index.js │ ├── HomeRecruitCard │ │ └── index.js │ ├── HomeTeamCard │ │ └── index.js │ ├── HomeUserInfoCard │ │ └── index.js │ ├── AccountResumeCard │ │ └── index.js │ ├── AccountGroupCard │ │ └── index.js │ ├── AccountWarCard │ │ └── index.js │ └── AccountRecruitCard │ │ └── index.js ├── store │ ├── index.js │ ├── configureStore.prod.js │ └── configureStore.dev.js ├── config │ └── index.js ├── screens │ ├── Welcome │ │ └── index.js │ ├── Home │ │ ├── Teams │ │ │ ├── index.js │ │ │ └── Detail │ │ │ │ └── index.js │ │ ├── UserInfos │ │ │ ├── index.js │ │ │ └── Detail │ │ │ │ └── index.js │ │ ├── WarOrders │ │ │ └── index.js │ │ ├── ResumeOrders │ │ │ └── index.js │ │ ├── GroupOrders │ │ │ └── index.js │ │ ├── RecruitOrders │ │ │ └── index.js │ │ └── index.js │ ├── Account │ │ ├── WarOrders │ │ │ └── index.js │ │ ├── GroupOrders │ │ │ └── index.js │ │ ├── ResumeOrders │ │ │ └── index.js │ │ ├── RecruitOrders │ │ │ └── index.js │ │ ├── EmaiVerify │ │ │ └── index.js │ │ └── Teams │ │ │ └── index.js │ ├── SignUp │ │ └── index.js │ └── SignIn │ │ └── index.js ├── reducers │ ├── nav.js │ ├── index.js │ ├── app.js │ ├── team.js │ └── user.js ├── utils │ ├── http.js │ └── utils.js ├── sagas │ ├── commonSaga.js │ ├── index.js │ ├── groupOrderSaga.js │ ├── resumeOrderSaga.js │ ├── warOrderSaga.js │ ├── recruitOrderSaga.js │ ├── userSaga.js │ └── teamsSaga.js ├── index.js └── constants │ └── index.js ├── .gitignore ├── assets ├── icon.png ├── splash.png ├── images │ ├── home.png │ ├── logo.png │ ├── home@2x.png │ ├── home@3x.png │ ├── home_icon.png │ ├── avatar_logo.png │ ├── home_icon@2x.png │ ├── home_icon@3x.png │ ├── ios_arrow_back.png │ ├── android_arrow_back.png │ ├── avatar_logo@2x.png.png │ ├── avatar_logo@3x.png.png │ ├── ios_arrow_back@2x.png │ ├── ios_arrow_back@3x.png │ ├── android_arrow_back@2x.png │ └── android_arrow_back@3x.png └── notification_icon.png ├── .babelrc ├── App.js ├── package.json ├── app.json └── README.md /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /src/services/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/services/api/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/splash.png -------------------------------------------------------------------------------- /assets/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/home.png -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/home@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/home@2x.png -------------------------------------------------------------------------------- /assets/images/home@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/home@3x.png -------------------------------------------------------------------------------- /assets/images/home_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/home_icon.png -------------------------------------------------------------------------------- /assets/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/notification_icon.png -------------------------------------------------------------------------------- /assets/images/avatar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/avatar_logo.png -------------------------------------------------------------------------------- /assets/images/home_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/home_icon@2x.png -------------------------------------------------------------------------------- /assets/images/home_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/home_icon@3x.png -------------------------------------------------------------------------------- /assets/images/ios_arrow_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/ios_arrow_back.png -------------------------------------------------------------------------------- /src/components/Container/index.js: -------------------------------------------------------------------------------- 1 | import Container from './Container' 2 | import styles from './styles' 3 | 4 | export { Container, styles } 5 | -------------------------------------------------------------------------------- /assets/images/android_arrow_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/android_arrow_back.png -------------------------------------------------------------------------------- /assets/images/avatar_logo@2x.png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/avatar_logo@2x.png.png -------------------------------------------------------------------------------- /assets/images/avatar_logo@3x.png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/avatar_logo@3x.png.png -------------------------------------------------------------------------------- /assets/images/ios_arrow_back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/ios_arrow_back@2x.png -------------------------------------------------------------------------------- /assets/images/ios_arrow_back@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/ios_arrow_back@3x.png -------------------------------------------------------------------------------- /assets/images/android_arrow_back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/android_arrow_back@2x.png -------------------------------------------------------------------------------- /assets/images/android_arrow_back@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxj963577494/OverWatchTeams-React-Native-Expo/HEAD/assets/images/android_arrow_back@3x.png -------------------------------------------------------------------------------- /src/components/CustomStyles/index.js: -------------------------------------------------------------------------------- 1 | import CardStyle from './Card' 2 | import ImagePickerStyle from './ImagePicker' 3 | 4 | export { CardStyle, ImagePickerStyle } 5 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configureStore.prod') 3 | } else { 4 | module.exports = require('./configureStore.dev') 5 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-expo"], 3 | "plugins": [["import", { "libraryName": "antd-mobile" }]], 4 | "env": { 5 | "development": { 6 | "plugins": ["transform-react-jsx-source"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | BASE_PIC_URL: 'http://p0bl3nkmx.bkt.clouddn.com', 3 | BASE_DEFAULT_PIC_URL: 'http://p0bl3nkmx.bkt.clouddn.com/logo.png', 4 | TEAM_DEFAULT_AVATAR: 'http://p0bl3nkmx.bkt.clouddn.com/avatar.png', 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Container/styles.js: -------------------------------------------------------------------------------- 1 | import EStyleSheet from 'react-native-extended-stylesheet' 2 | 3 | export default EStyleSheet.create({ 4 | container: { 5 | flex: 1, 6 | alignItems: 'center', 7 | justifyContent: 'center' 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /src/screens/Welcome/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | 5 | export default class SignUp extends Component { 6 | render() { 7 | return SignUp 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage' 2 | import moment from 'moment' 3 | import app from './src' 4 | 5 | require('moment/locale/zh-cn') 6 | moment.locale('zh-cn') 7 | 8 | const appId = 'Vvtn3QVyWcN9eVbuAT3wjMfG-9Nh9j0Va' 9 | const appKey = 'P59gxu0DMT7GkFeP1VlJoVmp' 10 | AV.init({ appId, appKey }) 11 | 12 | export default app 13 | -------------------------------------------------------------------------------- /src/components/Container/Container.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { View } from 'react-native' 4 | 5 | import styles from './styles' 6 | 7 | const Container = ({ children }) => ( 8 | {children} 9 | ) 10 | 11 | Container.propTypes = { 12 | children: PropTypes.any 13 | } 14 | 15 | export default Container 16 | -------------------------------------------------------------------------------- /src/components/CustomStyles/Card.js: -------------------------------------------------------------------------------- 1 | import CardStyle from 'antd-mobile/lib/card/style/index.native' 2 | 3 | export default { 4 | ...CardStyle, 5 | headerTitle: { 6 | flex: 3, 7 | flexDirection: 'row', 8 | alignItems: 'center' 9 | }, 10 | headerExtra: { 11 | flex: 1, 12 | }, 13 | footerContent: { 14 | flex: 3 15 | }, 16 | footerExtra: { 17 | flex: 1, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/reducers/nav.js: -------------------------------------------------------------------------------- 1 | import { NavigationActions } from 'react-navigation' 2 | import AppNavigator from '../routes' 3 | 4 | const initialNavState = AppNavigator.router.getStateForAction( 5 | NavigationActions.init() 6 | ) 7 | 8 | const navReducer = (state = initialNavState, action) => { 9 | const nextState = AppNavigator.router.getStateForAction(action, state) 10 | return nextState || state 11 | } 12 | 13 | export { navReducer } 14 | -------------------------------------------------------------------------------- /src/services/leanclound/index.js: -------------------------------------------------------------------------------- 1 | import * as commonService from './common' 2 | import * as userService from './user' 3 | import * as teamsService from './teams' 4 | import * as recruitOrderService from './recruitOrder' 5 | import * as groupOrderService from './groupOrder' 6 | import * as warOrderService from './warOrder' 7 | import * as resumeOrderService from './resumeOrder' 8 | 9 | export { 10 | commonService, 11 | userService, 12 | teamsService, 13 | recruitOrderService, 14 | groupOrderService, 15 | warOrderService, 16 | resumeOrderService 17 | } 18 | -------------------------------------------------------------------------------- /src/components/CustomStyles/ImagePicker.js: -------------------------------------------------------------------------------- 1 | import ImagePickerStyle from 'antd-mobile/lib/image-picker/style/index.native' 2 | 3 | export default { 4 | ...ImagePickerStyle, 5 | container: { 6 | flexWrap: 'nowrap', 7 | flexDirection: 'row', 8 | justifyContent: 'center' 9 | }, 10 | size: { 11 | width: 120, 12 | height: 120 13 | }, 14 | closeWrap: { 15 | width: 24, 16 | height: 24, 17 | backgroundColor: '#999', 18 | borderRadius: 12, 19 | position: 'absolute', 20 | top: 6, 21 | right: 6, 22 | justifyContent: 'center', 23 | alignItems: 'center', 24 | overflow: 'hidden' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { userReducer } from './user' 2 | import { appReducer } from './app' 3 | import { navReducer } from './nav' 4 | import { teamReducer } from './team' 5 | import { recruitOrderReducer } from './recruitOrder' 6 | import { groupOrderReducer } from './groupOrder' 7 | import { warOrderReducer } from './warOrder' 8 | import { resumeOrderReducer } from './resumeOrder' 9 | 10 | export default { 11 | app: appReducer, 12 | nav: navReducer, 13 | user: userReducer, 14 | team: teamReducer, 15 | recruitOrder: recruitOrderReducer, 16 | groupOrder: groupOrderReducer, 17 | warOrder: warOrderReducer, 18 | resumeOrder: resumeOrderReducer 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "private": true, 4 | "dependencies": { 5 | "antd-mobile": "^2.1.3", 6 | "expo": "^24.0.0", 7 | "leancloud-storage": "^3.4.2", 8 | "moment": "^2.20.1", 9 | "rc-form": "^2.1.6", 10 | "react": "16.0.0", 11 | "react-native": "https://github.com/expo/react-native/archive/sdk-24.0.0.tar.gz", 12 | "react-native-keyboard-aware-scroll-view": "^0.4.1", 13 | "react-native-timeago": "^0.4.0", 14 | "react-navigation": "^1.0.0-beta.22", 15 | "react-redux": "^5.0.6", 16 | "redux": "^3.7.2", 17 | "redux-logger": "^3.0.6", 18 | "redux-persist": "^5.4.0", 19 | "redux-saga": "^0.16.0" 20 | }, 21 | "devDependencies": { 22 | "babel-plugin-import": "^1.6.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import { persistStore, persistCombineReducers } from 'redux-persist' 3 | import storage from 'redux-persist/es/storage' 4 | import CreateSagaMiddleware, { END } from 'redux-saga' 5 | import reducers from '../reducers' 6 | 7 | const config = { 8 | key: 'root', 9 | storage 10 | } 11 | 12 | const reducer = persistCombineReducers(config, reducers) 13 | 14 | export default function configureStore(initialState) { 15 | const sagaMiddleware = CreateSagaMiddleware() 16 | const store = createStore( 17 | reducer, 18 | initialState, 19 | applyMiddleware(sagaMiddleware) 20 | ) 21 | store.runSaga = sagaMiddleware.run 22 | store.close = () => store.dispatch(END) 23 | const persistor = persistStore(store) 24 | return { persistor, store } 25 | } 26 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "OW Teams", 4 | "description": "OverWatch Teams", 5 | "slug": "OverWatchTeams", 6 | "privacy": "public", 7 | "sdkVersion": "24.0.0", 8 | "platforms": ["ios", "android"], 9 | "version": "1.0.2", 10 | "orientation": "portrait", 11 | "icon": "./assets/icon.png", 12 | "githubUrl": "https://github.com/zxj963577494/OverWatchTeams-React-Native", 13 | "notification": { 14 | "icon": "./assets/notification_icon.png" 15 | }, 16 | "splash": { 17 | "image": "./assets/splash.png", 18 | "resizeMode": "contain", 19 | "backgroundColor": "#ffffff" 20 | }, 21 | "ios": { 22 | "supportsTablet": true, 23 | "bundleIdentifier": "com.zhengxujiang.overwatchteams", 24 | "buildNumber": "1.0.2" 25 | }, 26 | "android": { 27 | "package": "com.zhengxujiang.overwatchteams", 28 | "versionCode": 3, 29 | "permissions": [] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/http.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | const headers = { 3 | Accept: 'application/json', 4 | }; 5 | 6 | function request(method, url, axiosConfig = {}) { 7 | const options = Object.assign( 8 | {}, 9 | { 10 | method, 11 | url, 12 | headers, 13 | responseType: 'json', 14 | }, 15 | headers, 16 | axiosConfig, 17 | ); 18 | return axios(options); 19 | } 20 | 21 | export function post(url, { data = {}, params = {}, transformResponse = [] }) { 22 | return request('post', url, { data, params, transformResponse }); 23 | } 24 | 25 | export function put(url, { data = {}, params = {}, transformResponse = [] }) { 26 | return request('put', url, { data, params, transformResponse }); 27 | } 28 | 29 | export function get(url, { params = {}, transformResponse = [] }) { 30 | return request('get', url, { transformResponse, params }); 31 | } 32 | 33 | export function destroy(url) { // delete 34 | return request('delete', url, {}); 35 | } -------------------------------------------------------------------------------- /src/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux' 2 | import { persistStore, persistCombineReducers } from 'redux-persist' 3 | import storage from 'redux-persist/es/storage' 4 | import CreateSagaMiddleware, { END } from 'redux-saga' 5 | import { createLogger } from 'redux-logger' 6 | import reducers from '../reducers' 7 | 8 | const config = { 9 | key: 'root', 10 | storage, 11 | debug: true 12 | } 13 | 14 | const reducer = persistCombineReducers(config, reducers) 15 | 16 | export default function configureStore(initialState) { 17 | const sagaMiddleware = CreateSagaMiddleware() 18 | const loggerMiddleware = createLogger() 19 | // 启动loggerMiddleware可能会导致XDE假死,高性能机器使用 20 | // const middlewares = [sagaMiddleware, loggerMiddleware] 21 | const middlewares = [sagaMiddleware] 22 | const enhancers = compose(applyMiddleware(...middlewares)) 23 | const store = createStore(reducer, initialState, enhancers) 24 | store.runSaga = sagaMiddleware.run 25 | store.close = () => store.dispatch(END) 26 | const persistor = persistStore(store) 27 | return { persistor, store } 28 | } 29 | -------------------------------------------------------------------------------- /src/services/leanclound/common.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage' 2 | 3 | // 发送验证码 4 | export function requestSmsCode(payload) { 5 | const { phone } = payload 6 | return AV.Cloud.requestSmsCode(phone).then(function(result) { 7 | return result.toJSON() 8 | }) 9 | } 10 | 11 | // 验证邮箱 12 | export function requestEmailVerify(payload) { 13 | const { email } = payload 14 | return AV.User.requestEmailVerify(email).then(function(result) { 15 | return result 16 | }) 17 | } 18 | 19 | // 邮箱重置密码 20 | export function requestPasswordReset(payload) { 21 | const { email } = payload 22 | return AV.User.requestPasswordReset(email).then(function(result) { 23 | return result 24 | }) 25 | } 26 | 27 | // 手机号码重置密码, 发送验证码 28 | export function requestPasswordResetBySmsCode(payload) { 29 | const { phone } = payload 30 | return AV.User.requestPasswordResetBySmsCode(phone) 31 | } 32 | 33 | // 手机号码重置密码 34 | export function resetPasswordBySmsCode(payload) { 35 | const { code, password } = payload 36 | return AV.User.resetPasswordBySmsCode(code, password) 37 | } 38 | 39 | // 上传图片 40 | export function uploadPic(payload) { 41 | const { image, name } = payload 42 | const file = new AV.File(name, { blob: image }) 43 | return file.save().then(function(result) { 44 | return result.toJSON() 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /src/screens/Home/Teams/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { NavigationActions } from 'react-navigation' 5 | import { connect } from 'react-redux' 6 | import { getHomeTeamListRequest } from '../../../actions' 7 | import { HomeTeamList } from '../../../components' 8 | 9 | class HomeTeams extends Component { 10 | componentDidMount() { 11 | if (this.props.team.list.length === 0) { 12 | this.props.getHomeTeamList({ page: 1 }) 13 | } 14 | } 15 | 16 | render() { 17 | const { team, navigateTo, getHomeTeamList } = this.props 18 | return ( 19 | 20 | 25 | 26 | ) 27 | } 28 | } 29 | 30 | const mapStateToProps = (state, ownProps) => { 31 | return { 32 | team: state.team.home.team 33 | } 34 | } 35 | 36 | const mapDispatchToProps = (dispatch, ownProps) => { 37 | return { 38 | getHomeTeamList: payload => { 39 | dispatch(getHomeTeamListRequest(payload)) 40 | }, 41 | navigateTo: (path, params) => { 42 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 43 | } 44 | } 45 | } 46 | 47 | HomeTeams.propTypes = { 48 | team: PropTypes.object, 49 | getHomeTeamList: PropTypes.func, 50 | navigateTo: PropTypes.func 51 | } 52 | 53 | export default connect(mapStateToProps, mapDispatchToProps)(HomeTeams) 54 | -------------------------------------------------------------------------------- /src/screens/Home/UserInfos/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { NavigationActions } from 'react-navigation' 5 | import { connect } from 'react-redux' 6 | import { getHomeUserInfoListRequest } from '../../../actions' 7 | import { HomeUserInfoList } from '../../../components' 8 | 9 | class HomeUserInfos extends Component { 10 | componentDidMount() { 11 | if (this.props.userinfo.list.length === 0) { 12 | this.props.getHomeUserInfoList({ page: 1 }) 13 | } 14 | } 15 | 16 | render() { 17 | const { userinfo, navigateTo, getHomeUserInfoList } = this.props 18 | return ( 19 | 20 | 25 | 26 | ) 27 | } 28 | } 29 | 30 | const mapStateToProps = (state, ownProps) => { 31 | return { 32 | userinfo: state.user.home.userinfo 33 | } 34 | } 35 | 36 | const mapDispatchToProps = (dispatch, ownProps) => { 37 | return { 38 | getHomeUserInfoList: payload => { 39 | dispatch(getHomeUserInfoListRequest(payload)) 40 | }, 41 | navigateTo: (path, params) => { 42 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 43 | } 44 | } 45 | } 46 | 47 | HomeUserInfos.propTypes = { 48 | userinfo: PropTypes.object, 49 | getHomeUserInfoList: PropTypes.func, 50 | navigateTo: PropTypes.func 51 | } 52 | 53 | export default connect(mapStateToProps, mapDispatchToProps)(HomeUserInfos) 54 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import HomeGroupCard from './HomeGroupCard' 2 | import HomeRecruitCard from './HomeRecruitCard' 3 | import HomeResumeCard from './HomeResumeCard' 4 | import HomeWarCard from './HomeWarCard' 5 | import HomeTeamCard from './HomeTeamCard' 6 | import AccountGroupCard from './AccountGroupCard' 7 | import AccountRecruitCard from './AccountRecruitCard' 8 | import AccountResumeCard from './AccountResumeCard' 9 | import AccountWarCard from './AccountWarCard' 10 | import HomeUserInfoCard from './HomeUserInfoCard' 11 | import HomeGroupOrderList from './HomeGroupOrderList' 12 | import HomeRecruitOrderList from './HomeRecruitOrderList' 13 | import HomeResumeOrderList from './HomeResumeOrderList' 14 | import HomeWarOrderList from './HomeWarOrderList' 15 | import HomeTeamList from './HomeTeamList' 16 | import HomeUserInfoList from './HomeUserInfoList' 17 | import AccountRecruitOrderList from './AccountRecruitOrderList' 18 | import AccountGroupOrderList from './AccountGroupOrderList' 19 | import AccountResumeOrderList from './AccountResumeOrderList' 20 | import AccountWarOrderList from './AccountWarOrderList' 21 | 22 | export { 23 | HomeGroupCard, 24 | HomeRecruitCard, 25 | HomeResumeCard, 26 | HomeWarCard, 27 | HomeTeamCard, 28 | HomeUserInfoCard, 29 | AccountRecruitCard, 30 | AccountGroupCard, 31 | AccountResumeCard, 32 | AccountWarCard, 33 | HomeGroupOrderList, 34 | HomeRecruitOrderList, 35 | HomeResumeOrderList, 36 | HomeWarOrderList, 37 | HomeTeamList, 38 | HomeUserInfoList, 39 | AccountRecruitOrderList, 40 | AccountGroupOrderList, 41 | AccountResumeOrderList, 42 | AccountWarOrderList 43 | } 44 | -------------------------------------------------------------------------------- /src/screens/Home/WarOrders/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { NavigationActions } from 'react-navigation' 5 | import { connect } from 'react-redux' 6 | import { getHomeWarOrderListRequest } from '../../../actions' 7 | import { HomeWarOrderList } from '../../../components' 8 | 9 | class HomeWarOrders extends Component { 10 | componentDidMount() { 11 | if (this.props.warOrder.list.length === 0) { 12 | this.props.getHomeWarOrderList({ page: 1 }) 13 | } 14 | } 15 | 16 | render() { 17 | const { warOrder, navigateTo, getHomeWarOrderList } = this.props 18 | return ( 19 | 20 | 25 | 26 | ) 27 | } 28 | } 29 | 30 | const mapStateToProps = (state, ownProps) => { 31 | return { 32 | warOrder: state.warOrder.home.warOrder 33 | } 34 | } 35 | 36 | const mapDispatchToProps = (dispatch, ownProps) => { 37 | return { 38 | getHomeWarOrderList: payload => { 39 | dispatch(getHomeWarOrderListRequest(payload)) 40 | }, 41 | navigateTo: (path, params) => { 42 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 43 | } 44 | } 45 | } 46 | 47 | HomeWarOrders.propTypes = { 48 | warOrder: PropTypes.object, 49 | getHomeWarOrderList: PropTypes.func, 50 | navigateTo: PropTypes.func 51 | } 52 | 53 | export default connect(mapStateToProps, mapDispatchToProps)(HomeWarOrders) 54 | -------------------------------------------------------------------------------- /src/screens/Home/ResumeOrders/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { NavigationActions } from 'react-navigation' 5 | import { connect } from 'react-redux' 6 | import { getHomeResumeOrderListRequest } from '../../../actions' 7 | import { HomeResumeOrderList } from '../../../components' 8 | 9 | class HomeResumeOrders extends Component { 10 | componentDidMount() { 11 | if (this.props.resumeOrder.list.length === 0) { 12 | this.props.getHomeResumeOrderList({ page: 1 }) 13 | } 14 | } 15 | 16 | render() { 17 | const { resumeOrder, navigateTo, getHomeResumeOrderList } = this.props 18 | return ( 19 | 24 | ) 25 | } 26 | } 27 | 28 | const mapStateToProps = (state, ownProps) => { 29 | return { 30 | resumeOrder: state.resumeOrder.home.resumeOrder 31 | } 32 | } 33 | 34 | const mapDispatchToProps = (dispatch, ownProps) => { 35 | return { 36 | getHomeResumeOrderList: payload => { 37 | dispatch(getHomeResumeOrderListRequest(payload)) 38 | }, 39 | navigateTo: (path, params) => { 40 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 41 | } 42 | } 43 | } 44 | 45 | HomeResumeOrders.propTypes = { 46 | resumeOrder: PropTypes.object, 47 | getHomeResumeOrderList: PropTypes.func, 48 | navigateTo: PropTypes.func 49 | } 50 | 51 | export default connect(mapStateToProps, mapDispatchToProps)(HomeResumeOrders) 52 | -------------------------------------------------------------------------------- /src/screens/Home/GroupOrders/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { NavigationActions } from 'react-navigation' 5 | import { connect } from 'react-redux' 6 | import { getHomeGroupOrderListRequest } from '../../../actions' 7 | import { HomeGroupOrderList } from '../../../components' 8 | 9 | class HomeGroupOrders extends Component { 10 | componentDidMount() { 11 | if (this.props.groupOrder.list.length === 0) { 12 | this.props.getHomeGroupOrderList({ page: 1 }) 13 | } 14 | } 15 | 16 | render() { 17 | const { groupOrder, navigateTo, getHomeGroupOrderList } = this.props 18 | return ( 19 | 20 | 25 | 26 | ) 27 | } 28 | } 29 | 30 | const mapStateToProps = (state, ownProps) => { 31 | return { 32 | groupOrder: state.groupOrder.home.groupOrder 33 | } 34 | } 35 | 36 | const mapDispatchToProps = (dispatch, ownProps) => { 37 | return { 38 | getHomeGroupOrderList: payload => { 39 | dispatch(getHomeGroupOrderListRequest(payload)) 40 | }, 41 | navigateTo: (path, params) => { 42 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 43 | } 44 | } 45 | } 46 | 47 | HomeGroupOrders.propTypes = { 48 | groupOrder: PropTypes.object, 49 | getHomeGroupOrderList: PropTypes.func, 50 | navigateTo: PropTypes.func 51 | } 52 | 53 | export default connect(mapStateToProps, mapDispatchToProps)(HomeGroupOrders) 54 | -------------------------------------------------------------------------------- /src/reducers/app.js: -------------------------------------------------------------------------------- 1 | import { 2 | FETCH_REQUEST, 3 | FETCH_SUCCESS, 4 | FETCH_FAILED, 5 | POST_UPLOAD_REQUEST, 6 | POST_UPLOAD_SUCCESS, 7 | POST_UPLOAD_FAILED, 8 | SEND_EMAIL_REQUEST, 9 | SEND_EMAIL_SUCCESS, 10 | SEND_EMAIL_FAILED, 11 | SEND_PASSWORD_RESET_REQUEST, 12 | SEND_PASSWORD_RESET_SUCCESS, 13 | SEND_PASSWORD_RESET_FAILED 14 | } from '../constants/actionTypes' 15 | 16 | const initialAppState = { 17 | isFetching: false, 18 | text: '', 19 | file: {}, 20 | emailError: '' 21 | } 22 | 23 | function appReducer(state = initialAppState, action) { 24 | switch (action.type) { 25 | case FETCH_REQUEST: 26 | return { 27 | ...state, 28 | isFetching: true, 29 | text: action.payload.text 30 | } 31 | case FETCH_SUCCESS: 32 | return { 33 | ...state, 34 | isFetching: false 35 | } 36 | case FETCH_FAILED: 37 | return { 38 | ...state, 39 | isFetching: false 40 | } 41 | case POST_UPLOAD_REQUEST: 42 | return state 43 | case POST_UPLOAD_SUCCESS: 44 | return { ...state, file: action.payload } 45 | case POST_UPLOAD_FAILED: 46 | return state 47 | case SEND_EMAIL_REQUEST: 48 | return { ...state, emailError: '正在发送...' } 49 | case SEND_EMAIL_SUCCESS: 50 | return { ...state, emailError: '发送成功' } 51 | case SEND_EMAIL_FAILED: 52 | return { ...state, emailError: '发送失败' } 53 | case SEND_PASSWORD_RESET_REQUEST: 54 | return state 55 | case SEND_PASSWORD_RESET_SUCCESS: 56 | return state 57 | case SEND_PASSWORD_RESET_FAILED: 58 | return state 59 | default: 60 | return state 61 | } 62 | } 63 | 64 | export { appReducer } 65 | -------------------------------------------------------------------------------- /src/screens/Home/RecruitOrders/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { NavigationActions } from 'react-navigation' 5 | import { connect } from 'react-redux' 6 | import { getHomeRecruitOrderListRequest } from '../../../actions' 7 | import { HomeRecruitOrderList } from '../../../components' 8 | 9 | class HomeRecruitOrders extends Component { 10 | componentDidMount() { 11 | if (this.props.recruitOrder.list.length === 0) { 12 | this.props.getHomeRecruitOrderList({ page: 1 }) 13 | } 14 | } 15 | 16 | render() { 17 | const { recruitOrder, navigateTo, getHomeRecruitOrderList } = this.props 18 | return ( 19 | 20 | 25 | 26 | ) 27 | } 28 | } 29 | 30 | const mapStateToProps = (state, ownProps) => { 31 | return { 32 | recruitOrder: state.recruitOrder.home.recruitOrder 33 | } 34 | } 35 | 36 | const mapDispatchToProps = (dispatch, ownProps) => { 37 | return { 38 | getHomeRecruitOrderList: payload => { 39 | dispatch(getHomeRecruitOrderListRequest(payload)) 40 | }, 41 | navigateTo: (path, params) => { 42 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 43 | } 44 | } 45 | } 46 | 47 | HomeRecruitOrders.propTypes = { 48 | recruitOrder: PropTypes.object, 49 | getHomeRecruitOrderList: PropTypes.func, 50 | navigateTo: PropTypes.func 51 | } 52 | 53 | export default connect(mapStateToProps, mapDispatchToProps)(HomeRecruitOrders) 54 | -------------------------------------------------------------------------------- /src/screens/Account/WarOrders/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { NavigationActions } from 'react-navigation' 5 | import { 6 | getAccountWarOrderListRequest, 7 | deleteWarOrderRequest 8 | } from '../../../actions' 9 | import { AccountWarOrderList } from '../../../components' 10 | 11 | class AccountWarOrders extends Component { 12 | componentDidMount() { 13 | if (this.props.warOrder.list.length === 0) { 14 | this.props.getAccountWarOrderList({ page: 1 }) 15 | } 16 | } 17 | 18 | render() { 19 | const { 20 | warOrder, 21 | navigateTo, 22 | getAccountWarOrderList, 23 | deleteWarOrder 24 | } = this.props 25 | return ( 26 | 32 | ) 33 | } 34 | } 35 | 36 | const mapStateToProps = (state, ownProps) => { 37 | return { 38 | warOrder: state.warOrder.account.warOrder 39 | } 40 | } 41 | 42 | const mapDispatchToProps = (dispatch, ownProps) => { 43 | return { 44 | getAccountWarOrderList: payload => { 45 | dispatch(getAccountWarOrderListRequest(payload)) 46 | }, 47 | deleteWarOrder: payload => { 48 | dispatch(deleteWarOrderRequest(payload)) 49 | }, 50 | navigateTo: (path, params) => { 51 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 52 | } 53 | } 54 | } 55 | 56 | AccountWarOrders.propTypes = { 57 | warOrder: PropTypes.object, 58 | getAccountWarOrderList: PropTypes.func, 59 | navigateTo: PropTypes.func, 60 | deleteWarOrder: PropTypes.func 61 | } 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)( 64 | AccountWarOrders 65 | ) 66 | -------------------------------------------------------------------------------- /src/screens/Account/GroupOrders/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { NavigationActions } from 'react-navigation' 5 | import { 6 | getAccountGroupOrderListRequest, 7 | deleteGroupOrderRequest 8 | } from '../../../actions' 9 | import { AccountGroupOrderList } from '../../../components' 10 | 11 | class AccountGroupOrders extends Component { 12 | componentDidMount() { 13 | if (this.props.groupOrder.list.length === 0) { 14 | this.props.getAccountGroupOrderList({ page: 1 }) 15 | } 16 | } 17 | 18 | render() { 19 | const { 20 | groupOrder, 21 | navigateTo, 22 | getAccountGroupOrderList, 23 | deleteGroupOrder 24 | } = this.props 25 | return ( 26 | 32 | ) 33 | } 34 | } 35 | 36 | const mapStateToProps = (state, ownProps) => { 37 | return { 38 | groupOrder: state.groupOrder.account.groupOrder 39 | } 40 | } 41 | 42 | const mapDispatchToProps = (dispatch, ownProps) => { 43 | return { 44 | getAccountGroupOrderList: payload => { 45 | dispatch(getAccountGroupOrderListRequest(payload)) 46 | }, 47 | deleteGroupOrder: payload => { 48 | dispatch(deleteGroupOrderRequest(payload)) 49 | }, 50 | navigateTo: (path, params) => { 51 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 52 | } 53 | } 54 | } 55 | 56 | AccountGroupOrders.propTypes = { 57 | groupOrder: PropTypes.object, 58 | getAccountGroupOrderList: PropTypes.func, 59 | navigateTo: PropTypes.func, 60 | deleteGroupOrder: PropTypes.func 61 | } 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)( 64 | AccountGroupOrders 65 | ) 66 | -------------------------------------------------------------------------------- /src/screens/Account/ResumeOrders/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { NavigationActions } from 'react-navigation' 5 | import { 6 | getAccountResumeOrderListRequest, 7 | deleteResumeOrderRequest 8 | } from '../../../actions' 9 | import { AccountResumeOrderList } from '../../../components' 10 | 11 | class AccountResumeOrders extends Component { 12 | componentDidMount() { 13 | if (this.props.resumeOrder.list.length === 0) { 14 | this.props.getAccountResumeOrderList({ page: 1 }) 15 | } 16 | } 17 | 18 | render() { 19 | const { 20 | resumeOrder, 21 | navigateTo, 22 | getAccountResumeOrderList, 23 | deleteResumeOrder 24 | } = this.props 25 | return ( 26 | 32 | ) 33 | } 34 | } 35 | 36 | const mapStateToProps = (state, ownProps) => { 37 | return { 38 | resumeOrder: state.resumeOrder.account.resumeOrder 39 | } 40 | } 41 | 42 | const mapDispatchToProps = (dispatch, ownProps) => { 43 | return { 44 | getAccountResumeOrderList: payload => { 45 | dispatch(getAccountResumeOrderListRequest(payload)) 46 | }, 47 | deleteResumeOrder: payload => { 48 | dispatch(deleteResumeOrderRequest(payload)) 49 | }, 50 | navigateTo: (path, params) => { 51 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 52 | } 53 | } 54 | } 55 | 56 | AccountResumeOrders.propTypes = { 57 | resumeOrder: PropTypes.object, 58 | getAccountResumeOrderList: PropTypes.func, 59 | navigateTo: PropTypes.func, 60 | deleteResumeOrder: PropTypes.func 61 | } 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)( 64 | AccountResumeOrders 65 | ) 66 | -------------------------------------------------------------------------------- /src/screens/Account/RecruitOrders/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { NavigationActions } from 'react-navigation' 5 | import { 6 | getAccountRecruitOrderListRequest, 7 | deleteRecruitOrderRequest 8 | } from '../../../actions' 9 | import { AccountRecruitOrderList } from '../../../components' 10 | 11 | class AccountRecruitOrders extends Component { 12 | componentDidMount() { 13 | if (this.props.recruitOrder.list.length === 0) { 14 | this.props.getAccountRecruitOrderList({ page: 1 }) 15 | } 16 | } 17 | 18 | render() { 19 | const { 20 | recruitOrder, 21 | navigateTo, 22 | getAccountRecruitOrderList, 23 | deleteRecruitOrder 24 | } = this.props 25 | return ( 26 | 32 | ) 33 | } 34 | } 35 | 36 | const mapStateToProps = (state, ownProps) => { 37 | return { 38 | recruitOrder: state.recruitOrder.account.recruitOrder 39 | } 40 | } 41 | 42 | const mapDispatchToProps = (dispatch, ownProps) => { 43 | return { 44 | getAccountRecruitOrderList: payload => { 45 | dispatch(getAccountRecruitOrderListRequest(payload)) 46 | }, 47 | deleteRecruitOrder: payload => { 48 | dispatch(deleteRecruitOrderRequest(payload)) 49 | }, 50 | navigateTo: (path, params) => { 51 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 52 | } 53 | } 54 | } 55 | 56 | AccountRecruitOrders.propTypes = { 57 | recruitOrder: PropTypes.object, 58 | getAccountRecruitOrderList: PropTypes.func, 59 | navigateTo: PropTypes.func, 60 | deleteRecruitOrder: PropTypes.func 61 | } 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)( 64 | AccountRecruitOrders 65 | ) 66 | -------------------------------------------------------------------------------- /src/sagas/commonSaga.js: -------------------------------------------------------------------------------- 1 | import { put, fork, take, call } from 'redux-saga/effects' 2 | import { delay } from 'redux-saga' 3 | import { Toast } from 'antd-mobile' 4 | import { NavigationActions } from 'react-navigation' 5 | import { 6 | POST_UPLOAD_REQUEST, 7 | SEND_EMAIL_REQUEST, 8 | SEND_PASSWORD_RESET_REQUEST 9 | } from '../constants/actionTypes' 10 | import * as action from '../actions' 11 | import { commonService, userService } from '../services/leanclound' 12 | 13 | function* postUploadWorker(payload) { 14 | try { 15 | Toast.loading('上传中') 16 | const response = yield call(commonService.uploadPic, payload) 17 | yield put(action.postUploadSuccess(response)) 18 | Toast.success('上传成功', 1) 19 | } catch (error) { 20 | yield put(action.postUploadFailed(error)) 21 | Toast.fail('上传失败', 1) 22 | } 23 | } 24 | 25 | function* sendEmailWorker(payload) { 26 | try { 27 | Toast.loading('邮件发送中') 28 | const response = yield call(commonService.requestEmailVerify, payload) 29 | yield put(action.sendEmailSuccess(response)) 30 | Toast.success('发送成功', 1) 31 | } catch (error) { 32 | yield put(action.sendEmailFailed(error)) 33 | Toast.fail('发送失败', 1) 34 | } 35 | } 36 | 37 | function* sendPasswordResetWorker(payload) { 38 | try { 39 | Toast.loading('上传中') 40 | const response = yield call(commonService.requestPasswordReset, payload) 41 | yield put(action.sendPasswordResetSuccess(response)) 42 | yield call(userService.logOut) 43 | yield put(NavigationActions.navigate('Home')) 44 | Toast.success('重置密码的邮件已发送', 1.5) 45 | } catch (error) { 46 | yield put(action.sendPasswordResetFailed(error)) 47 | Toast.fail('重置密码的邮件发送失败', 1.5) 48 | } 49 | } 50 | 51 | function* watchUpload() { 52 | while (true) { 53 | const { payload } = yield take(POST_UPLOAD_REQUEST) 54 | yield fork(postUploadWorker, payload) 55 | } 56 | } 57 | 58 | function* watchSendEmail() { 59 | while (true) { 60 | const { payload } = yield take(SEND_EMAIL_REQUEST) 61 | yield fork(sendEmailWorker, payload) 62 | } 63 | } 64 | 65 | function* watchSendPasswordReset() { 66 | while (true) { 67 | const { payload } = yield take(SEND_PASSWORD_RESET_REQUEST) 68 | yield fork(sendPasswordResetWorker, payload) 69 | } 70 | } 71 | 72 | export { watchUpload, watchSendEmail, watchSendPasswordReset } 73 | -------------------------------------------------------------------------------- /src/components/HomeTeamList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { FlatList, View, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { WhiteSpace } from 'antd-mobile' 5 | import HomeTeamCard from '../HomeTeamCard' 6 | 7 | export default class HomeTeamList extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this._onEndReached = this._onEndReached.bind(this) 11 | this._onRefresh = this._onRefresh.bind(this) 12 | this._renderFonter = this._renderFonter.bind(this) 13 | this._renderSeparator = this._renderSeparator.bind(this) 14 | this._renderItem = this._renderItem.bind(this) 15 | } 16 | 17 | _onEndReached() { 18 | if (this.props.team.isFetching || !this.props.team.isLoadMore) { 19 | return 20 | } 21 | const page = this.props.team.page + 1 22 | this.props.getHomeTeamList({ page: page }) 23 | } 24 | 25 | _onRefresh() { 26 | this.props.getHomeTeamList({ isRefreshing: true }) 27 | } 28 | 29 | _renderFonter() { 30 | return ( 31 | 32 | 33 | {this.props.team.isFetching ? '' : '到底了'} 34 | 35 | 36 | ) 37 | } 38 | 39 | _renderItem({ item }) { 40 | return 41 | } 42 | 43 | _renderSeparator() { 44 | return 45 | } 46 | 47 | _getItemLayout(data, index) { 48 | let [length, separator, header] = [200, 3, 0] 49 | return { length, offset: (length + separator) * index + header, index } 50 | } 51 | 52 | _keyExtractor = (item, index) => item.objectId 53 | 54 | render() { 55 | const { list, isRefreshing, fetchingText, isFetching } = this.props.team 56 | return ( 57 | 71 | ) 72 | } 73 | } 74 | 75 | HomeTeamList.propTypes = { 76 | team: PropTypes.object, 77 | navigateTo: PropTypes.func, 78 | getHomeTeamList: PropTypes.func 79 | } 80 | -------------------------------------------------------------------------------- /src/components/HomeWarOrderList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { FlatList, View, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { WhiteSpace } from 'antd-mobile' 5 | import HomeWarCard from '../HomeWarCard' 6 | 7 | export default class HomeWarOrderList extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this._onEndReached = this._onEndReached.bind(this) 11 | this._onRefresh = this._onRefresh.bind(this) 12 | this._renderFonter = this._renderFonter.bind(this) 13 | this._renderSeparator = this._renderSeparator.bind(this) 14 | this._renderItem = this._renderItem.bind(this) 15 | } 16 | 17 | _onEndReached() { 18 | if (this.props.warOrder.isFetching || !this.props.warOrder.isLoadMore) { 19 | return 20 | } 21 | const page = this.props.warOrder.page + 1 22 | this.props.getHomeWarOrderList({ page: page }) 23 | } 24 | 25 | _onRefresh() { 26 | this.props.getHomeWarOrderList({ isRefreshing: true }) 27 | } 28 | 29 | _renderFonter() { 30 | return ( 31 | 32 | 33 | {this.props.warOrder.isFetching ? '' : '到底了'} 34 | 35 | 36 | ) 37 | } 38 | 39 | _renderItem({ item }) { 40 | return 41 | } 42 | 43 | _renderSeparator() { 44 | return 45 | } 46 | 47 | _getItemLayout(data, index) { 48 | let [length, separator, header] = [200, 3, 0] 49 | return { length, offset: (length + separator) * index + header, index } 50 | } 51 | 52 | _keyExtractor = (item, index) => item.objectId 53 | 54 | render() { 55 | const { list, isRefreshing, fetchingText, isFetching } = this.props.warOrder 56 | return ( 57 | 71 | ) 72 | } 73 | } 74 | 75 | HomeWarOrderList.propTypes = { 76 | warOrder: PropTypes.object, 77 | navigateTo: PropTypes.func, 78 | getHomeWarOrderList: PropTypes.func 79 | } 80 | -------------------------------------------------------------------------------- /src/components/HomeUserInfoList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { FlatList, View, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { WhiteSpace } from 'antd-mobile' 5 | import HomeUserInfoCard from '../HomeUserInfoCard' 6 | 7 | export default class HomeUserInfoList extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this._onEndReached = this._onEndReached.bind(this) 11 | this._onRefresh = this._onRefresh.bind(this) 12 | this._renderFonter = this._renderFonter.bind(this) 13 | this._renderSeparator = this._renderSeparator.bind(this) 14 | this._renderItem = this._renderItem.bind(this) 15 | } 16 | 17 | _onEndReached() { 18 | if (this.props.userinfo.isFetching || !this.props.userinfo.isLoadMore) { 19 | return 20 | } 21 | const page = this.props.userinfo.page + 1 22 | this.props.getHomeUserInfoList({ page: page }) 23 | } 24 | 25 | _onRefresh() { 26 | this.props.getHomeUserInfoList({ isRefreshing: true }) 27 | } 28 | 29 | _renderFonter() { 30 | return ( 31 | 32 | 33 | {this.props.userinfo.isFetching ? '' : '到底了'} 34 | 35 | 36 | ) 37 | } 38 | 39 | _renderItem({ item }) { 40 | return 41 | } 42 | 43 | _renderSeparator() { 44 | return 45 | } 46 | 47 | _getItemLayout(data, index) { 48 | let [length, separator, header] = [200, 3, 0] 49 | return { length, offset: (length + separator) * index + header, index } 50 | } 51 | 52 | _keyExtractor = (item, index) => item.objectId 53 | 54 | render() { 55 | const { list, isRefreshing, fetchingText, isFetching } = this.props.userinfo 56 | return ( 57 | 71 | ) 72 | } 73 | } 74 | 75 | HomeUserInfoList.propTypes = { 76 | userinfo: PropTypes.object, 77 | navigateTo: PropTypes.func, 78 | getHomeUserInfoList: PropTypes.func 79 | } 80 | -------------------------------------------------------------------------------- /src/components/HomeGroupOrderList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { FlatList, View, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { WhiteSpace } from 'antd-mobile' 5 | import HomeGroupCard from '../HomeGroupCard' 6 | 7 | export default class HomeGroupOrderList extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this._onEndReached = this._onEndReached.bind(this) 11 | this._onRefresh = this._onRefresh.bind(this) 12 | this._renderFonter = this._renderFonter.bind(this) 13 | this._renderSeparator = this._renderSeparator.bind(this) 14 | this._renderItem = this._renderItem.bind(this) 15 | } 16 | 17 | _onEndReached() { 18 | if (this.props.groupOrder.isFetching || !this.props.groupOrder.isLoadMore) { 19 | return 20 | } 21 | const page = this.props.groupOrder.page + 1 22 | this.props.getHomeGroupOrderList({ page: page }) 23 | } 24 | 25 | _onRefresh() { 26 | this.props.getHomeGroupOrderList({ isRefreshing: true }) 27 | } 28 | 29 | _renderFonter() { 30 | return ( 31 | 32 | 33 | {this.props.groupOrder.isFetching ? '' : '到底了'} 34 | 35 | 36 | ) 37 | } 38 | 39 | _renderItem({ item }) { 40 | return 41 | } 42 | 43 | _renderSeparator() { 44 | return 45 | } 46 | 47 | _getItemLayout(data, index) { 48 | let [length, separator, header] = [197, 3, 0] 49 | return { length, offset: (length + separator) * index + header, index } 50 | } 51 | 52 | _keyExtractor = (item, index) => item.objectId 53 | 54 | render() { 55 | const { 56 | list, 57 | isRefreshing, 58 | fetchingText, 59 | isFetching 60 | } = this.props.groupOrder 61 | return ( 62 | 77 | ) 78 | } 79 | } 80 | 81 | HomeGroupOrderList.propTypes = { 82 | groupOrder: PropTypes.object, 83 | navigateTo: PropTypes.func, 84 | getHomeGroupOrderList: PropTypes.func 85 | } 86 | -------------------------------------------------------------------------------- /src/components/HomeRecruitOrderList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { FlatList, View, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { WhiteSpace } from 'antd-mobile' 5 | import HomeRecruitCard from '../HomeRecruitCard' 6 | 7 | export default class HomeRecruitOrderList extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this._onEndReached = this._onEndReached.bind(this) 11 | this._onRefresh = this._onRefresh.bind(this) 12 | this._renderFonter = this._renderFonter.bind(this) 13 | this._renderSeparator = this._renderSeparator.bind(this) 14 | this._renderItem = this._renderItem.bind(this) 15 | } 16 | 17 | _onEndReached() { 18 | if ( 19 | this.props.recruitOrder.isFetching || 20 | !this.props.recruitOrder.isLoadMore 21 | ) { 22 | return 23 | } 24 | const page = this.props.recruitOrder.page + 1 25 | this.props.getHomeRecruitOrderList({ page: page }) 26 | } 27 | 28 | _onRefresh() { 29 | this.props.getHomeRecruitOrderList({ isRefreshing: true }) 30 | } 31 | 32 | _renderFonter() { 33 | return ( 34 | 35 | 36 | {this.props.recruitOrder.isFetching ? '' : '到底了'} 37 | 38 | 39 | ) 40 | } 41 | 42 | _renderItem({ item }) { 43 | return 44 | } 45 | 46 | _renderSeparator() { 47 | return 48 | } 49 | 50 | _getItemLayout(data, index) { 51 | let [length, separator, header] = [197, 3, 0] 52 | return { length, offset: (length + separator) * index + header, index } 53 | } 54 | 55 | _keyExtractor = (item, index) => item.objectId 56 | 57 | render() { 58 | const { 59 | list, 60 | isRefreshing, 61 | fetchingText, 62 | isFetching 63 | } = this.props.recruitOrder 64 | return ( 65 | 79 | ) 80 | } 81 | } 82 | 83 | HomeRecruitOrderList.propTypes = { 84 | recruitOrder: PropTypes.object, 85 | navigateTo: PropTypes.func, 86 | getHomeRecruitOrderList: PropTypes.func 87 | } 88 | -------------------------------------------------------------------------------- /src/components/HomeResumeOrderList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { FlatList, View, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { WhiteSpace } from 'antd-mobile' 5 | import HomeResumeCard from '../HomeResumeCard' 6 | 7 | export default class HomeResumeOrderList extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this._onEndReached = this._onEndReached.bind(this) 11 | this._onRefresh = this._onRefresh.bind(this) 12 | this._renderFonter = this._renderFonter.bind(this) 13 | this._renderSeparator = this._renderSeparator.bind(this) 14 | this._renderItem = this._renderItem.bind(this) 15 | } 16 | 17 | _onEndReached() { 18 | if ( 19 | this.props.resumeOrder.isFetching || 20 | !this.props.resumeOrder.isLoadMore 21 | ) { 22 | return 23 | } 24 | const page = this.props.resumeOrder.page + 1 25 | this.props.getHomeResumeOrderList({ page: page }) 26 | } 27 | 28 | _onRefresh() { 29 | this.props.getHomeResumeOrderList({ isRefreshing: true }) 30 | } 31 | 32 | _renderFonter() { 33 | return ( 34 | 35 | 36 | {this.props.resumeOrder.isFetching ? '' : '到底了'} 37 | 38 | 39 | ) 40 | } 41 | 42 | _renderItem({ item }) { 43 | return 44 | } 45 | 46 | _renderSeparator() { 47 | return 48 | } 49 | 50 | _getItemLayout(data, index) { 51 | let [length, separator, header] = [197, 3, 0] 52 | return { length, offset: (length + separator) * index + header, index } 53 | } 54 | 55 | _keyExtractor = (item, index) => item.objectId 56 | 57 | render() { 58 | const { 59 | list, 60 | isRefreshing, 61 | fetchingText, 62 | isFetching 63 | } = this.props.resumeOrder 64 | return ( 65 | 80 | ) 81 | } 82 | } 83 | 84 | HomeResumeOrderList.propTypes = { 85 | resumeOrder: PropTypes.object, 86 | navigateTo: PropTypes.func, 87 | getHomeResumeOrderList: PropTypes.func 88 | } 89 | -------------------------------------------------------------------------------- /src/components/AccountWarOrderList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { FlatList, View, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { WhiteSpace } from 'antd-mobile' 5 | import AccountWarCard from '../AccountWarCard' 6 | 7 | export default class AccountWarOrderList extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this._onEndReached = this._onEndReached.bind(this) 11 | this._onRefresh = this._onRefresh.bind(this) 12 | this._renderFonter = this._renderFonter.bind(this) 13 | this._renderSeparator = this._renderSeparator.bind(this) 14 | this._renderItem = this._renderItem.bind(this) 15 | } 16 | 17 | _onEndReached() { 18 | if (this.props.warOrder.isFetching || !this.props.warOrder.isLoadMore) { 19 | return 20 | } 21 | const page = this.props.warOrder.page + 1 22 | this.props.getAccountWarOrderList({ page: page }) 23 | } 24 | 25 | _onRefresh() { 26 | this.props.getAccountWarOrderList({ isRefreshing: true }) 27 | } 28 | 29 | _renderFonter() { 30 | return ( 31 | 32 | 33 | {this.props.warOrder.isFetching ? '' : '到底了'} 34 | 35 | 36 | ) 37 | } 38 | 39 | _renderItem({ item }) { 40 | return ( 41 | 46 | ) 47 | } 48 | 49 | _renderSeparator() { 50 | return 51 | } 52 | 53 | _getItemLayout(data, index) { 54 | let [length, separator, header] = [197, 3, 0] 55 | return { length, offset: (length + separator) * index + header, index } 56 | } 57 | 58 | _keyExtractor = (item, index) => item.objectId 59 | 60 | render() { 61 | const { list, isRefreshing, fetchingText, isFetching } = this.props.warOrder 62 | return ( 63 | 78 | ) 79 | } 80 | } 81 | 82 | AccountWarOrderList.propTypes = { 83 | warOrder: PropTypes.object, 84 | navigateTo: PropTypes.func, 85 | getAccountWarOrderList: PropTypes.func, 86 | deleteWarOrder: PropTypes.func 87 | } 88 | -------------------------------------------------------------------------------- /src/components/AccountGroupOrderList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { FlatList, View, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { WhiteSpace } from 'antd-mobile' 5 | import AccountGroupCard from '../AccountGroupCard' 6 | 7 | export default class AccountGroupOrderList extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this._onEndReached = this._onEndReached.bind(this) 11 | this._onRefresh = this._onRefresh.bind(this) 12 | this._renderFonter = this._renderFonter.bind(this) 13 | this._renderSeparator = this._renderSeparator.bind(this) 14 | this._renderItem = this._renderItem.bind(this) 15 | } 16 | 17 | _onEndReached() { 18 | if (this.props.groupOrder.isFetching || !this.props.groupOrder.isLoadMore) { 19 | return 20 | } 21 | const page = this.props.groupOrder.page + 1 22 | this.props.getAccountGroupOrderList({ page: page }) 23 | } 24 | 25 | _onRefresh() { 26 | this.props.getAccountGroupOrderList({ isRefreshing: true }) 27 | } 28 | 29 | _renderFonter() { 30 | return ( 31 | 32 | 33 | {this.props.groupOrder.isFetching ? '' : '到底了'} 34 | 35 | 36 | ) 37 | } 38 | 39 | _renderItem({ item }) { 40 | return ( 41 | 46 | ) 47 | } 48 | 49 | _renderSeparator() { 50 | return 51 | } 52 | 53 | _getItemLayout(data, index) { 54 | let [length, separator, header] = [197, 3, 0] 55 | return { length, offset: (length + separator) * index + header, index } 56 | } 57 | 58 | _keyExtractor = (item, index) => item.objectId 59 | 60 | render() { 61 | const { 62 | list, 63 | isRefreshing, 64 | fetchingText, 65 | isFetching 66 | } = this.props.groupOrder 67 | return ( 68 | 82 | ) 83 | } 84 | } 85 | 86 | AccountGroupOrderList.propTypes = { 87 | groupOrder: PropTypes.object, 88 | navigateTo: PropTypes.func, 89 | getAccountGroupOrderList: PropTypes.func, 90 | deleteGroupOrder: PropTypes.func 91 | } 92 | -------------------------------------------------------------------------------- /src/components/AccountResumeOrderList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { FlatList, View, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { WhiteSpace } from 'antd-mobile' 5 | import AccountResumeCard from '../AccountResumeCard' 6 | 7 | export default class AccountResumeOrderList extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this._onEndReached = this._onEndReached.bind(this) 11 | this._onRefresh = this._onRefresh.bind(this) 12 | this._renderFonter = this._renderFonter.bind(this) 13 | this._renderSeparator = this._renderSeparator.bind(this) 14 | this._renderItem = this._renderItem.bind(this) 15 | } 16 | 17 | _onEndReached() { 18 | if ( 19 | this.props.resumeOrder.isFetching || 20 | !this.props.resumeOrder.isLoadMore 21 | ) { 22 | return 23 | } 24 | const page = this.props.resumeOrder.page + 1 25 | this.props.getAccountResumeOrderList({ page: page }) 26 | } 27 | 28 | _onRefresh() { 29 | this.props.getAccountResumeOrderList({ isRefreshing: true }) 30 | } 31 | 32 | _renderFonter() { 33 | return ( 34 | 35 | 36 | {this.props.resumeOrder.isFetching ? '' : '到底了'} 37 | 38 | 39 | ) 40 | } 41 | 42 | _renderItem({ item }) { 43 | return ( 44 | 49 | ) 50 | } 51 | 52 | _renderSeparator() { 53 | return 54 | } 55 | 56 | _getItemLayout(data, index) { 57 | let [length, separator, header] = [197, 3, 0] 58 | return { length, offset: (length + separator) * index + header, index } 59 | } 60 | 61 | _keyExtractor = (item, index) => item.objectId 62 | 63 | render() { 64 | const { 65 | list, 66 | isRefreshing, 67 | fetchingText, 68 | isFetching 69 | } = this.props.resumeOrder 70 | return ( 71 | 85 | ) 86 | } 87 | } 88 | 89 | AccountResumeOrderList.propTypes = { 90 | resumeOrder: PropTypes.object, 91 | navigateTo: PropTypes.func, 92 | getAccountResumeOrderList: PropTypes.func, 93 | deleteResumeOrder: PropTypes.func 94 | } 95 | -------------------------------------------------------------------------------- /src/components/HomeResumeCard/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Text, View, TouchableWithoutFeedback } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { Card, Flex, WhiteSpace } from 'antd-mobile' 5 | import TimeAgo from 'react-native-timeago' 6 | 7 | import { cutstr } from '../../utils/utils' 8 | 9 | export default class HomeResumeCard extends PureComponent { 10 | 11 | render() { 12 | const { item, navigateTo } = this.props 13 | return ( 14 | 16 | navigateTo('HomeUserInfoDetail', { 17 | objectId: item.user.userinfo.objectId 18 | }) 19 | } 20 | > 21 | 22 | 23 | 33 | 34 | 35 | 36 | 37 | {item.user.userinfo.nickname} 38 | 39 | 40 | 41 | 42 | {item.contact} 43 | 44 | 45 | 46 | 47 | 48 | 49 | 55 | {cutstr(item.description, 60, 0)} 56 | 57 | 58 | 59 | 60 | 67 | 发布时间: 68 | 69 | } 70 | extra={ 71 | 72 | 有效日期: 73 | 74 | } 75 | style={{ paddingBottom: 5 }} 76 | /> 77 | 78 | 79 | 80 | ) 81 | } 82 | } 83 | 84 | HomeResumeCard.propTypes = { 85 | navigateTo: PropTypes.func, 86 | item: PropTypes.object 87 | } 88 | -------------------------------------------------------------------------------- /src/sagas/index.js: -------------------------------------------------------------------------------- 1 | import { fork, all } from 'redux-saga/effects' 2 | import { 3 | watchSignUp, 4 | watchLogin, 5 | watchLogout, 6 | watchPutUserInfo, 7 | watchGetUserInfo, 8 | watchGetHomeUserList, 9 | watchGetHomeUserDetail, 10 | watchGetCurrentUser 11 | } from './userSaga' 12 | import { 13 | watchPostTeams, 14 | watchPutTeams, 15 | watchDeleteTeamMember, 16 | watchDeleteTeam, 17 | watchGetHomeTeamList, 18 | watchGetHomeTeamDetail, 19 | watchGetMyTeams, 20 | watchGetInTeams 21 | } from './teamsSaga' 22 | import { 23 | watchUpload, 24 | watchSendEmail, 25 | watchSendPasswordReset 26 | } from './commonSaga' 27 | import { 28 | watchGetAccountRecruitOrderList, 29 | watchGetHomeRecruitOrderList, 30 | watchPostRecruitOrder, 31 | watchPutRecruitOrder, 32 | watchDeleteRecruitOrder 33 | } from './recruitOrderSaga' 34 | import { 35 | watchGetAccountGroupOrderList, 36 | watchGetHomeGroupOrderList, 37 | watchPostGroupOrder, 38 | watchPutGroupOrder, 39 | watchDeleteGroupOrder 40 | } from './groupOrderSaga' 41 | import { 42 | watchGetAccountWarOrderList, 43 | watchGetHomeWarOrderList, 44 | watchPostWarOrder, 45 | watchPutWarOrder, 46 | watchDeleteWarOrder 47 | } from './warOrderSaga' 48 | import { 49 | watchGetAccountResumeOrderList, 50 | watchGetHomeResumeOrderList, 51 | watchPostResumeOrder, 52 | watchPutResumeOrder, 53 | watchDeleteResumeOrder 54 | } from './resumeOrderSaga' 55 | 56 | export default function* rootSaga() { 57 | yield all([ 58 | fork(watchGetCurrentUser), 59 | fork(watchLogin), 60 | fork(watchLogout), 61 | fork(watchSignUp), 62 | fork(watchUpload), 63 | fork(watchPutUserInfo), 64 | fork(watchGetUserInfo), 65 | fork(watchPostTeams), 66 | fork(watchPutTeams), 67 | fork(watchDeleteTeamMember), 68 | fork(watchDeleteTeam), 69 | fork(watchGetHomeUserList), 70 | fork(watchGetHomeUserDetail), 71 | fork(watchGetHomeTeamList), 72 | fork(watchGetHomeTeamDetail), 73 | fork(watchGetMyTeams), 74 | fork(watchGetInTeams), 75 | fork(watchGetAccountRecruitOrderList), 76 | fork(watchGetHomeRecruitOrderList), 77 | fork(watchPostRecruitOrder), 78 | fork(watchPutRecruitOrder), 79 | fork(watchDeleteRecruitOrder), 80 | fork(watchGetAccountGroupOrderList), 81 | fork(watchGetHomeGroupOrderList), 82 | fork(watchPostGroupOrder), 83 | fork(watchPutGroupOrder), 84 | fork(watchDeleteGroupOrder), 85 | fork(watchGetAccountWarOrderList), 86 | fork(watchGetHomeWarOrderList), 87 | fork(watchPostWarOrder), 88 | fork(watchPutWarOrder), 89 | fork(watchDeleteWarOrder), 90 | fork(watchGetAccountResumeOrderList), 91 | fork(watchGetHomeResumeOrderList), 92 | fork(watchPostResumeOrder), 93 | fork(watchPutResumeOrder), 94 | fork(watchDeleteResumeOrder), 95 | fork(watchSendEmail), 96 | fork(watchSendPasswordReset), 97 | ]) 98 | } 99 | -------------------------------------------------------------------------------- /src/components/HomeGroupCard/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Text, View, TouchableWithoutFeedback } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { Card, Flex, WhiteSpace } from 'antd-mobile' 5 | import TimeAgo from 'react-native-timeago' 6 | 7 | import { cutstr } from '../../utils/utils' 8 | 9 | export default class HomeGroupCard extends PureComponent { 10 | render() { 11 | const { item, navigateTo } = this.props 12 | return ( 13 | 15 | navigateTo('HomeUserInfoDetail', { 16 | objectId: item.user.userinfo.objectId 17 | }) 18 | } 19 | > 20 | 21 | 22 | 32 | 33 | 34 | 35 | 36 | {item.user.userinfo.nickname} 37 | 38 | 39 | 40 | 41 | {item.contact} 42 | 43 | 44 | 45 | 46 | 47 | 48 | 54 | {cutstr(item.description, 60, 0)} 55 | 56 | 57 | 58 | 59 | 66 | 发布时间: 67 | 68 | } 69 | extra={ 70 | 71 | 有效日期: 72 | 73 | } 74 | style={{ paddingBottom: 5 }} 75 | /> 76 | 77 | 78 | 79 | 80 | ) 81 | } 82 | } 83 | 84 | HomeGroupCard.propTypes = { 85 | navigateTo: PropTypes.func, 86 | item: PropTypes.object 87 | } 88 | -------------------------------------------------------------------------------- /src/components/HomeWarCard/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Text, View, TouchableWithoutFeedback } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { Card, Flex, WhiteSpace } from 'antd-mobile' 5 | import TimeAgo from 'react-native-timeago' 6 | 7 | export default class HomeWarCard extends PureComponent { 8 | render() { 9 | const { item, navigateTo } = this.props 10 | return ( 11 | 13 | navigateTo('HomeTeamDetail', { 14 | objectId: item.team.objectId 15 | }) 16 | } 17 | > 18 | 19 | 20 | 30 | 31 | 32 | 33 | 34 | {item.team.englishFullName || 35 | item.team.chineseFullName || 36 | item.team.englishName || 37 | item.team.chineseName} 38 | 39 | 40 | 41 | 42 | {item.contact} 43 | 44 | 45 | 46 | 47 | 48 | 49 | 55 | {item.description} 56 | 57 | 58 | 59 | 60 | 67 | 发布时间: 68 | 69 | } 70 | extra={ 71 | 72 | 有效日期: 73 | 74 | } 75 | style={{ paddingBottom: 5 }} 76 | /> 77 | 78 | 79 | 80 | ) 81 | } 82 | } 83 | 84 | HomeWarCard.propTypes = { 85 | navigateTo: PropTypes.func, 86 | item: PropTypes.object 87 | } 88 | -------------------------------------------------------------------------------- /src/components/AccountRecruitOrderList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { FlatList, View, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { WhiteSpace } from 'antd-mobile' 5 | import AccountRecruitCard from '../AccountRecruitCard' 6 | 7 | export default class AccountRecruitOrderList extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this._onEndReached = this._onEndReached.bind(this) 11 | this._onRefresh = this._onRefresh.bind(this) 12 | this._renderFonter = this._renderFonter.bind(this) 13 | this._renderSeparator = this._renderSeparator.bind(this) 14 | this._renderItem = this._renderItem.bind(this) 15 | } 16 | 17 | _onEndReached() { 18 | if ( 19 | this.props.recruitOrder.isFetching || 20 | !this.props.recruitOrder.isLoadMore 21 | ) { 22 | return 23 | } 24 | const page = this.props.recruitOrder.page + 1 25 | this.props.getAccountRecruitOrderList({ page: page }) 26 | } 27 | 28 | _onRefresh() { 29 | this.props.getAccountRecruitOrderList({ isRefreshing: true }) 30 | } 31 | 32 | _renderFonter() { 33 | return ( 34 | 35 | 36 | {this.props.recruitOrder.isFetching ? '' : '到底了'} 37 | 38 | 39 | ) 40 | } 41 | 42 | _renderItem({ item }) { 43 | return ( 44 | 49 | ) 50 | } 51 | 52 | _renderSeparator() { 53 | return 54 | } 55 | 56 | _getItemLayout(data, index) { 57 | let [length, separator, header] = [197, 3, 0] 58 | return { length, offset: (length + separator) * index + header, index } 59 | } 60 | 61 | _keyExtractor = (item, index) => item.objectId 62 | 63 | render() { 64 | const { 65 | list, 66 | isRefreshing, 67 | fetchingText, 68 | isFetching 69 | } = this.props.recruitOrder 70 | return ( 71 | 86 | ) 87 | } 88 | } 89 | 90 | AccountRecruitOrderList.propTypes = { 91 | recruitOrder: PropTypes.object, 92 | navigateTo: PropTypes.func, 93 | getAccountRecruitOrderList: PropTypes.func, 94 | deleteRecruitOrder: PropTypes.func 95 | } 96 | -------------------------------------------------------------------------------- /src/components/HomeRecruitCard/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Text, View, TouchableWithoutFeedback } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { Card, Flex, WhiteSpace } from 'antd-mobile' 5 | import TimeAgo from 'react-native-timeago' 6 | 7 | import { cutstr } from '../../utils/utils' 8 | 9 | export default class HomeRecruitCard extends PureComponent { 10 | render() { 11 | const { item, navigateTo } = this.props 12 | return ( 13 | 15 | navigateTo('HomeTeamDetail', { 16 | objectId: item.team.objectId 17 | }) 18 | } 19 | > 20 | 21 | 22 | 32 | 33 | 34 | 35 | 36 | {item.team.englishFullName || 37 | item.team.chineseFullName || 38 | item.team.englishName || 39 | item.team.chineseName} 40 | 41 | 42 | 43 | 44 | {item.contact} 45 | 46 | 47 | 48 | 49 | 50 | 51 | 56 | {item.description} 57 | 58 | 59 | 60 | 61 | 69 | 发布时间: 70 | 71 | } 72 | extra={ 73 | 74 | 有效日期: 75 | 76 | } 77 | style={{ paddingBottom: 5 }} 78 | /> 79 | 80 | 81 | 82 | 83 | ) 84 | } 85 | } 86 | 87 | HomeRecruitCard.propTypes = { 88 | navigateTo: PropTypes.func, 89 | item: PropTypes.object 90 | } 91 | -------------------------------------------------------------------------------- /src/components/HomeTeamCard/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Text, View, TouchableWithoutFeedback } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { Card, Flex, WhiteSpace } from 'antd-mobile' 5 | import TimeAgo from 'react-native-timeago' 6 | 7 | import { CardStyle } from '../../components/CustomStyles' 8 | import { cutstr } from '../../utils/utils' 9 | 10 | export default class HomeTeamCard extends PureComponent { 11 | render() { 12 | const { item, navigateTo } = this.props 13 | return ( 14 | 16 | navigateTo('HomeTeamDetail', { objectId: item.objectId }) 17 | } 18 | > 19 | 20 | 21 | 36 | 37 | 38 | 39 | 40 | {cutstr(item.introduction, 400, 0)} 41 | 42 | 43 | 44 | {item.isRecruit ? ( 45 | 46 | 47 | 48 | {item.isRecruit ? ( 49 | 50 | 正在招募 51 | 52 | ) : ( 53 | 暂无招募 54 | )} 55 | 56 | 57 | 58 | 59 | 60 | 61 | 联系方式:{item.contact ? item.contact : '暂无'} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 战队地点:{item.createCity ? item.createCity : '暂无'} 70 | 71 | 72 | 73 | 74 | ) : null} 75 | 76 | 77 | 78 | 79 | ) 80 | } 81 | } 82 | 83 | HomeTeamCard.propTypes = { 84 | navigateTo: PropTypes.func, 85 | item: PropTypes.object 86 | } 87 | -------------------------------------------------------------------------------- /src/components/HomeUserInfoCard/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Text, View, TouchableWithoutFeedback, Image } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { Card, Flex, WhiteSpace, Grid } from 'antd-mobile' 5 | import TimeAgo from 'react-native-timeago' 6 | import { RANKS, TEAMPOSITIONS } from '../../constants' 7 | 8 | export default class HomeUserInfoCard extends PureComponent { 9 | layout = e => { 10 | console.log(e) 11 | } 12 | 13 | render() { 14 | const { item, navigateTo } = this.props 15 | return ( 16 | 18 | navigateTo('HomeUserDetail', { 19 | objectId: item.objectId 20 | }) 21 | } 22 | > 23 | this.layout(e)}> 24 | 25 | 35 | 36 | 37 | 38 | 39 | 天梯:{item.rankscore ? item.rankscore + '分' : '未知'} 40 | 41 | 42 | 43 | 44 | 段位:{item.rank 45 | ? RANKS.filter(x => x.value === item.rank)[0].label 46 | : '未知'} 47 | 48 | 49 | 50 | 51 | 位置:{item.position 52 | ? TEAMPOSITIONS.filter(x => x.value === item.position)[0] 53 | .label 54 | : '未知'} 55 | 56 | 57 | 58 | 59 | {item.heros ? ( 60 | ( 65 | 66 | 70 | 71 | )} 72 | /> 73 | ) : ( 74 | 75 | )} 76 | 77 | 78 | 79 | {item.introduction} 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | ) 88 | } 89 | } 90 | 91 | HomeUserInfoCard.propTypes = { 92 | navigateTo: PropTypes.func, 93 | item: PropTypes.object 94 | } 95 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Provider, connect } from 'react-redux' 3 | import { PersistGate } from 'redux-persist/es/integration/react' 4 | import { BackHandler, Platform, StatusBar, View } from 'react-native' 5 | import { addNavigationHelpers, NavigationActions } from 'react-navigation' 6 | import { AppLoading, Asset, Font } from 'expo' 7 | import { Ionicons } from '@expo/vector-icons' 8 | import { Toast } from 'antd-mobile' 9 | import Navigator from './routes' 10 | import configureStore from './store' 11 | import rootSaga from './sagas' 12 | 13 | function cacheImages(images) { 14 | return images.map(image => { 15 | if (typeof image === 'string') { 16 | return Image.prefetch(image) 17 | } else { 18 | return Asset.fromModule(image).downloadAsync() 19 | } 20 | }) 21 | } 22 | 23 | function cacheFonts(fonts) { 24 | return fonts.map(font => Font.loadAsync(font)) 25 | } 26 | 27 | class App extends Component { 28 | state = { 29 | isReady: false 30 | } 31 | async _loadAssetsAsync() { 32 | const imageAssets = cacheImages([ 33 | require('../assets/images/home.png'), 34 | require('../assets/images/home_icon.png'), 35 | require('../assets/images/avatar_logo.png') 36 | ]) 37 | const fontAssets = cacheFonts([Ionicons.font]) 38 | await Promise.all([...imageAssets, ...fontAssets]) 39 | } 40 | 41 | onBackPress = () => { 42 | const { dispatch, nav } = this.props 43 | if (nav.routes[0].index === 0) { 44 | if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) { 45 | BackHandler.exitApp() 46 | return true 47 | } 48 | this.lastBackPressed = Date.now() 49 | Toast.show('再按一次退出应用') 50 | return true 51 | } else { 52 | dispatch(NavigationActions.back()) 53 | return true 54 | } 55 | } 56 | 57 | componentWillUnmount() { 58 | BackHandler.removeEventListener('hardwareBackPress', this.onBackPress) 59 | } 60 | 61 | componentDidMount() { 62 | if (Platform.OS === 'android') { 63 | BackHandler.addEventListener('hardwareBackPress', this.onBackPress) 64 | } 65 | } 66 | 67 | render() { 68 | const { dispatch, nav } = this.props 69 | if (!this.state.isReady) { 70 | return ( 71 | { 74 | this.setState({ isReady: true }) 75 | }} 76 | onError={console.warn} 77 | /> 78 | ) 79 | } 80 | return ( 81 | 82 | 88 | 89 | ) 90 | } 91 | } 92 | 93 | const mapStateToProps = state => ({ 94 | nav: state.nav 95 | }) 96 | 97 | const AppWithNavigator = connect(mapStateToProps)(App) 98 | 99 | export default () => { 100 | const { persistor, store } = configureStore({}) 101 | 102 | store.runSaga(rootSaga) 103 | 104 | return ( 105 | 106 | 107 | 108 | 109 | 110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /src/services/leanclound/groupOrder.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage' 2 | import { getCurrentUserAsync } from './user' 3 | import { getDayStart, getDayEnd } from '../../utils/utils' 4 | 5 | // 创建组队帖 6 | export function cerateGroupOrder(payload, userinfo, currentUser) { 7 | const groupOrders = new AV.Object('GroupOrders') 8 | groupOrders.set('title', payload.title) 9 | groupOrders.set('description', payload.description) 10 | groupOrders.set('contact', payload.contact) 11 | const endDate = new Date(payload.endDate) 12 | groupOrders.set('endDate', endDate) 13 | groupOrders.set('user', currentUser) 14 | groupOrders.set('stick', 0) 15 | groupOrders.set('show', 1) 16 | 17 | var acl = new AV.ACL() 18 | acl.setPublicReadAccess(true) 19 | acl.setWriteAccess(currentUser, true) 20 | groupOrders.setACL(acl) 21 | 22 | return groupOrders.save().then(function(result) { 23 | return { ...result.toJSON(), userinfo } 24 | }) 25 | } 26 | 27 | export function updateGroupOrder(payload) { 28 | const groupOrders = AV.Object.createWithoutData( 29 | 'GroupOrders', 30 | payload.objectId 31 | ) 32 | groupOrders.set('title', payload.title) 33 | groupOrders.set('description', payload.description) 34 | groupOrders.set('contact', payload.contact) 35 | const endDate = new Date(payload.endDate) 36 | groupOrders.set('endDate', endDate) 37 | 38 | return groupOrders.save().then(function(result) { 39 | return { 40 | ...result.toJSON() 41 | } 42 | }) 43 | } 44 | 45 | export function removeGroupOrder(payload) { 46 | var groupOrders = AV.Object.createWithoutData( 47 | 'GroupOrders', 48 | payload.objectId 49 | ) 50 | return groupOrders.destroy().then(function(success) { 51 | return success.toJSON() 52 | }) 53 | } 54 | 55 | export function getAccountGroupOrderList(payload, currentUser) { 56 | let list = [] 57 | let { page, pagesize } = payload 58 | pagesize = pagesize || 20 59 | const query = new AV.Query('GroupOrders') 60 | query.equalTo('user', currentUser) 61 | query.greaterThanOrEqualTo('endDate', new Date()) 62 | query.descending('updatedAt') 63 | query.limit(pagesize) 64 | query.skip(pagesize * (page - 1)) 65 | query.include('user.userinfo') 66 | return query.find().then(function(result) { 67 | result.forEach(item => { 68 | list.push(item.toJSON()) 69 | }) 70 | return list 71 | }) 72 | } 73 | 74 | export function getHomeGroupOrderList(payload) { 75 | let list = [] 76 | let { page, pagesize } = payload 77 | pagesize = pagesize || 20 78 | const query = new AV.Query('GroupOrders') 79 | query.equalTo('show', 1) 80 | query.greaterThanOrEqualTo('endDate', new Date()) 81 | query.descending('stick') 82 | query.descending('createdAt') 83 | query.limit(pagesize) 84 | query.skip(pagesize * (page - 1)) 85 | query.include('user.userinfo') 86 | return query.find().then(function(result) { 87 | result.forEach(item => { 88 | list.push(item.toJSON()) 89 | }) 90 | return list 91 | }) 92 | } 93 | 94 | export function getGroupOrderCountOfToday(currentUser) { 95 | const query = new AV.Query('GroupOrders') 96 | query.equalTo('user', currentUser) 97 | query.lessThanOrEqualTo('createdAt', getDayEnd()) 98 | query.greaterThanOrEqualTo('createdAt', getDayStart()) 99 | return query.count().then(function(result) { 100 | return result 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /src/services/leanclound/resumeOrder.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage' 2 | import { getCurrentUserAsync } from './user' 3 | import { getDayStart, getDayEnd } from '../../utils/utils' 4 | 5 | export function cerateResumeOrder(payload, userinfo, currentUser) { 6 | const resumeOrders = new AV.Object('ResumeOrders') 7 | resumeOrders.set('title', payload.title) 8 | resumeOrders.set('description', payload.description) 9 | resumeOrders.set('contact', payload.contact) 10 | const endDate = new Date(payload.endDate) 11 | resumeOrders.set('endDate', endDate) 12 | resumeOrders.set('user', currentUser) 13 | resumeOrders.set('stick', 0) 14 | resumeOrders.set('show', 1) 15 | 16 | var acl = new AV.ACL() 17 | acl.setPublicReadAccess(true) 18 | acl.setWriteAccess(currentUser, true) 19 | 20 | resumeOrders.setACL(acl) 21 | 22 | return resumeOrders.save().then(function(result) { 23 | return { ...result.toJSON(), userinfo } 24 | }) 25 | } 26 | 27 | export function updateResumeOrder(payload) { 28 | const resumeOrders = AV.Object.createWithoutData( 29 | 'ResumeOrders', 30 | payload.objectId 31 | ) 32 | resumeOrders.set('title', payload.title) 33 | resumeOrders.set('description', payload.description) 34 | resumeOrders.set('contact', payload.contact) 35 | const endDate = new Date(payload.endDate) 36 | resumeOrders.set('endDate', endDate) 37 | 38 | return resumeOrders.save().then(function(result) { 39 | return { 40 | ...result.toJSON() 41 | } 42 | }) 43 | } 44 | 45 | export function removeResumeOrder(payload) { 46 | var resumeOrders = AV.Object.createWithoutData( 47 | 'ResumeOrders', 48 | payload.objectId 49 | ) 50 | return resumeOrders.destroy().then(function(success) { 51 | return success.toJSON() 52 | }) 53 | } 54 | 55 | export function getAccountResumeOrderList(payload, currentUser) { 56 | let list = [] 57 | let { page, pagesize } = payload 58 | pagesize = pagesize || 20 59 | const query = new AV.Query('ResumeOrders') 60 | query.descending('updatedAt') 61 | query.limit(pagesize) 62 | query.skip(pagesize * (page - 1)) 63 | query.equalTo('user', currentUser) 64 | query.greaterThanOrEqualTo('endDate', new Date()) 65 | query.include('user.userinfo') 66 | return query.find().then(function(result) { 67 | result.forEach(item => { 68 | list.push(item.toJSON()) 69 | }) 70 | return list 71 | }) 72 | } 73 | 74 | export function getHomeResumeOrderList(payload) { 75 | let list = [] 76 | let { page, pagesize } = payload 77 | pagesize = pagesize || 20 78 | const query = new AV.Query('ResumeOrders') 79 | query.equalTo('show', 1) 80 | query.greaterThanOrEqualTo('endDate', new Date()) 81 | query.descending('stick') 82 | query.descending('createdAt') 83 | query.limit(pagesize) 84 | query.skip(pagesize * (page - 1)) 85 | query.include('user.userinfo') 86 | return query.find().then(function(result) { 87 | result.forEach(item => { 88 | list.push(item.toJSON()) 89 | }) 90 | return list 91 | }) 92 | } 93 | 94 | export function getResumeOrderCountOfToday(currentUser) { 95 | const query = new AV.Query('ResumeOrders') 96 | query.equalTo('user', currentUser) 97 | query.lessThanOrEqualTo('createdAt', getDayEnd()) 98 | query.greaterThanOrEqualTo('createdAt', getDayStart()) 99 | return query.count().then(function(result) { 100 | return result 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OverWatchTeams-React-Native-Expo 2 | 3 | ## iOS演示 4 | 5 | ![ezgif-2-466979bde1.gif](https://i.loli.net/2017/12/30/5a473217396e1.gif) 6 | 7 | ## 开源地址 8 | 9 | [GitHub](https://github.com/zxj963577494/OverWatchTeams-React-Native-Expo) 10 | 11 | [Gitee](https://gitee.com/zhengxujiang/OverWatchTeams-React-Redux-Native) 12 | 13 | ## 项目地址 14 | 15 | ### Android 版 16 | 17 | ![android.png](https://i.loli.net/2017/12/30/5a47184bb539c.png) 18 | 19 | [Android](https://fir.im/yg3w) 20 | 21 | ### Expo 版(需 Expo 移动端([Android](https://play.google.com/store/apps/details?id=host.exp.exponent)/[iOS](https://itunes.com/apps/exponent))) 22 | 23 | ![expo.png](https://i.loli.net/2017/12/30/5a47184bb1438.png) 24 | 25 | [Expo](https://exp.host/@linq/OverWatchTeams) 26 | 27 | ## 简介 28 | 29 | 项目基于[Expo](https://expo.io/)的 React Native 构建技术。 30 | 31 | Expo 是一个围绕 React Native 构建的免费开源工具链,可帮助您使用 JavaScript 和 React 构建本地 iOS 和 Android 项目 32 | 33 | [Expo 文档](https://docs.expo.io/versions/latest/index.html) 34 | 35 | ## 技术栈 36 | 37 | * Expo 38 | * React-Native 39 | * React-Navigation 40 | * Redux 41 | * Redux-Saga 42 | * LeanCloud 43 | * Antd-Mobile 44 | 45 | ## 项目结构 46 | 47 | ![screenshot_22.png](https://i.loli.net/2017/12/30/5a471b6bc4ea1.png) 48 | 49 | ## 准备工具 50 | 51 | 1. 获取 Expo 构建桌面客户端(XDE) 52 | 53 | * [macOS](https://xde-updates.exponentjs.com/download/mac) 54 | 55 | * [Windows64-bit](https://xde-updates.exponentjs.com/download/win32) 56 | 57 | * [Linux](https://xde-updates.exponentjs.com/download/linux-x86_64) 58 | 59 | 2. 获取 Expo 预览 iOS 或 Android 客户端 60 | 61 | * [Android](https://play.google.com/store/apps/details?id=host.exp.exponent) 62 | 63 | ![google.png](https://i.loli.net/2017/12/30/5a47184bb7ee3.png) 64 | 65 | * [iOS](https://itunes.com/apps/exponent) 66 | 67 | ![appstore.png](https://i.loli.net/2017/12/30/5a47184bb675c.png) 68 | 69 | 3. [参考资料](https://docs.expo.io/versions/latest/introduction/installation.html) 70 | 71 | ## 使用方式 72 | 73 | 1. git clone https://github.com/zxj963577494/OverWatchTeams-React-Native-Expo.git 74 | 75 | 2. 打开桌面客户端(XDE)加载本项目,启动本项目,点击 Share 获取二维码 76 | 77 | 3. 打开 iOS 或 Android 客户端,扫描 XDE 客户端二维码 78 | 79 | 4. [参考资料](https://docs.expo.io/versions/latest/introduction/xde-tour.html) 80 | 81 | ## 项目生成 82 | 83 | 1. yarn global add exp 84 | 85 | 2. exp build:ios / build:android 86 | 87 | 3. exp build:status 88 | 89 | 4. [参考资料](https://docs.expo.io/versions/latest/guides/exp-cli.html) 90 | 91 | ## 项目发布 92 | 93 | 1. yarn global add exp 94 | 95 | 2. exp publish 96 | 97 | ## 一些说明 98 | 99 | `exp build:ios/build:android`生成的是 JS Bundle 文件,Expo 会将该文件上传到 Expo 云端,由 Expo 构建 APP,使用`exp build:status`可以得到 APP 在云端构建的进度,构建完成后,会返回 APP 地址 100 | 101 | 使用 `exp build:ios` 时必须有$99 的开发者账户(我没有,所以没构建 iOS 版本),如果 apple id 开启了两步验证,需要加`--local-auth` 102 | 103 | `exp publish`用于发布 JS Bundle 文件,更改 app.json 文件的版本号,icon 之类,用户下载的 App 会自动同步发布时版本所改动的内容,类似于 CodePush 热更新。[参考资料 1](https://docs.expo.io/versions/latest/guides/publishing.html) [参考资料 2](https://docs.expo.io/versions/latest/guides/offline-support.html) 104 | 105 | Expo 无法像原生 React Native 一样,可以对本地模块进行操作,当然`react-native link` 命令也是无法使用的,但你可以使用 Expo 开放的操作本地功能的 API,[SDK API 参考](https://docs.expo.io/versions/latest/sdk/index.html) 106 | 107 | ## 开源协议 108 | 109 | [GPL-2.0](./LICENSE) 110 | 111 | ## 免责声明 112 | 113 | 网站数据均为测试数据,不作为商业用途使用 114 | 115 | ## Web版 116 | 117 | [https://github.com/zxj963577494/OverWatchTeams](https://github.com/zxj963577494/OverWatchTeams) -------------------------------------------------------------------------------- /src/services/leanclound/warOrder.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage' 2 | import { getCurrentUserAsync } from './user' 3 | import { getDayStart, getDayEnd } from '../../utils/utils' 4 | 5 | // 创建战队训练赛约战 6 | export function cerateWarOrder(payload, team, currentUser) { 7 | const teamData = AV.Object.createWithoutData('Teams', payload.teamid) 8 | const warOrders = new AV.Object('WarOrders') 9 | warOrders.set('title', payload.title) 10 | warOrders.set('description', payload.description) 11 | warOrders.set('contact', payload.contact) 12 | warOrders.set('stick', 0) 13 | warOrders.set('show', 1) 14 | const endDate = new Date(payload.endDate) 15 | warOrders.set('endDate', endDate) 16 | warOrders.set('user', currentUser) 17 | warOrders.set('team', teamData) 18 | 19 | var acl = new AV.ACL() 20 | acl.setPublicReadAccess(true) 21 | acl.setWriteAccess(currentUser, true) 22 | warOrders.setACL(acl) 23 | 24 | return warOrders.save().then(function(result) { 25 | return {...result.toJSON(), team} 26 | }) 27 | } 28 | 29 | export function updateWarOrder(payload, team) { 30 | const warOrders = AV.Object.createWithoutData( 31 | 'WarOrders', 32 | payload.objectId 33 | ) 34 | warOrders.set('title', payload.title) 35 | warOrders.set('description', payload.description) 36 | warOrders.set('contact', payload.contact) 37 | const endDate = new Date(payload.endDate) 38 | warOrders.set('endDate', endDate) 39 | warOrders.set('team', team) 40 | 41 | return warOrders.save().then(function(result) { 42 | return { 43 | ...result.toJSON(), 44 | team: team.toJSON() 45 | } 46 | }) 47 | } 48 | 49 | export function removeWarOrder(payload) { 50 | var warOrders = AV.Object.createWithoutData('WarOrders', payload.objectId) 51 | return warOrders.destroy().then(function(success) { 52 | return success.toJSON() 53 | }) 54 | } 55 | 56 | export function getAccountWarOrderList(payload, currentUser) { 57 | let list = [] 58 | let { page, pagesize } = payload 59 | pagesize = pagesize || 20 60 | const query = new AV.Query('WarOrders') 61 | query.equalTo('user', currentUser) 62 | query.greaterThanOrEqualTo('endDate', new Date()) 63 | query.descending('updatedAt') 64 | query.limit(pagesize) 65 | query.skip(pagesize * (page - 1)) 66 | query.include('team') 67 | return query.find().then(function(result) { 68 | result.forEach(item => { 69 | const team = item.get('team').toJSON() 70 | const res = { ...item.toJSON(), team } 71 | list.push(res) 72 | }) 73 | return list 74 | }) 75 | } 76 | 77 | export function getHomeWarOrderList(payload) { 78 | let list = [] 79 | let { page, pagesize } = payload 80 | pagesize = pagesize || 20 81 | const query = new AV.Query('WarOrders') 82 | query.equalTo('show', 1) 83 | query.greaterThanOrEqualTo('endDate', new Date()) 84 | query.descending('stick') 85 | query.descending('createdAt') 86 | query.limit(pagesize) 87 | query.skip(pagesize * (page - 1)) 88 | query.include('team') 89 | return query.find().then(function(result) { 90 | result.forEach(item => { 91 | const team = item.get('team').toJSON() 92 | const res = { ...item.toJSON(), team } 93 | list.push(res) 94 | }) 95 | return list 96 | }) 97 | } 98 | 99 | export function getWarOrderCountOfToday(currentUser) { 100 | const query = new AV.Query('WarOrders') 101 | query.equalTo('user', currentUser) 102 | query.lessThanOrEqualTo('createdAt', getDayEnd()) 103 | query.greaterThanOrEqualTo('createdAt', getDayStart()) 104 | return query.count().then(function(result) { 105 | return result 106 | }) 107 | } -------------------------------------------------------------------------------- /src/services/leanclound/recruitOrder.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage' 2 | import { getCurrentUserAsync } from './user' 3 | import { getDayStart, getDayEnd } from '../../utils/utils' 4 | 5 | // 创建招募令 6 | export function cerateRecruitOrder(payload, team, currentUser) { 7 | const teamData = AV.Object.createWithoutData('Teams', payload.teamid) 8 | const recruitOrders = new AV.Object('RecruitOrders') 9 | recruitOrders.set('title', payload.title) 10 | recruitOrders.set('description', payload.description) 11 | recruitOrders.set('contact', payload.contact) 12 | recruitOrders.set('stick', 0) 13 | recruitOrders.set('show', 1) 14 | const endDate = new Date(payload.endDate) 15 | recruitOrders.set('endDate', endDate) 16 | recruitOrders.set('user', currentUser) 17 | recruitOrders.set('team', teamData) 18 | 19 | var acl = new AV.ACL() 20 | acl.setPublicReadAccess(true) 21 | acl.setWriteAccess(currentUser, true) 22 | recruitOrders.setACL(acl) 23 | 24 | return recruitOrders.save().then(function(result) { 25 | return { ...result.toJSON(), team } 26 | }) 27 | } 28 | 29 | export function updateRecruitOrder(payload, team) { 30 | const recruitOrders = AV.Object.createWithoutData( 31 | 'RecruitOrders', 32 | payload.objectId 33 | ) 34 | recruitOrders.set('title', payload.title) 35 | recruitOrders.set('description', payload.description) 36 | recruitOrders.set('contact', payload.contact) 37 | const endDate = new Date(payload.endDate) 38 | recruitOrders.set('endDate', endDate) 39 | 40 | return recruitOrders.save().then(function(result) { 41 | return { 42 | ...result.toJSON(), 43 | team: team.toJSON() 44 | } 45 | }) 46 | } 47 | 48 | export function removeRecruitOrder(payload) { 49 | var recruitOrders = AV.Object.createWithoutData( 50 | 'RecruitOrders', 51 | payload.objectId 52 | ) 53 | return recruitOrders.destroy().then(function(success) { 54 | return success.toJSON() 55 | }) 56 | } 57 | 58 | export function getAccountRecruitOrderList(payload, currentUser) { 59 | let list = [] 60 | let { page, pagesize } = payload 61 | pagesize = pagesize || 20 62 | const query = new AV.Query('RecruitOrders') 63 | query.equalTo('user', currentUser) 64 | query.greaterThanOrEqualTo('endDate', new Date()) 65 | query.descending('updatedAt') 66 | query.limit(pagesize) 67 | query.skip(pagesize * (page - 1)) 68 | query.include('team') 69 | return query.find().then(function(result) { 70 | result.forEach(item => { 71 | const team = item.get('team').toJSON() 72 | const res = { ...item.toJSON(), team } 73 | list.push(res) 74 | }) 75 | return list 76 | }) 77 | } 78 | 79 | export function getHomeRecruitOrderList(payload) { 80 | let list = [] 81 | let { page, pagesize } = payload 82 | pagesize = pagesize || 20 83 | const query = new AV.Query('RecruitOrders') 84 | query.equalTo('show', 1) 85 | query.greaterThanOrEqualTo('endDate', new Date()) 86 | query.descending('stick') 87 | query.descending('createdAt') 88 | query.limit(pagesize) 89 | query.skip(pagesize * (page - 1)) 90 | query.include('team') 91 | return query.find().then(function(result) { 92 | result.forEach(item => { 93 | const team = item.get('team').toJSON() 94 | const res = { ...item.toJSON(), team } 95 | list.push(res) 96 | }) 97 | return list 98 | }) 99 | } 100 | 101 | export function getRecruitOrderCountOfToday(currentUser) { 102 | const query = new AV.Query('RecruitOrders') 103 | query.equalTo('user', currentUser) 104 | console.log(getDayEnd()) 105 | console.log(getDayStart()) 106 | query.lessThanOrEqualTo('createdAt', getDayEnd()) 107 | query.greaterThanOrEqualTo('createdAt', getDayStart()) 108 | return query.count().then(function(result) { 109 | return result 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /src/components/AccountResumeCard/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Text, View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { Card, Flex, WhiteSpace, Button, Modal } from 'antd-mobile' 5 | import TimeAgo from 'react-native-timeago' 6 | import { cutstr } from '../../utils/utils' 7 | import { CardStyle } from '../CustomStyles' 8 | 9 | export default class AccountResumeCard extends PureComponent { 10 | onRemove = objectId => e => { 11 | Modal.alert('警告', '是否删除该自荐帖?', [ 12 | { text: '取消', onPress: () => console.log('cancel') }, 13 | { 14 | text: '确定', 15 | onPress: () => this.props.deleteResumeOrder({ objectId: objectId }) 16 | } 17 | ]) 18 | } 19 | 20 | render() { 21 | const { item, navigateTo } = this.props 22 | return ( 23 | 24 | 25 | 36 | 49 | 50 | 59 | 60 | } 61 | /> 62 | 63 | 64 | 65 | 66 | {item.user.userinfo.nickname} 67 | 68 | 69 | 70 | 71 | {item.contact} 72 | 73 | 74 | 75 | 76 | 77 | 78 | 83 | {item.description} 84 | 85 | 86 | 87 | 88 | 96 | 发布时间: 97 | 98 | } 99 | extra={ 100 | 101 | 有效日期: 102 | 103 | } 104 | style={{ paddingBottom: 5 }} 105 | /> 106 | 107 | 108 | ) 109 | } 110 | } 111 | 112 | AccountResumeCard.propTypes = { 113 | navigateTo: PropTypes.func, 114 | item: PropTypes.object, 115 | deleteResumeOrder: PropTypes.func 116 | } 117 | -------------------------------------------------------------------------------- /src/components/AccountGroupCard/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Text, View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { Card, Flex, WhiteSpace, Button, Modal } from 'antd-mobile' 5 | import TimeAgo from 'react-native-timeago' 6 | import { cutstr } from '../../utils/utils' 7 | import { CardStyle } from '../CustomStyles' 8 | 9 | export default class AccountGroupCard extends PureComponent { 10 | onRemove = objectId => e => { 11 | Modal.alert('警告', '是否删除该组队帖?', [ 12 | { text: '取消', onPress: () => console.log('cancel') }, 13 | { 14 | text: '确定', 15 | onPress: () => this.props.deleteGroupOrder({ objectId: objectId }) 16 | } 17 | ]) 18 | } 19 | 20 | render() { 21 | const { item, navigateTo } = this.props 22 | return ( 23 | 24 | 25 | 36 | 49 | 50 | 59 | 60 | } 61 | /> 62 | 63 | 64 | 65 | 66 | {item.user.userinfo.nickname} 67 | 68 | 69 | 70 | 71 | {item.contact} 72 | 73 | 74 | 75 | 76 | 77 | 78 | 83 | {item.description} 84 | 85 | 86 | 87 | 88 | 96 | 发布时间: 97 | 98 | } 99 | extra={ 100 | 101 | 有效日期: 102 | 103 | } 104 | style={{ paddingBottom: 5 }} 105 | /> 106 | 107 | 108 | ) 109 | } 110 | } 111 | 112 | AccountGroupCard.propTypes = { 113 | navigateTo: PropTypes.func, 114 | item: PropTypes.object, 115 | deleteGroupOrder: PropTypes.func 116 | } 117 | -------------------------------------------------------------------------------- /src/components/AccountWarCard/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Text, View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { Card, Flex, WhiteSpace, Button, Modal } from 'antd-mobile' 5 | import TimeAgo from 'react-native-timeago' 6 | import { cutstr } from '../../utils/utils' 7 | import { CardStyle } from '../CustomStyles' 8 | 9 | export default class AccountWarCard extends PureComponent { 10 | onRemove = objectId => e => { 11 | Modal.alert('警告', '是否删除该约战帖?', [ 12 | { text: '取消', onPress: () => console.log('cancel') }, 13 | { 14 | text: '确定', 15 | onPress: () => this.props.deleteWarOrder({ objectId: objectId }) 16 | } 17 | ]) 18 | } 19 | 20 | render() { 21 | const { item, navigateTo } = this.props 22 | return ( 23 | 24 | 25 | 36 | 49 | 50 | 59 | 60 | } 61 | /> 62 | 63 | 64 | 65 | 66 | {item.team.englishFullName || 67 | item.team.chineseFullName || 68 | item.team.englishName || 69 | item.team.chineseName} 70 | 71 | 72 | 73 | 74 | {item.contact} 75 | 76 | 77 | 78 | 79 | 80 | 81 | 86 | {item.description} 87 | 88 | 89 | 90 | 91 | 99 | 发布时间: 100 | 101 | } 102 | extra={ 103 | 104 | 有效日期: 105 | 106 | } 107 | style={{ paddingBottom: 5 }} 108 | /> 109 | 110 | 111 | ) 112 | } 113 | } 114 | 115 | AccountWarCard.propTypes = { 116 | navigateTo: PropTypes.func, 117 | item: PropTypes.object, 118 | deleteWarOrder: PropTypes.func 119 | } 120 | -------------------------------------------------------------------------------- /src/components/AccountRecruitCard/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Text, View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { Card, Flex, WhiteSpace, Button, Modal } from 'antd-mobile' 5 | import TimeAgo from 'react-native-timeago' 6 | import { cutstr } from '../../utils/utils' 7 | import { CardStyle } from '../CustomStyles' 8 | 9 | export default class AccountRecruitCard extends PureComponent { 10 | onRemove = objectId => e => { 11 | Modal.alert('警告', '是否删除该招募令?', [ 12 | { text: '取消', onPress: () => console.log('cancel') }, 13 | { 14 | text: '确定', 15 | onPress: () => this.props.deleteRecruitOrder({ objectId: objectId }) 16 | } 17 | ]) 18 | } 19 | 20 | render() { 21 | const { item, navigateTo } = this.props 22 | return ( 23 | 24 | 25 | 36 | 49 | 50 | 59 | 60 | } 61 | /> 62 | 63 | 64 | 65 | 66 | {item.team.englishFullName || 67 | item.team.chineseFullName || 68 | item.team.englishName || 69 | item.team.chineseName} 70 | 71 | 72 | 73 | 74 | {item.contact} 75 | 76 | 77 | 78 | 79 | 80 | 81 | 86 | {item.description} 87 | 88 | 89 | 90 | 91 | 99 | 发布时间: 100 | 101 | } 102 | extra={ 103 | 104 | 有效日期: 105 | 106 | } 107 | style={{ paddingBottom: 5 }} 108 | /> 109 | 110 | 111 | ) 112 | } 113 | } 114 | 115 | AccountRecruitCard.propTypes = { 116 | navigateTo: PropTypes.func, 117 | item: PropTypes.object, 118 | deleteRecruitOrder: PropTypes.func 119 | } 120 | -------------------------------------------------------------------------------- /src/screens/Account/EmaiVerify/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, StyleSheet } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { connect } from 'react-redux' 5 | import { createForm } from 'rc-form' 6 | import { 7 | Button, 8 | InputItem, 9 | WhiteSpace, 10 | Flex, 11 | WingBlank, 12 | List, 13 | Toast 14 | } from 'antd-mobile' 15 | import _ from 'lodash' 16 | import { setNavBar, sendEmailRequest } from '../../../actions' 17 | import { userService } from '../../../services/leanclound' 18 | 19 | class AccountEmaiVerify extends Component { 20 | constructor(props) { 21 | super(props) 22 | this.state = { 23 | isVerify: props.user.emailVerified, 24 | email: props.user.email, 25 | pending: props.app.isFetching 26 | } 27 | this.onEmailChange = this.onEmailChange.bind(this) 28 | this.onSubmit = this.onSubmit.bind(this) 29 | } 30 | 31 | onEmailChange(value) { 32 | this.setState({ 33 | email: value 34 | }) 35 | } 36 | 37 | onSubmit() { 38 | const { sendEmail, form } = this.props 39 | form.validateFields((error, value) => { 40 | if (!error) { 41 | sendEmail({ 42 | email: this.state.email 43 | }) 44 | } else { 45 | Toast.fail('格式错误,请检查后提交', 1.5) 46 | } 47 | }) 48 | } 49 | 50 | componentDidMount() {} 51 | 52 | render() { 53 | const { getFieldProps, getFieldError } = this.props.form 54 | const { app, pending, user } = this.props 55 | const { email, isVerify } = this.state 56 | const emailErrors = getFieldError('email') 57 | const VerifyMessage = user.emailVerified ? '已验证' : '未验证' 58 | return ( 59 | 60 | ( 62 | 70 | 邮箱地址[{VerifyMessage}] 71 | 72 | )} 73 | > 74 | 91 | 92 | 93 | 94 | 95 | {emailErrors ? emailErrors.join(',') : null} 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 110 | 111 | 112 | {app.emailError} 113 | 114 | 115 | 116 | 117 | ) 118 | } 119 | } 120 | 121 | const mapStateToProps = (state, ownProps) => { 122 | return { 123 | app: state.app, 124 | user: state.user.account.user 125 | } 126 | } 127 | 128 | const mapDispatchToProps = (dispatch, ownProps) => { 129 | return { 130 | sendEmail: payload => { 131 | dispatch(sendEmailRequest(payload)) 132 | } 133 | } 134 | } 135 | 136 | AccountEmaiVerify.propTypes = { 137 | app: PropTypes.object, 138 | user: PropTypes.object, 139 | sendEmail: PropTypes.func, 140 | form: PropTypes.object 141 | } 142 | 143 | const styles = StyleSheet.create({ 144 | error: { 145 | color: 'red', 146 | textAlign: 'center' 147 | } 148 | }) 149 | 150 | export default connect(mapStateToProps, mapDispatchToProps)( 151 | createForm()(AccountEmaiVerify) 152 | ) 153 | -------------------------------------------------------------------------------- /src/screens/Home/Teams/Detail/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Text, ScrollView, View, Image } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { connect } from 'react-redux' 5 | import { List, Card, WhiteSpace } from 'antd-mobile' 6 | import { setNavBar, getHomeTeamDetailRequest } from '../../../../actions' 7 | import { RANKS } from '../../../../constants' 8 | 9 | class HomeTeamDetail extends Component { 10 | constructor(props) { 11 | super(props) 12 | this.state = { 13 | isGetMember: false 14 | } 15 | this.getTeamById = this.getTeamById.bind(this) 16 | } 17 | 18 | componentDidMount() { 19 | if (!this.props.team) { 20 | const id = this.props.navigation.state.params.objectId 21 | this.props.getTeamById({ objectId: id }) 22 | } 23 | } 24 | 25 | getTeamById() { 26 | this.setState({ 27 | isGetMember: true 28 | }) 29 | const id = this.props.navigation.state.params.objectId 30 | this.props.getTeamById({ objectId: id }) 31 | } 32 | 33 | render() { 34 | let { team, current } = this.props 35 | if ((team == null && current != null) || this.state.isGetMember) { 36 | team = current 37 | } 38 | if (!team) { 39 | return null 40 | } 41 | return ( 42 | 43 | 44 | 45 | 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 | x.value === team.rank)[0].label 88 | : '未知' 89 | } 90 | > 91 | 平均段位 92 | 93 | 94 | '战队介绍'}> 95 | 96 | {team.introduction} 97 | 98 | 99 | '比赛经历'}> 100 | 101 | {team.match} 102 | 103 | 104 | '主要荣耀'}> 105 | 106 | {team.honor} 107 | 108 | 109 | 110 | 111 | 112 | ) 113 | } 114 | } 115 | 116 | const mapStateToProps = (state, ownProps) => { 117 | return { 118 | current: state.team.home.team.current, 119 | team: state.team.home.team.list.filter( 120 | x => x.objectId === ownProps.navigation.state.params.objectId 121 | )[0] 122 | } 123 | } 124 | 125 | const mapDispatchToProps = (dispatch, ownProps) => { 126 | return { 127 | getTeamById: payload => { 128 | dispatch(getHomeTeamDetailRequest(payload)) 129 | } 130 | } 131 | } 132 | 133 | HomeTeamDetail.propTypes = { 134 | current: PropTypes.object, 135 | team: PropTypes.object 136 | } 137 | 138 | export default connect(mapStateToProps, mapDispatchToProps)(HomeTeamDetail) 139 | -------------------------------------------------------------------------------- /src/services/leanclound/user.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage' 2 | import { getAvatar, getNickName } from '../../utils/utils' 3 | 4 | // 用户名和密码注册 5 | export function signUp(payload) { 6 | const { username, password, email } = payload 7 | const avatar = getAvatar() 8 | const nickname = getNickName() 9 | const user = new AV.User() 10 | user.setUsername(username) 11 | user.setPassword(password) 12 | user.setEmail(email) 13 | return user.signUp().then(function(loginedUser) { 14 | if (!loginedUser.get('userinfo')) { 15 | const Userinfo = AV.Object.extend('UserInfo') 16 | const userinfo = new Userinfo() 17 | userinfo.set('nickname', nickname) 18 | userinfo.set('contact', email) 19 | userinfo.set('avatar', avatar) 20 | userinfo.set('avatar', avatar) 21 | userinfo.set('isPublic', true) 22 | userinfo.set('show', 1) 23 | userinfo.set('stick', 0) 24 | userinfo.set('teamLimit', 1) 25 | userinfo.set('groupOrderLimit', 1) 26 | userinfo.set('recruitOrderLimit', 1) 27 | userinfo.set('resumeOrderLimit', 1) 28 | userinfo.set('warOrderLimit', 1) 29 | userinfo.set('introduction', '这个世界需要更多的英雄') 30 | loginedUser.set('userinfo', userinfo) 31 | return loginedUser.save().then(function(result) { 32 | return result.toJSON() 33 | }) 34 | } 35 | }) 36 | } 37 | 38 | // 手机号码注册 39 | export function signUpBySmsCode(payload) { 40 | const { phone, code } = payload 41 | return AV.User.signUpOrlogInWithMobilePhone(phone, code) 42 | } 43 | 44 | // 第三方账号登录 45 | export function signUpOrlogInWithAuthData(payload) {} 46 | 47 | // 用户名和密码登录 48 | export function logIn(payload) { 49 | const { username, password } = payload 50 | return AV.User.logIn(username, password).then(function(result) { 51 | return result.toJSON() 52 | }) 53 | } 54 | 55 | // 手机号和密码登录 56 | export function logInWithMobilePhone(payload) { 57 | const { phone, password } = payload 58 | return AV.User.logInWithMobilePhone(phone, password) 59 | } 60 | 61 | // 手机号和验证码登录 62 | export function requestLoginSmsCode(payload) { 63 | const { phone, code } = payload 64 | return AV.User.logInWithMobilePhoneSmsCode(phone, code) 65 | } 66 | 67 | // 当前用户 68 | export function getCurrentUserAsync() { 69 | console.log('service getCurrentUserAsync'); 70 | return AV.User.currentAsync() 71 | } 72 | 73 | // 验证 SessionToken 是否在有效期内 74 | export function isAuthenticated() { 75 | // var currentUser = AV.User.currentAsync() 76 | // return currentUser.isAuthenticated() 77 | } 78 | 79 | // 使用 SessionToken 登录 80 | // 登录后可以调用 user.getSessionToken() 方法得到当前登录用户的 sessionToken 81 | export function logInWithSessionToken(payload) { 82 | const { sessionToken } = payload 83 | return AV.User.become(sessionToken) 84 | } 85 | 86 | // 登出 87 | export function logOut() { 88 | return AV.User.logOut() 89 | // 现在的 currentUser 是 null 了 90 | // var currentUser = AV.User.currentAsync() 91 | } 92 | 93 | export function putUserInfo(payload) { 94 | const userinfo = AV.Object.createWithoutData('UserInfo', payload.objectId) 95 | for (let key of Object.keys(payload)) { 96 | if (key !== 'objectId') { 97 | userinfo.set(key, payload[key]) 98 | } 99 | } 100 | return userinfo.save().then(function(result) { 101 | return result.toJSON() 102 | }) 103 | } 104 | 105 | export function getUserInfoToJson(currentUser) { 106 | const user = new AV.Query('_User') 107 | user.equalTo('objectId', currentUser.id) 108 | user.include('userinfo') 109 | return user.first().then(function(result) { 110 | return result.get('userinfo').toJSON() 111 | }) 112 | } 113 | 114 | export function getHomeUserInfoList(payload) { 115 | let list = [] 116 | let { page, pagesize } = payload 117 | pagesize = pagesize || 20 118 | const query = new AV.Query('UserInfo') 119 | query.equalTo('isPublic', true) 120 | query.equalTo('show', 1) 121 | query.descending('stick') 122 | query.descending('createdAt') 123 | query.limit(pagesize) 124 | query.skip(pagesize * (page - 1)) 125 | return query.find().then(function(result) { 126 | result.forEach(item => { 127 | list.push(item.toJSON()) 128 | }) 129 | return list 130 | }) 131 | } 132 | 133 | export function getHomeUserInfoDetail(payload) { 134 | const { objectId } = payload 135 | const query = new AV.Query('UserInfo') 136 | query.equalTo('objectId', objectId) 137 | return query.first().then(function(result) { 138 | return result.toJSON() 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /src/sagas/groupOrderSaga.js: -------------------------------------------------------------------------------- 1 | import { put, fork, take, call } from 'redux-saga/effects' 2 | import { delay } from 'redux-saga' 3 | import { Toast } from 'antd-mobile' 4 | import { NavigationActions } from 'react-navigation' 5 | import { 6 | GET_HOME_GROUP_ORDER_LIST_REQUEST, 7 | GET_ACCOUNT_GROUP_ORDER_LIST_REQUEST, 8 | POST_GROUP_ORDER_REQUEST, 9 | PUT_GROUP_ORDER_REQUEST, 10 | DELETE_GROUP_ORDER_REQUEST 11 | } from '../constants/actionTypes' 12 | import * as action from '../actions' 13 | import { groupOrderService, userService } from '../services/leanclound' 14 | 15 | function* postGroupOrderWorker(payload) { 16 | try { 17 | Toast.loading('提交中') 18 | const currentUser = yield call(userService.getCurrentUserAsync) 19 | const userinfo = yield call(userService.getUserInfoToJson, currentUser) 20 | const count = yield call( 21 | groupOrderService.getGroupOrderCountOfToday, 22 | currentUser 23 | ) 24 | const groupOrderLimit = userinfo.groupOrderLimit 25 | if (count < groupOrderLimit) { 26 | const response = yield call( 27 | groupOrderService.cerateGroupOrder, 28 | payload, 29 | userinfo, 30 | currentUser 31 | ) 32 | yield put(action.postGroupOrderSuccess(response)) 33 | Toast.success('提交成功', 1) 34 | yield delay(1000) 35 | yield put(NavigationActions.back()) 36 | } else { 37 | yield put(action.putGroupOrderFailed()) 38 | Toast.fail(`1天最多发布${groupOrderLimit}条组队上分帖`, 2) 39 | yield delay(2000) 40 | yield put(NavigationActions.back()) 41 | } 42 | } catch (error) { 43 | yield put(action.postGroupOrderFailed(error)) 44 | Toast.fail('提交失败', 1) 45 | } 46 | } 47 | 48 | function* putGroupOrderWorker(payload) { 49 | try { 50 | Toast.loading('提交中') 51 | const response = yield call( 52 | groupOrderService.updateGroupOrder, 53 | payload, 54 | ) 55 | yield put(action.putGroupOrderSuccess(response)) 56 | Toast.success('提交成功', 1) 57 | yield delay(1000) 58 | yield put(NavigationActions.back()) 59 | } catch (error) { 60 | yield put(action.putGroupOrderFailed(error)) 61 | Toast.fail('提交失败', 1) 62 | } 63 | } 64 | 65 | function* deleteGroupOrderWorker(payload) { 66 | try { 67 | Toast.loading('提交中') 68 | const response = yield call(groupOrderService.removeGroupOrder, payload) 69 | yield put(action.deleteGroupOrderSuccess(response)) 70 | Toast.success('删除成功', 1) 71 | } catch (error) { 72 | yield put(action.deleteGroupOrderFailed(error)) 73 | Toast.fail('删除失败', 1) 74 | } 75 | } 76 | 77 | function* getAccountGroupOrderListWorker(payload) { 78 | try { 79 | Toast.loading('加载中') 80 | const currentUser = yield call(userService.getCurrentUserAsync) 81 | const response = yield call( 82 | groupOrderService.getAccountGroupOrderList, 83 | payload, 84 | currentUser 85 | ) 86 | yield put(action.getAccountGroupOrderListSuccess(response)) 87 | Toast.hide() 88 | } catch (error) { 89 | yield put(action.getAccountGroupOrderListFailed(error)) 90 | Toast.hide() 91 | } 92 | } 93 | 94 | function* getHomeGroupOrderListWorker(payload) { 95 | try { 96 | Toast.loading('加载中') 97 | const response = yield call( 98 | groupOrderService.getHomeGroupOrderList, 99 | payload 100 | ) 101 | yield put(action.getHomeGroupOrderListSuccess(response)) 102 | Toast.hide() 103 | } catch (error) { 104 | yield put(action.getHomeGroupOrderListFailed(error)) 105 | Toast.hide() 106 | } 107 | } 108 | 109 | function* watchPostGroupOrder() { 110 | while (true) { 111 | const { payload } = yield take(POST_GROUP_ORDER_REQUEST) 112 | yield fork(postGroupOrderWorker, payload) 113 | } 114 | } 115 | 116 | function* watchGetAccountGroupOrderList() { 117 | while (true) { 118 | const { payload } = yield take(GET_ACCOUNT_GROUP_ORDER_LIST_REQUEST) 119 | yield fork(getAccountGroupOrderListWorker, payload) 120 | } 121 | } 122 | 123 | function* watchGetHomeGroupOrderList() { 124 | while (true) { 125 | const { payload } = yield take(GET_HOME_GROUP_ORDER_LIST_REQUEST) 126 | yield fork(getHomeGroupOrderListWorker, payload) 127 | } 128 | } 129 | 130 | function* watchPutGroupOrder() { 131 | while (true) { 132 | const { payload } = yield take(PUT_GROUP_ORDER_REQUEST) 133 | yield fork(putGroupOrderWorker, payload) 134 | } 135 | } 136 | 137 | function* watchDeleteGroupOrder() { 138 | while (true) { 139 | const { payload } = yield take(DELETE_GROUP_ORDER_REQUEST) 140 | yield fork(deleteGroupOrderWorker, payload) 141 | } 142 | } 143 | 144 | export { 145 | watchGetHomeGroupOrderList, 146 | watchPostGroupOrder, 147 | watchGetAccountGroupOrderList, 148 | watchPutGroupOrder, 149 | watchDeleteGroupOrder 150 | } 151 | -------------------------------------------------------------------------------- /src/sagas/resumeOrderSaga.js: -------------------------------------------------------------------------------- 1 | import { put, fork, take, call } from 'redux-saga/effects' 2 | import { delay } from 'redux-saga' 3 | import { Toast } from 'antd-mobile' 4 | import { NavigationActions } from 'react-navigation' 5 | import { 6 | GET_HOME_RESUME_ORDER_LIST_REQUEST, 7 | GET_ACCOUNT_RESUME_ORDER_LIST_REQUEST, 8 | POST_RESUME_ORDER_REQUEST, 9 | PUT_RESUME_ORDER_REQUEST, 10 | DELETE_RESUME_ORDER_REQUEST 11 | } from '../constants/actionTypes' 12 | import * as action from '../actions' 13 | import { resumeOrderService, userService } from '../services/leanclound' 14 | 15 | function* postResumeOrderWorker(payload) { 16 | try { 17 | Toast.loading('提交中') 18 | const currentUser = yield call(userService.getCurrentUserAsync) 19 | const userinfo = yield call(userService.getUserInfoToJson, currentUser) 20 | const count = yield call( 21 | resumeOrderService.getResumeOrderCountOfToday, 22 | currentUser 23 | ) 24 | const resumeOrderLimit = userinfo.resumeOrderLimit 25 | if (count < resumeOrderLimit) { 26 | const response = yield call( 27 | resumeOrderService.cerateResumeOrder, 28 | payload, 29 | userinfo, 30 | currentUser 31 | ) 32 | yield put(action.postResumeOrderSuccess(response)) 33 | Toast.success('提交成功', 1) 34 | yield delay(1000) 35 | yield put(NavigationActions.back()) 36 | } else { 37 | yield put(action.postResumeOrderFailed()) 38 | Toast.fail(`1天最多发布${resumeOrderLimit}条寻找战队帖`, 2) 39 | yield delay(2000) 40 | yield put(NavigationActions.back()) 41 | } 42 | } catch (error) { 43 | yield put(action.postResumeOrderFailed(error)) 44 | Toast.fail('提交失败', 1) 45 | } 46 | } 47 | 48 | function* putResumeOrderWorker(payload) { 49 | try { 50 | Toast.loading('提交中') 51 | const response = yield call( 52 | resumeOrderService.updateResumeOrder, 53 | payload, 54 | ) 55 | yield put(action.putResumeOrderSuccess(response)) 56 | Toast.success('提交成功', 1) 57 | yield delay(1000) 58 | yield put(NavigationActions.back()) 59 | } catch (error) { 60 | yield put(action.putResumeOrderFailed(error)) 61 | Toast.fail('提交失败', 1) 62 | } 63 | } 64 | 65 | function* deleteResumeOrderWorker(payload) { 66 | try { 67 | Toast.loading('提交中') 68 | const response = yield call(resumeOrderService.removeResumeOrder, payload) 69 | yield put(action.deleteResumeOrderSuccess(response)) 70 | Toast.success('删除成功', 1) 71 | } catch (error) { 72 | yield put(action.deleteResumeOrderFailed(error)) 73 | Toast.fail('删除失败', 1) 74 | } 75 | } 76 | 77 | function* getAccountResumeOrderListWorker(payload) { 78 | try { 79 | Toast.loading('加载中') 80 | const currentUser = yield call(userService.getCurrentUserAsync) 81 | const response = yield call( 82 | resumeOrderService.getAccountResumeOrderList, 83 | payload, 84 | currentUser 85 | ) 86 | yield put(action.getAccountResumeOrderListSuccess(response)) 87 | Toast.hide() 88 | } catch (error) { 89 | yield put(action.getAccountResumeOrderListFailed(error)) 90 | Toast.hide() 91 | } 92 | } 93 | 94 | function* getHomeResumeOrderListWorker(payload) { 95 | try { 96 | Toast.loading('加载中') 97 | const response = yield call( 98 | resumeOrderService.getHomeResumeOrderList, 99 | payload 100 | ) 101 | yield put(action.getHomeResumeOrderListSuccess(response)) 102 | Toast.hide() 103 | } catch (error) { 104 | yield put(action.getHomeResumeOrderListFailed(error)) 105 | Toast.hide() 106 | } 107 | } 108 | 109 | function* watchPostResumeOrder() { 110 | while (true) { 111 | const { payload } = yield take(POST_RESUME_ORDER_REQUEST) 112 | yield fork(postResumeOrderWorker, payload) 113 | } 114 | } 115 | 116 | function* watchGetAccountResumeOrderList() { 117 | while (true) { 118 | const { payload } = yield take(GET_ACCOUNT_RESUME_ORDER_LIST_REQUEST) 119 | yield fork(getAccountResumeOrderListWorker, payload) 120 | } 121 | } 122 | 123 | function* watchGetHomeResumeOrderList() { 124 | while (true) { 125 | const { payload } = yield take(GET_HOME_RESUME_ORDER_LIST_REQUEST) 126 | yield fork(getHomeResumeOrderListWorker, payload) 127 | } 128 | } 129 | 130 | function* watchPutResumeOrder() { 131 | while (true) { 132 | const { payload } = yield take(PUT_RESUME_ORDER_REQUEST) 133 | yield fork(putResumeOrderWorker, payload) 134 | } 135 | } 136 | 137 | function* watchDeleteResumeOrder() { 138 | while (true) { 139 | const { payload } = yield take(DELETE_RESUME_ORDER_REQUEST) 140 | yield fork(deleteResumeOrderWorker, payload) 141 | } 142 | } 143 | 144 | export { 145 | watchGetHomeResumeOrderList, 146 | watchPostResumeOrder, 147 | watchGetAccountResumeOrderList, 148 | watchPutResumeOrder, 149 | watchDeleteResumeOrder 150 | } 151 | -------------------------------------------------------------------------------- /src/screens/Account/Teams/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { ScrollView, View } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { connect } from 'react-redux' 5 | import { 6 | Button, 7 | WhiteSpace, 8 | WingBlank, 9 | Card, 10 | Modal, 11 | List, 12 | } from 'antd-mobile' 13 | import { NavigationActions } from 'react-navigation' 14 | import _ from 'lodash' 15 | import { getMyTeamsRequest, deleteTeamRequest } from '../../../actions' 16 | import { cutstr } from '../../../utils/utils' 17 | import { CardStyle } from '../../../components/CustomStyles' 18 | 19 | class AccountTeams extends Component { 20 | constructor(props) { 21 | super(props) 22 | this.state = { 23 | modal: false, 24 | teamid: '' 25 | } 26 | this.onCreateTeam = this.onCreateTeam.bind(this) 27 | } 28 | 29 | onCreateTeam() { 30 | if (!_.isEmpty(this.props.user)) { 31 | if (this.props.teams.length < this.props.user.teamLimit) { 32 | this.props.navigateTo('AccountTeamsCreate') 33 | } else { 34 | Modal.alert( 35 | '提示', 36 | '每位用户最多可创建一支战队,若想创建多支战队,请联系管理员963577494@qq.com', 37 | [{ text: '确定', onPress: () => console.log('success') }] 38 | ) 39 | } 40 | } else { 41 | console.log('登录') 42 | } 43 | } 44 | 45 | onRemoveTeam = id => e => { 46 | Modal.alert('警告', '是否解散该队伍?', [ 47 | { text: '取消', onPress: () => null }, 48 | { text: '确定', onPress: () => this.props.deleteTeam({ teamid: id }) } 49 | ]) 50 | } 51 | 52 | componentDidMount() { 53 | if (this.props.teams.length === 0) { 54 | this.props.getMyTeams() 55 | } 56 | } 57 | 58 | render() { 59 | const { teams, navigateTo } = this.props 60 | return ( 61 | 62 | {teams.map((item, index) => ( 63 | 64 | 65 | 66 | { 84 | navigateTo('AccountTeamsEdit', { 85 | objectId: item.objectId 86 | }) 87 | }} 88 | type="ghost" 89 | size="small" 90 | inline 91 | > 92 | 编辑 93 | 94 | } 95 | /> 96 | 97 | 98 | 99 | {cutstr(item.introduction, 200, 0)} 100 | 101 | 102 | 103 | 112 | 解散 113 | 114 | } 115 | /> 116 | 117 | 118 | ))} 119 | 120 | 121 | 124 | 125 | 126 | ) 127 | } 128 | } 129 | 130 | const mapStateToProps = (state, ownProps) => { 131 | return { 132 | teams: state.team.account.team.myTeams, 133 | user: state.user.account.user 134 | } 135 | } 136 | 137 | const mapDispatchToProps = (dispatch, ownProps) => { 138 | return { 139 | getMyTeams: () => { 140 | dispatch(getMyTeamsRequest()) 141 | }, 142 | deleteTeam: payload => { 143 | dispatch(deleteTeamRequest(payload)) 144 | }, 145 | navigateTo: (path, params) => { 146 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 147 | } 148 | } 149 | } 150 | 151 | AccountTeams.propTypes = { 152 | teams: PropTypes.array, 153 | getMyTeams: PropTypes.func, 154 | navigateTo: PropTypes.func 155 | } 156 | 157 | export default connect(mapStateToProps, mapDispatchToProps)(AccountTeams) 158 | -------------------------------------------------------------------------------- /src/sagas/warOrderSaga.js: -------------------------------------------------------------------------------- 1 | import { put, fork, take, call } from 'redux-saga/effects' 2 | import { delay } from 'redux-saga' 3 | import { Toast } from 'antd-mobile' 4 | import { NavigationActions } from 'react-navigation' 5 | import { 6 | GET_HOME_WAR_ORDER_LIST_REQUEST, 7 | GET_ACCOUNT_WAR_ORDER_LIST_REQUEST, 8 | POST_WAR_ORDER_REQUEST, 9 | PUT_WAR_ORDER_REQUEST, 10 | DELETE_WAR_ORDER_REQUEST 11 | } from '../constants/actionTypes' 12 | import * as action from '../actions' 13 | import { 14 | warOrderService, 15 | teamsService, 16 | userService 17 | } from '../services/leanclound' 18 | 19 | function* postWarOrderWorker(payload) { 20 | try { 21 | Toast.loading('提交中') 22 | const currentUser = yield call(userService.getCurrentUserAsync) 23 | const userinfo = yield call(userService.getUserInfoToJson, currentUser) 24 | const count = yield call( 25 | warOrderService.getWarOrderCountOfToday, 26 | currentUser 27 | ) 28 | const warOrderLimit = userinfo.warOrderLimit 29 | if (count < warOrderLimit) { 30 | const team = yield call(teamsService.getTeamToJson, payload) 31 | const response = yield call( 32 | warOrderService.cerateWarOrder, 33 | payload, 34 | team, 35 | currentUser 36 | ) 37 | yield put(action.postWarOrderSuccess(response)) 38 | Toast.success('提交成功', 1) 39 | yield delay(1000) 40 | yield put(NavigationActions.back()) 41 | } else { 42 | yield put(action.postWarOrderFailed()) 43 | Toast.fail(`1天最多发布${warOrderLimit}条比赛约战帖`, 2) 44 | yield delay(2000) 45 | yield put(NavigationActions.back()) 46 | } 47 | } catch (error) { 48 | yield put(action.postWarOrderFailed(error)) 49 | Toast.fail('提交失败', 1) 50 | } 51 | } 52 | 53 | function* putWarOrderWorker(payload) { 54 | try { 55 | Toast.loading('提交中') 56 | const currentUser = yield call(userService.getCurrentUserAsync) 57 | const team = yield call(teamsService.getTeam, payload) 58 | const response = yield call(warOrderService.updateWarOrder, payload, team) 59 | yield put(action.putWarOrderSuccess(response)) 60 | Toast.success('提交成功', 1) 61 | yield delay(1000) 62 | yield put(NavigationActions.back()) 63 | } catch (error) { 64 | yield put(action.putWarOrderFailed(error)) 65 | Toast.fail('提交失败', 1) 66 | } 67 | } 68 | 69 | function* deleteWarOrderWorker(payload) { 70 | try { 71 | Toast.loading('提交中') 72 | const response = yield call(warOrderService.removeWarOrder, payload) 73 | yield put(action.deleteWarOrderSuccess(response)) 74 | yield put(action.fetchSuccess()) 75 | Toast.success('删除成功', 1) 76 | } catch (error) { 77 | yield put(action.deleteWarOrderFailed(error)) 78 | yield put(action.fetchFailed()) 79 | Toast.fail('删除失败', 1) 80 | } 81 | } 82 | 83 | function* getAccountWarOrderListWorker(payload) { 84 | try { 85 | Toast.loading('加载中') 86 | const currentUser = yield call(userService.getCurrentUserAsync) 87 | const response = yield call( 88 | warOrderService.getAccountWarOrderList, 89 | payload, 90 | currentUser 91 | ) 92 | yield put(action.getAccountWarOrderListSuccess(response)) 93 | Toast.hide() 94 | } catch (error) { 95 | yield put(action.getAccountWarOrderListFailed(error)) 96 | Toast.hide() 97 | } 98 | } 99 | 100 | function* getHomeWarOrderListWorker(payload) { 101 | try { 102 | Toast.loading('加载中') 103 | const response = yield call(warOrderService.getHomeWarOrderList, payload) 104 | yield put(action.getHomeWarOrderListSuccess(response)) 105 | Toast.hide() 106 | } catch (error) { 107 | yield put(action.getHomeWarOrderListFailed(error)) 108 | Toast.hide() 109 | } 110 | } 111 | 112 | function* watchPostWarOrder() { 113 | while (true) { 114 | const { payload } = yield take(POST_WAR_ORDER_REQUEST) 115 | yield fork(postWarOrderWorker, payload) 116 | } 117 | } 118 | 119 | function* watchGetAccountWarOrderList() { 120 | while (true) { 121 | const { payload } = yield take(GET_ACCOUNT_WAR_ORDER_LIST_REQUEST) 122 | yield fork(getAccountWarOrderListWorker, payload) 123 | } 124 | } 125 | 126 | function* watchGetHomeWarOrderList() { 127 | while (true) { 128 | const { payload } = yield take(GET_HOME_WAR_ORDER_LIST_REQUEST) 129 | yield fork(getHomeWarOrderListWorker, payload) 130 | } 131 | } 132 | 133 | function* watchPutWarOrder() { 134 | while (true) { 135 | const { payload } = yield take(PUT_WAR_ORDER_REQUEST) 136 | yield fork(putWarOrderWorker, payload) 137 | } 138 | } 139 | 140 | function* watchDeleteWarOrder() { 141 | while (true) { 142 | const { payload } = yield take(DELETE_WAR_ORDER_REQUEST) 143 | yield fork(deleteWarOrderWorker, payload) 144 | } 145 | } 146 | 147 | export { 148 | watchGetHomeWarOrderList, 149 | watchPostWarOrder, 150 | watchGetAccountWarOrderList, 151 | watchPutWarOrder, 152 | watchDeleteWarOrder 153 | } 154 | -------------------------------------------------------------------------------- /src/screens/Home/UserInfos/Detail/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Text, ScrollView, View, Image } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { connect } from 'react-redux' 5 | import { WhiteSpace, List, Result } from 'antd-mobile' 6 | import { RANKS, TEAMPOSITIONS } from '../../../../constants' 7 | import { getHomeUserInfoDetailRequest } from '../../../../actions' 8 | import config from '../../../../config' 9 | 10 | class HomeUserInfoDetail extends Component { 11 | componentDidMount() { 12 | if (!this.props.userinfo) { 13 | const id = this.props.navigation.state.params.objectId 14 | this.props.getUserById({ objectId: id }) 15 | } 16 | } 17 | 18 | render() { 19 | let { userinfo, current } = this.props 20 | if (userinfo == null && current != null) { 21 | userinfo = current 22 | } 23 | return ( 24 | 25 | 26 | 33 | ) : ( 34 | 38 | ) 39 | } 40 | title={userinfo.nickname} 41 | message={userinfo.introduction} 42 | /> 43 | 44 | 45 | 48 | 天梯分 49 | 50 | x.value === userinfo.rank)[0].label 54 | : '未知' 55 | } 56 | > 57 | 天梯段位 58 | 59 | x.value === userinfo.position)[0] 63 | .label 64 | : '未知' 65 | } 66 | > 67 | 团队定位 68 | 69 | 70 | '擅长英雄'}> 71 | 78 | {userinfo.heros ? ( 79 | userinfo.heros.map((item, index) => { 80 | return ( 81 | 91 | ) 92 | }) 93 | ) : ( 94 | 103 | )} 104 | 105 | 106 | '个人比赛经历'}> 107 | 108 | {userinfo.match} 109 | 110 | 111 | '其他'}> 112 | 113 | 鼠标 114 | 115 | 116 | 键盘 117 | 118 | 119 | 耳机 120 | 121 | 122 | 123 | 124 | ) 125 | } 126 | } 127 | 128 | const mapStateToProps = (state, ownProps) => { 129 | return { 130 | current: state.user.home.userinfo.current, 131 | userinfo: state.user.home.userinfo.list.filter( 132 | x => x.objectId === ownProps.navigation.state.params.objectId 133 | )[0] 134 | } 135 | } 136 | 137 | const mapDispatchToProps = (dispatch, ownProps) => { 138 | return { 139 | getUserById: payload => { 140 | dispatch(getHomeUserInfoDetailRequest(payload)) 141 | } 142 | } 143 | } 144 | 145 | HomeUserInfoDetail.propTypes = { 146 | current: PropTypes.object, 147 | user: PropTypes.object, 148 | getUserById: PropTypes.func 149 | } 150 | 151 | export default connect(mapStateToProps, mapDispatchToProps)(HomeUserInfoDetail) 152 | -------------------------------------------------------------------------------- /src/sagas/recruitOrderSaga.js: -------------------------------------------------------------------------------- 1 | import { put, fork, take, call } from 'redux-saga/effects' 2 | import { delay } from 'redux-saga' 3 | import { Toast } from 'antd-mobile' 4 | import { NavigationActions } from 'react-navigation' 5 | import { 6 | GET_HOME_RECRUIT_ORDER_LIST_REQUEST, 7 | GET_ACCOUNT_RECRUIT_ORDER_LIST_REQUEST, 8 | POST_RECRUIT_ORDER_REQUEST, 9 | PUT_RECRUIT_ORDER_REQUEST, 10 | DELETE_RECRUIT_ORDER_REQUEST 11 | } from '../constants/actionTypes' 12 | import * as action from '../actions' 13 | import { 14 | recruitOrderService, 15 | teamsService, 16 | userService 17 | } from '../services/leanclound' 18 | 19 | function* postRecruitOrderWorker(payload) { 20 | try { 21 | Toast.loading('提交中') 22 | const currentUser = yield call(userService.getCurrentUserAsync) 23 | const userinfo = yield call(userService.getUserInfoToJson, currentUser) 24 | const count = yield call( 25 | recruitOrderService.getRecruitOrderCountOfToday, 26 | currentUser 27 | ) 28 | const recruitOrderLimit = userinfo.recruitOrderLimit 29 | if (count < recruitOrderLimit) { 30 | const team = yield call(teamsService.getTeamToJson, payload) 31 | const response = yield call( 32 | recruitOrderService.cerateRecruitOrder, 33 | payload, 34 | team, 35 | currentUser 36 | ) 37 | yield put(action.postRecruitOrderSuccess(response)) 38 | Toast.success('提交成功', 1) 39 | yield delay(1000) 40 | yield put(NavigationActions.back()) 41 | } else { 42 | yield put(action.postRecruitOrderFailed()) 43 | Toast.fail(`1天最多发布${recruitOrderLimit}条战队招募令`, 2) 44 | yield delay(2000) 45 | yield put(NavigationActions.back()) 46 | } 47 | } catch (error) { 48 | yield put(action.postRecruitOrderFailed(error)) 49 | Toast.fail('提交失败', 1) 50 | } 51 | } 52 | 53 | function* putRecruitOrderWorker(payload) { 54 | try { 55 | Toast.loading('提交中') 56 | const currentUser = yield call(userService.getCurrentUserAsync) 57 | const team = yield call(teamsService.getTeam, payload) 58 | const response = yield call( 59 | recruitOrderService.updateRecruitOrder, 60 | payload, 61 | team 62 | ) 63 | yield put(action.putRecruitOrderSuccess(response)) 64 | Toast.success('提交成功', 1) 65 | yield delay(1000) 66 | yield put(NavigationActions.back()) 67 | } catch (error) { 68 | yield put(action.putRecruitOrderFailed(error)) 69 | Toast.fail('提交失败', 1) 70 | } 71 | } 72 | 73 | function* deleteRecruitOrderWorker(payload) { 74 | try { 75 | Toast.loading('提交中') 76 | const response = yield call(recruitOrderService.removeRecruitOrder, payload) 77 | yield put(action.deleteRecruitOrderSuccess(response)) 78 | Toast.success('删除成功', 1) 79 | } catch (error) { 80 | yield put(action.deleteRecruitOrderFailed(error)) 81 | Toast.fail('删除失败', 1) 82 | } 83 | } 84 | 85 | function* getAccountRecruitOrderListWorker(payload) { 86 | try { 87 | Toast.loading('加载中') 88 | const currentUser = yield call(userService.getCurrentUserAsync) 89 | const response = yield call( 90 | recruitOrderService.getAccountRecruitOrderList, 91 | payload, 92 | currentUser 93 | ) 94 | yield put(action.getAccountRecruitOrderListSuccess(response)) 95 | Toast.hide() 96 | } catch (error) { 97 | yield put(action.getAccountRecruitOrderListFailed(error)) 98 | Toast.hide() 99 | } 100 | } 101 | 102 | function* getHomeRecruitOrderListWorker(payload) { 103 | try { 104 | Toast.loading('加载中') 105 | const response = yield call( 106 | recruitOrderService.getHomeRecruitOrderList, 107 | payload 108 | ) 109 | yield put(action.getHomeRecruitOrderListSuccess(response)) 110 | Toast.hide() 111 | } catch (error) { 112 | yield put(action.getHomeRecruitOrderListFailed(error)) 113 | Toast.hide() 114 | } 115 | } 116 | 117 | function* watchPostRecruitOrder() { 118 | while (true) { 119 | const { payload } = yield take(POST_RECRUIT_ORDER_REQUEST) 120 | yield fork(postRecruitOrderWorker, payload) 121 | } 122 | } 123 | 124 | function* watchGetAccountRecruitOrderList() { 125 | while (true) { 126 | const { payload } = yield take(GET_ACCOUNT_RECRUIT_ORDER_LIST_REQUEST) 127 | yield fork(getAccountRecruitOrderListWorker, payload) 128 | } 129 | } 130 | 131 | function* watchGetHomeRecruitOrderList() { 132 | while (true) { 133 | const { payload } = yield take(GET_HOME_RECRUIT_ORDER_LIST_REQUEST) 134 | yield fork(getHomeRecruitOrderListWorker, payload) 135 | } 136 | } 137 | 138 | function* watchPutRecruitOrder() { 139 | while (true) { 140 | const { payload } = yield take(PUT_RECRUIT_ORDER_REQUEST) 141 | yield fork(putRecruitOrderWorker, payload) 142 | } 143 | } 144 | 145 | function* watchDeleteRecruitOrder() { 146 | while (true) { 147 | const { payload } = yield take(DELETE_RECRUIT_ORDER_REQUEST) 148 | yield fork(deleteRecruitOrderWorker, payload) 149 | } 150 | } 151 | 152 | export { 153 | watchGetHomeRecruitOrderList, 154 | watchPostRecruitOrder, 155 | watchGetAccountRecruitOrderList, 156 | watchPutRecruitOrder, 157 | watchDeleteRecruitOrder 158 | } 159 | -------------------------------------------------------------------------------- /src/screens/Home/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { 3 | View, 4 | Image, 5 | Text, 6 | StyleSheet, 7 | TouchableWithoutFeedback, 8 | ScrollView, 9 | StatusBar 10 | } from 'react-native' 11 | import PropTypes from 'prop-types' 12 | import { connect } from 'react-redux' 13 | import { Flex, WhiteSpace, Grid, List } from 'antd-mobile' 14 | import { NavigationActions } from 'react-navigation' 15 | import { 16 | getHomeGroupOrderListRequest, 17 | getHomeRecruitOrderListRequest 18 | } from '../../actions' 19 | import { HomeGroupCard, HomeRecruitCard } from '../../components' 20 | 21 | const data = [ 22 | { 23 | path: 'HomeResumeOrders', 24 | icon: require('../../../assets/images/home_icon.png'), 25 | text: '寻找战队' 26 | }, 27 | { 28 | path: 'HomeRecruitOrders', 29 | icon: require('../../../assets/images/home_icon.png'), 30 | text: '战队招募' 31 | }, 32 | { 33 | path: 'HomeWarOrders', 34 | icon: require('../../../assets/images/home_icon.png'), 35 | text: '比赛约战' 36 | }, 37 | { 38 | path: 'HomeGroupOrders', 39 | icon: require('../../../assets/images/home_icon.png'), 40 | text: '组队上分' 41 | }, 42 | { 43 | path: 'HomeTeams', 44 | icon: require('../../../assets/images/home_icon.png'), 45 | text: '战队列表' 46 | }, 47 | { 48 | path: 'HomeUserInfos', 49 | icon: require('../../../assets/images/home_icon.png'), 50 | text: '个人列表' 51 | } 52 | ] 53 | 54 | class Home extends Component { 55 | static propTypes = { 56 | navigateTo: PropTypes.func, 57 | groupOrder: PropTypes.object, 58 | recruitOrder: PropTypes.object, 59 | getHomeGroupOrderList: PropTypes.func, 60 | getHomeRecruitOrderList: PropTypes.func 61 | } 62 | 63 | _onPressButton = path => e => { 64 | this.props.navigateTo(path) 65 | } 66 | 67 | componentDidMount() { 68 | if (this.props.groupOrder.list.length === 0) { 69 | this.props.getHomeGroupOrderList({ page: 1 }) 70 | } 71 | if (this.props.recruitOrder.list.length === 0) { 72 | this.props.getHomeRecruitOrderList({ page: 1 }) 73 | } 74 | } 75 | 76 | render() { 77 | const { navigateTo, groupOrder, recruitOrder } = this.props 78 | return ( 79 | 80 | 81 | 82 | 83 | 84 | 88 | 89 | 90 | 这个世界需要更多的英雄 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ( 103 | 106 | 107 | 111 | {dataItem.text} 112 | 113 | 114 | )} 115 | /> 116 | 117 | 118 | '组队上分'}> 119 | {groupOrder.list.slice(0, 3).map((item, index) => { 120 | return ( 121 | 126 | ) 127 | })} 128 | 129 | '战队招募'}> 130 | {recruitOrder.list.slice(0, 3).map((item, index) => { 131 | return ( 132 | 137 | ) 138 | })} 139 | 140 | 141 | ) 142 | } 143 | } 144 | 145 | const styles = StyleSheet.create({ 146 | headerer: { 147 | height: 180, 148 | backgroundColor: '#fff' 149 | }, 150 | grid: { 151 | backgroundColor: '#fff' 152 | } 153 | }) 154 | 155 | const mapStateToProps = state => ({ 156 | groupOrder: state.groupOrder.home.groupOrder, 157 | recruitOrder: state.recruitOrder.home.recruitOrder 158 | }) 159 | 160 | const mapDispatchToProps = dispatch => ({ 161 | getHomeGroupOrderList: payload => { 162 | dispatch(getHomeGroupOrderListRequest(payload)) 163 | }, 164 | getHomeRecruitOrderList: payload => { 165 | dispatch(getHomeRecruitOrderListRequest(payload)) 166 | }, 167 | navigateTo: (path, params) => { 168 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 169 | } 170 | }) 171 | 172 | export default connect(mapStateToProps, mapDispatchToProps)(Home) 173 | -------------------------------------------------------------------------------- /src/screens/SignUp/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Text, View, StyleSheet } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { connect } from 'react-redux' 5 | import { createForm } from 'rc-form' 6 | import { 7 | Button, 8 | InputItem, 9 | WhiteSpace, 10 | Flex, 11 | WingBlank, 12 | } from 'antd-mobile' 13 | import { postSignUpRequest } from '../../actions' 14 | 15 | class SignUp extends Component { 16 | constructor(props) { 17 | super(props) 18 | this.state = { 19 | username: '', 20 | password: '', 21 | email: '' 22 | } 23 | this.onUserNameChange = this.onUserNameChange.bind(this) 24 | this.onPasswordChange = this.onPasswordChange.bind(this) 25 | this.onEmailChange = this.onEmailChange.bind(this) 26 | this.onSubmit = this.onSubmit.bind(this) 27 | } 28 | 29 | onUserNameChange(value) { 30 | this.setState({ 31 | username: value 32 | }) 33 | } 34 | 35 | onPasswordChange(value) { 36 | this.setState({ 37 | password: value 38 | }) 39 | } 40 | 41 | onEmailChange(value) { 42 | this.setState({ 43 | email: value 44 | }) 45 | } 46 | 47 | onSubmit = () => { 48 | const { postSignUp, form } = this.props 49 | form.validateFields((error, value) => { 50 | if (!error) { 51 | postSignUp({ 52 | username: this.state.username, 53 | password: this.state.password, 54 | email: this.state.email 55 | }) 56 | } else { 57 | Toast.fail('格式错误,请检查后提交', 1.5) 58 | } 59 | }) 60 | } 61 | 62 | componentDidMount() {} 63 | 64 | render() { 65 | const { getFieldProps, getFieldError } = this.props.form 66 | const { user_home } = this.props 67 | const usernameErrors = getFieldError('username') 68 | const passwordErrors = getFieldError('password') 69 | const emailErrors = getFieldError('email') 70 | return ( 71 | 72 | 73 | 88 | 用户名 89 | 90 | 91 | 92 | 93 | 94 | {usernameErrors ? usernameErrors.join(',') : null} 95 | 96 | 97 | 98 | 114 | 密码 115 | 116 | 117 | 118 | 119 | 120 | {passwordErrors ? passwordErrors.join(',') : null} 121 | 122 | 123 | 124 | 135 | 邮箱 136 | 137 | 138 | 139 | 140 | 141 | {emailErrors ? emailErrors.join(',') : null} 142 | 143 | 144 | 145 | 146 | 147 | 150 | 151 | 152 | 153 | 154 | 155 | {user_home ? user_home.signupError : null} 156 | 157 | 158 | 159 | 160 | ) 161 | } 162 | } 163 | 164 | const mapStateToProps = (state, ownProps) => { 165 | return { 166 | user_home: state.user.home 167 | } 168 | } 169 | 170 | const mapDispatchToProps = (dispatch, ownProps) => { 171 | return { 172 | postSignUp: payload => { 173 | dispatch(postSignUpRequest(payload)) 174 | } 175 | } 176 | } 177 | 178 | SignUp.propTypes = { 179 | user_home: PropTypes.object, 180 | postSignUp: PropTypes.func, 181 | form: PropTypes.object 182 | } 183 | 184 | const styles = StyleSheet.create({ 185 | error: { 186 | color: 'red', 187 | textAlign: 'center' 188 | } 189 | }) 190 | 191 | export default connect(mapStateToProps, mapDispatchToProps)( 192 | createForm()(SignUp) 193 | ) 194 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import config from '../config' 2 | 3 | export function cutstr(str, len, flag) { 4 | var str_length = 0 5 | var str_cut = '' 6 | var str_len = str.length 7 | for (var i = 0; i < str_len; i++) { 8 | var a = str.charAt(i) 9 | str_length++ 10 | if (escape(a).length > 4) { 11 | //中文字符的长度经编码之后大于4 12 | str_length++ 13 | } 14 | str_cut = str_cut.concat(a) 15 | if (str_length >= len) { 16 | if (flag === 0) { 17 | str_cut = str_cut.concat('...') 18 | } 19 | return str_cut 20 | } 21 | } 22 | //如果给定字符串小于指定长度,则返回源字符串; 23 | if (str_length < len) { 24 | return str 25 | } 26 | } 27 | 28 | export function getNickName() { 29 | const nicks = [ 30 | '末日铁拳', 31 | '源氏', 32 | '麦克雷', 33 | '法老之鹰', 34 | '死神', 35 | '士兵:76', 36 | '黑影', 37 | '堡垒', 38 | '半藏', 39 | '狂鼠', 40 | '美', 41 | '托比昂', 42 | '黑百合', 43 | 'D.Va', 44 | '奥丽莎', 45 | '莱因哈特', 46 | '路霸', 47 | '温斯顿', 48 | '查莉娅', 49 | '安娜', 50 | '卢西奥', 51 | '天使', 52 | '莫伊拉', 53 | '秩序之光', 54 | '禅雅塔' 55 | ] 56 | const i = nicks[Math.floor(Math.random() * nicks.length)] 57 | return i 58 | } 59 | 60 | export function getAvatar() { 61 | const avatars = [ 62 | 'avatar/00007642.png', 63 | 'avatar/00007643.png', 64 | 'avatar/00007644.png', 65 | 'avatar/00007647.png', 66 | 'avatar/00007641.png', 67 | 'avatar/00008056.png', 68 | 'avatar/00007646.png', 69 | 'avatar/00008057.png', 70 | 'avatar/00007649.png', 71 | 'avatar/00008058.png', 72 | 'avatar/00007648.png', 73 | 'avatar/00008059.png', 74 | 'avatar/00008063.png', 75 | 'avatar/00008060.png', 76 | 'avatar/00008064.png', 77 | 'avatar/00008062.png', 78 | 'avatar/00007645.png', 79 | 'avatar/00008067.png', 80 | 'avatar/00008069.png', 81 | 'avatar/00008065.png', 82 | 'avatar/00008066.png', 83 | 'avatar/00007174.png', 84 | 'avatar/00008068.png', 85 | 'avatar/00007176.png', 86 | 'avatar/00007177.png', 87 | 'avatar/00007179.png', 88 | 'avatar/00007184.png', 89 | 'avatar/00007181.png', 90 | 'avatar/00007185.png', 91 | 'avatar/00007186.png', 92 | 'avatar/00007183.png', 93 | 'avatar/00007180.png', 94 | 'avatar/00007051.png', 95 | 'avatar/00007052.png', 96 | 'avatar/00007640.png', 97 | 'avatar/00007152.png', 98 | 'avatar/00007155.png', 99 | 'avatar/00007188.png', 100 | 'avatar/00007182.png', 101 | 'avatar/00007154.png', 102 | 'avatar/00007156.png', 103 | 'avatar/00007157.png', 104 | 'avatar/00007153.png', 105 | 'avatar/00007158.png', 106 | 'avatar/00007160.png', 107 | 'avatar/00007159.png', 108 | 'avatar/00007161.png', 109 | 'avatar/00007162.png', 110 | 'avatar/00007165.png', 111 | 'avatar/00007166.png', 112 | 'avatar/00007168.png', 113 | 'avatar/00007164.png', 114 | 'avatar/00007169.png', 115 | 'avatar/00007170.png', 116 | 'avatar/00007172.png', 117 | 'avatar/0000764B.png', 118 | 'avatar/0000764A.png', 119 | 'avatar/0000764C.png', 120 | 'avatar/0000805B.png', 121 | 'avatar/00007171.png', 122 | 'avatar/0000764D.png', 123 | 'avatar/0000764E.png', 124 | 'avatar/0000804C.png', 125 | 'avatar/0000805A.png', 126 | 'avatar/0000805C.png', 127 | 'avatar/0000805D.png', 128 | 'avatar/0000805E.png', 129 | 'avatar/0000806A.png', 130 | 'avatar/0000805F.png', 131 | 'avatar/0000806B.png', 132 | 'avatar/0000806C.png', 133 | 'avatar/0000806D.png', 134 | 'avatar/0000715A.png', 135 | 'avatar/0000715B.png', 136 | 'avatar/0000715D.png', 137 | 'avatar/0000715E.png', 138 | 'avatar/0000715C.png', 139 | 'avatar/0000716B.png', 140 | 'avatar/0000716A.png', 141 | 'avatar/0000715F.png', 142 | 'avatar/0000716F.png', 143 | 'avatar/0000716C.png', 144 | 'avatar/0000716D.png', 145 | 'avatar/0000716E.png', 146 | 'avatar/0000717B.png', 147 | 'avatar/0000717E.png', 148 | 'avatar/0000719A.png', 149 | 'avatar/0000717A.png', 150 | 'avatar/0000719B.png', 151 | 'avatar/0000719E.png', 152 | 'avatar/0000719F.png', 153 | 'avatar/000071A0.png', 154 | 'avatar/000051CC.png', 155 | 'avatar/000071A1.png', 156 | 'avatar/000071A4.png', 157 | 'avatar/000071A7.png', 158 | 'avatar/000071A5.png', 159 | 'avatar/000071A6.png', 160 | 'avatar/000071A9.png', 161 | 'avatar/000071AA.png', 162 | 'avatar/000071A8.png', 163 | 'avatar/0000717C.png', 164 | 'avatar/000071AC.png', 165 | 'avatar/000071AB.png', 166 | 'avatar/000071AE.png', 167 | 'avatar/000071B0.png', 168 | 'avatar/000071B1.png', 169 | 'avatar/000071AF.png', 170 | 'avatar/000071B2.png', 171 | 'avatar/000071B3.png' 172 | ] 173 | const i = avatars[Math.floor(Math.random() * avatars.length)] 174 | return config.BASE_PIC_URL + '/' + i 175 | } 176 | 177 | export function getPosition(type) { 178 | if (type) { 179 | const positions = { 180 | DPS: config.BASE_PIC_URL + '/dps.png', 181 | Flex: config.BASE_PIC_URL + '/flex.png', 182 | Tank: config.BASE_PIC_URL + '/tank.png', 183 | Support: config.BASE_PIC_URL + '/support.png' 184 | } 185 | return positions[type] 186 | } else { 187 | return config.BASE_PIC_URL + '/position.png' 188 | } 189 | } 190 | 191 | export function getDayStart() { 192 | return new Date(new Date(new Date().toDateString()).getTime()) 193 | } 194 | 195 | export function getDayEnd() { 196 | return new Date(new Date(new Date().toDateString()).getTime()+24*60*60*1000-1) 197 | } -------------------------------------------------------------------------------- /src/screens/SignIn/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, StyleSheet, Text } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import { connect } from 'react-redux' 5 | import { createForm } from 'rc-form' 6 | import { 7 | Button, 8 | InputItem, 9 | WhiteSpace, 10 | Flex, 11 | WingBlank, 12 | Toast 13 | } from 'antd-mobile' 14 | import { NavigationActions } from 'react-navigation' 15 | import { postLoginRequest } from '../../actions' 16 | 17 | class SignIn extends Component { 18 | constructor(props) { 19 | super(props) 20 | this.state = { 21 | username: '', 22 | password: '' 23 | } 24 | this.onUserNameChange = this.onUserNameChange.bind(this) 25 | this.onPasswordChange = this.onPasswordChange.bind(this) 26 | this.onSignUp = this.onSignUp.bind(this) 27 | this.onSubmit = this.onSubmit.bind(this) 28 | } 29 | 30 | onUserNameChange(value) { 31 | this.setState({ 32 | username: value 33 | }) 34 | } 35 | 36 | onPasswordChange(value) { 37 | this.setState({ 38 | password: value 39 | }) 40 | } 41 | 42 | onSubmit = () => { 43 | const { postLogin, form } = this.props 44 | form.validateFields((error, value) => { 45 | if (!error) { 46 | postLogin({ 47 | username: this.state.username, 48 | password: this.state.password 49 | }) 50 | } else { 51 | Toast.fail('格式错误,请检查后提交', 1.5) 52 | } 53 | }) 54 | } 55 | 56 | onSignUp() { 57 | this.props.navigateTo('SignUp') 58 | } 59 | 60 | componentDidMount() {} 61 | 62 | render() { 63 | const { getFieldProps, getFieldError } = this.props.form 64 | const { user_home, goBack } = this.props 65 | const usernameErrors = getFieldError('username') 66 | const passwordErrors = getFieldError('password') 67 | return ( 68 | 69 | 70 | 71 | 87 | 用户名 88 | 89 | 90 | 91 | 92 | 93 | {usernameErrors ? usernameErrors.join(',') : null} 94 | 95 | 96 | 97 | 114 | 密码 115 | 116 | 117 | 118 | 119 | 120 | {passwordErrors ? passwordErrors.join(',') : null} 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 132 | 133 | 134 | 135 | 136 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | {user_home ? user_home.loginError : null} 148 | 149 | 150 | 151 | 152 | 153 | ) 154 | } 155 | } 156 | 157 | const mapStateToProps = (state, ownProps) => { 158 | return { 159 | user_home: state.user.home 160 | } 161 | } 162 | 163 | const mapDispatchToProps = (dispatch, ownProps) => { 164 | return { 165 | postLogin: payload => { 166 | dispatch(postLoginRequest(payload)) 167 | }, 168 | navigateTo: (path, params) => { 169 | dispatch(NavigationActions.navigate({ routeName: path, params: params })) 170 | }, 171 | } 172 | } 173 | 174 | SignIn.propTypes = { 175 | user_home: PropTypes.object, 176 | postLogin: PropTypes.func, 177 | navigateTo: PropTypes.func, 178 | form: PropTypes.object 179 | } 180 | 181 | const styles = StyleSheet.create({ 182 | error: { 183 | color: 'red', 184 | textAlign: 'center' 185 | } 186 | }) 187 | 188 | export default connect(mapStateToProps, mapDispatchToProps)( 189 | createForm()(SignIn) 190 | ) 191 | -------------------------------------------------------------------------------- /src/sagas/userSaga.js: -------------------------------------------------------------------------------- 1 | import { put, fork, take, call } from 'redux-saga/effects' 2 | import { delay } from 'redux-saga' 3 | import { NavigationActions } from 'react-navigation' 4 | import { Toast } from 'antd-mobile' 5 | import { 6 | GET_CURRENTUSER_REQUEST, 7 | POST_SIGNUP_REQUEST, 8 | POST_LOGIN_REQUEST, 9 | POST_LOGOUT_REQUEST, 10 | PUT_USERINFO_REQUEST, 11 | GET_USERINFO_REQUEST, 12 | GET_HOME_USERINFO_LIST_REQUEST, 13 | GET_HOME_USERINFO_DETAIL_REQUEST 14 | } from '../constants/actionTypes' 15 | import * as action from '../actions' 16 | import { userService } from '../services/leanclound' 17 | 18 | function* getCurrentUserWorker() { 19 | try { 20 | const response = yield call(userService.getCurrentUserAsync) 21 | yield put(action.getCurrentUserSuccess(response)) 22 | } catch (error) { 23 | yield put(action.getCurrentUserFailed()) 24 | } 25 | } 26 | 27 | function* postSignUpWorker(payload) { 28 | try { 29 | Toast.loading('注册中') 30 | const response = yield call(userService.signUp, payload) 31 | yield put(action.postSignUpSuccess(response)) 32 | Toast.success('注册成功', 1) 33 | yield put( 34 | NavigationActions.reset({ 35 | index: 0, 36 | actions: [NavigationActions.navigate({ routeName: 'Account' })] 37 | }) 38 | ) 39 | } catch (error) { 40 | Toast.fail('注册失败', 1) 41 | yield put(action.postSignUpFailed(error)) 42 | } 43 | } 44 | 45 | function* postLoginWorker(payload) { 46 | try { 47 | Toast.loading('登录中') 48 | const response = yield call(userService.logIn, payload) 49 | yield put(action.postLoginSuccess(response)) 50 | Toast.success('登录成功', 1) 51 | yield put( 52 | NavigationActions.reset({ 53 | index: 0, 54 | actions: [NavigationActions.navigate({ routeName: 'Account' })] 55 | }) 56 | ) 57 | } catch (error) { 58 | Toast.fail('登录失败', 1) 59 | yield put(action.postLoginFailed(error)) 60 | } 61 | } 62 | 63 | function* postLogoutWorker() { 64 | try { 65 | yield call(userService.logOut) 66 | Toast.success('注销成功', 1) 67 | yield delay(1000) 68 | yield put(action.postLogoutSuccess()) 69 | yield put( 70 | NavigationActions.reset({ 71 | index: 0, 72 | actions: [NavigationActions.navigate({ routeName: 'Account' })] 73 | }) 74 | ) 75 | } catch (error) { 76 | Toast.success('注销失败', 1) 77 | yield put(action.postLogoutFailed(error)) 78 | } 79 | } 80 | 81 | function* putUserInfoWorker(payload) { 82 | try { 83 | Toast.loading('提交中') 84 | const response = yield call(userService.putUserInfo, payload) 85 | yield put(action.putUserInfoSuccess(response)) 86 | Toast.success('提交成功', 1.5) 87 | yield delay(1500) 88 | yield put(NavigationActions.back()) 89 | } catch (error) { 90 | yield put(action.putUserInfoFailed(error)) 91 | Toast.fail('提交失败', 1.5) 92 | } 93 | } 94 | 95 | function* getUserInfoWorker() { 96 | try { 97 | const currentUser = yield call(userService.getCurrentUserAsync) 98 | const response = yield call(userService.getUserInfoToJson, currentUser) 99 | yield put(action.getUserInfoSuccess(response)) 100 | } catch (error) { 101 | yield put(action.getUserInfoFailed(error)) 102 | } 103 | } 104 | 105 | function* getHomeUserInfoListWorker(payload) { 106 | try { 107 | const response = yield call(userService.getHomeUserInfoList, payload) 108 | yield put(action.getHomeUserInfoListSuccess(response)) 109 | } catch (error) { 110 | yield put(action.getHomeUserInfoListFailed(error)) 111 | } 112 | } 113 | 114 | function* getHomeUserInfoDetailWorker(payload) { 115 | try { 116 | Toast.loading('加载中') 117 | const response = yield call(userService.getHomeUserInfoDetail, payload) 118 | yield put(action.getHomeUserInfoDetailSuccess(response)) 119 | Toast.hide() 120 | } catch (error) { 121 | yield put(action.getHomeUserInfoDetailFailed(error)) 122 | Toast.hide() 123 | } 124 | } 125 | 126 | function* watchLogin() { 127 | while (true) { 128 | const { payload } = yield take(POST_LOGIN_REQUEST) 129 | yield fork(postLoginWorker, payload) 130 | } 131 | } 132 | 133 | function* watchSignUp() { 134 | while (true) { 135 | const { payload } = yield take(POST_SIGNUP_REQUEST) 136 | yield fork(postSignUpWorker, payload) 137 | } 138 | } 139 | 140 | function* watchLogout() { 141 | while (true) { 142 | yield take(POST_LOGOUT_REQUEST) 143 | yield fork(postLogoutWorker) 144 | } 145 | } 146 | 147 | function* watchPutUserInfo() { 148 | while (true) { 149 | const { payload } = yield take(PUT_USERINFO_REQUEST) 150 | yield fork(putUserInfoWorker, payload) 151 | } 152 | } 153 | 154 | function* watchGetUserInfo() { 155 | while (true) { 156 | yield take(GET_USERINFO_REQUEST) 157 | yield fork(getUserInfoWorker) 158 | } 159 | } 160 | 161 | function* watchGetHomeUserList() { 162 | while (true) { 163 | const { payload } = yield take(GET_HOME_USERINFO_LIST_REQUEST) 164 | yield fork(getHomeUserInfoListWorker, payload) 165 | } 166 | } 167 | 168 | function* watchGetHomeUserDetail() { 169 | while (true) { 170 | const { payload } = yield take(GET_HOME_USERINFO_DETAIL_REQUEST) 171 | yield fork(getHomeUserInfoDetailWorker, payload) 172 | } 173 | } 174 | 175 | function* watchGetCurrentUser() { 176 | while (true) { 177 | yield take(GET_CURRENTUSER_REQUEST) 178 | yield fork(getCurrentUserWorker) 179 | } 180 | } 181 | 182 | export { 183 | watchSignUp, 184 | watchLogin, 185 | watchLogout, 186 | watchGetUserInfo, 187 | watchPutUserInfo, 188 | watchGetHomeUserList, 189 | watchGetHomeUserDetail, 190 | watchGetCurrentUser 191 | } 192 | -------------------------------------------------------------------------------- /src/sagas/teamsSaga.js: -------------------------------------------------------------------------------- 1 | import { put, fork, take, call } from 'redux-saga/effects' 2 | import { delay } from 'redux-saga' 3 | import { NavigationActions } from 'react-navigation' 4 | import { Toast } from 'antd-mobile' 5 | import { 6 | POST_TEAMS_REQUEST, 7 | PUT_TEAMS_REQUEST, 8 | DELETE_TEAM_MEMBER_REQUEST, 9 | DELETE_TEAM_REQUEST, 10 | GET_HOME_TEAM_LIST_REQUEST, 11 | GET_HOME_TEAM_DETAIL_REQUEST, 12 | GET_MY_TEAMS_REQUEST, 13 | GET_IN_TEAMS_REQUEST 14 | } from '../constants/actionTypes' 15 | import * as action from '../actions' 16 | import { teamsService, userService } from '../services/leanclound' 17 | 18 | function* postTeamsWorker(payload) { 19 | try { 20 | Toast.loading('提交中') 21 | const currentUser = yield call(userService.getCurrentUserAsync) 22 | const team = yield call(teamsService.getMyTeams, currentUser) 23 | if (team.length < currentUser.get('teamLimit')) { 24 | const response = yield call(teamsService.cerateTeam, payload, currentUser) 25 | yield put(action.postTeamsSuccess(response)) 26 | Toast.success('提交成功', 1) 27 | yield delay(1000) 28 | yield put(NavigationActions.back()) 29 | } else { 30 | yield put(action.postTeamsFailed()) 31 | Toast.fail( 32 | '提交失败,每位用户最多可创建一支战队,若想创建多支战队,请联系管理员963577494@qq.com', 33 | 3 34 | ) 35 | } 36 | } catch (error) { 37 | yield put(action.postTeamsFailed()) 38 | Toast.fail('提交失败', 1) 39 | } 40 | } 41 | 42 | function* putTeamsWorker(payload) { 43 | try { 44 | Toast.loading('提交中') 45 | const response = yield call(teamsService.updateTeam, payload) 46 | yield put(action.putTeamsSuccess(response)) 47 | Toast.success('提交成功', 1) 48 | yield delay(1000) 49 | yield put(NavigationActions.back()) 50 | } catch (error) { 51 | yield put(action.putTeamsFailed()) 52 | Toast.fail('提交失败', 1) 53 | } 54 | } 55 | 56 | function* getInTeamsWorker() { 57 | try { 58 | Toast.loading('加载中') 59 | const currentUser = yield call(userService.getCurrentUserAsync) 60 | const response = yield call(teamsService.getInTeams, currentUser) 61 | yield put(action.getInTeamsSuccess(response)) 62 | Toast.hide() 63 | } catch (error) { 64 | yield put(action.getInTeamsFailed(error)) 65 | Toast.hide() 66 | } 67 | } 68 | 69 | function* getMyTeamsWorker() { 70 | try { 71 | Toast.loading('加载中') 72 | const currentUser = yield call(userService.getCurrentUserAsync) 73 | const response = yield call(teamsService.getMyTeams, currentUser) 74 | yield put(action.getMyTeamsSuccess(response)) 75 | Toast.hide() 76 | } catch (error) { 77 | yield put(action.getMyTeamsFailed(error)) 78 | Toast.hide() 79 | } 80 | } 81 | 82 | function* deleteTeamMemberWorker(payload) { 83 | try { 84 | Toast.loading('提交中') 85 | const response = yield call(teamsService.removeMember, payload) 86 | yield put(action.deleteTeamMemberSuccess(response)) 87 | Toast.success('移除队员成功', 1.5) 88 | yield delay(1500) 89 | yield put(NavigationActions.back()) 90 | } catch (error) { 91 | yield put(action.deleteTeamMemberFailed(error)) 92 | Toast.fail(error.message, 1.5) 93 | } 94 | } 95 | 96 | function* deleteTeamWorker(payload) { 97 | try { 98 | Toast.loading('提交中') 99 | const response = yield call(teamsService.removeTeam, payload) 100 | yield put(action.deleteTeamSuccess(response)) 101 | Toast.success('解散队伍成功', 1.5) 102 | yield delay(1500) 103 | } catch (error) { 104 | yield put(action.deleteTeamFailed(error)) 105 | Toast.fail(error.message, 1.5) 106 | } 107 | } 108 | 109 | function* getHomeTeamListWorker(payload) { 110 | try { 111 | Toast.loading('加载中') 112 | const response = yield call(teamsService.getHomeTeamsList, payload) 113 | yield put(action.getHomeTeamListSuccess(response)) 114 | Toast.hide() 115 | } catch (error) { 116 | yield put(action.getHomeTeamListFailed(error)) 117 | Toast.hide() 118 | } 119 | } 120 | 121 | function* getHomeTeamDetailWorker(payload) { 122 | try { 123 | Toast.loading('加载中') 124 | const response = yield call(teamsService.getHomeTeamDetail, payload) 125 | yield put(action.getHomeTeamDetailSuccess(response)) 126 | Toast.hide() 127 | } catch (error) { 128 | yield put(action.getHomeTeamDetailFailed(error)) 129 | Toast.hide() 130 | } 131 | } 132 | 133 | function* watchPostTeams() { 134 | while (true) { 135 | const { payload } = yield take(POST_TEAMS_REQUEST) 136 | yield fork(postTeamsWorker, payload) 137 | } 138 | } 139 | 140 | function* watchPutTeams() { 141 | while (true) { 142 | const { payload } = yield take(PUT_TEAMS_REQUEST) 143 | yield fork(putTeamsWorker, payload) 144 | } 145 | } 146 | 147 | function* watchDeleteTeamMember() { 148 | while (true) { 149 | const { payload } = yield take(DELETE_TEAM_MEMBER_REQUEST) 150 | yield fork(deleteTeamMemberWorker, payload) 151 | } 152 | } 153 | 154 | function* watchDeleteTeam() { 155 | while (true) { 156 | const { payload } = yield take(DELETE_TEAM_REQUEST) 157 | yield fork(deleteTeamWorker, payload) 158 | } 159 | } 160 | 161 | function* watchGetHomeTeamList() { 162 | while (true) { 163 | const { payload } = yield take(GET_HOME_TEAM_LIST_REQUEST) 164 | yield fork(getHomeTeamListWorker, payload) 165 | } 166 | } 167 | 168 | function* watchGetHomeTeamDetail() { 169 | while (true) { 170 | const { payload } = yield take(GET_HOME_TEAM_DETAIL_REQUEST) 171 | yield fork(getHomeTeamDetailWorker, payload) 172 | } 173 | } 174 | 175 | function* watchGetMyTeams() { 176 | while (true) { 177 | yield take(GET_MY_TEAMS_REQUEST) 178 | yield fork(getMyTeamsWorker) 179 | } 180 | } 181 | 182 | function* watchGetInTeams() { 183 | while (true) { 184 | yield take(GET_IN_TEAMS_REQUEST) 185 | yield fork(getInTeamsWorker) 186 | } 187 | } 188 | 189 | export { 190 | watchPostTeams, 191 | watchPutTeams, 192 | watchDeleteTeamMember, 193 | watchDeleteTeam, 194 | watchGetHomeTeamList, 195 | watchGetHomeTeamDetail, 196 | watchGetMyTeams, 197 | watchGetInTeams 198 | } 199 | -------------------------------------------------------------------------------- /src/reducers/team.js: -------------------------------------------------------------------------------- 1 | import { 2 | POST_TEAMS_REQUEST, 3 | POST_TEAMS_SUCCESS, 4 | POST_TEAMS_FAILED, 5 | PUT_TEAMS_REQUEST, 6 | PUT_TEAMS_SUCCESS, 7 | PUT_TEAMS_FAILED, 8 | GET_MY_TEAMS_REQUEST, 9 | GET_MY_TEAMS_SUCCESS, 10 | GET_MY_TEAMS_FAILED, 11 | GET_IN_TEAMS_REQUEST, 12 | GET_IN_TEAMS_SUCCESS, 13 | GET_IN_TEAMS_FAILED, 14 | DELETE_TEAM_MEMBER_REQUEST, 15 | DELETE_TEAM_MEMBER_SUCCESS, 16 | DELETE_TEAM_MEMBER_FAILED, 17 | DELETE_TEAM_REQUEST, 18 | DELETE_TEAM_SUCCESS, 19 | DELETE_TEAM_FAILED, 20 | GET_HOME_TEAM_LIST_REQUEST, 21 | GET_HOME_TEAM_LIST_SUCCESS, 22 | GET_HOME_TEAM_LIST_FAILED, 23 | GET_HOME_TEAM_DETAIL_REQUEST, 24 | GET_HOME_TEAM_DETAIL_SUCCESS, 25 | GET_HOME_TEAM_DETAIL_FAILED 26 | } from '../constants/actionTypes' 27 | 28 | const initialTeamState = { 29 | home: { 30 | team: { 31 | list: [], 32 | current: {}, 33 | isFetching: false, 34 | fetchingText: '加载中', 35 | isLoadMore: false, 36 | isRefreshing: false, 37 | page: 1, 38 | pagesize: 20 39 | } 40 | }, 41 | account: { 42 | current: {}, 43 | team: { 44 | myTeams: [], 45 | inTeams: [] 46 | }, 47 | pending: false 48 | } 49 | } 50 | 51 | function teamReducer(state = initialTeamState, action) { 52 | switch (action.type) { 53 | case POST_TEAMS_REQUEST: 54 | return { 55 | ...state, 56 | account: { 57 | ...state.account, 58 | team: { 59 | ...state.account.team, 60 | pending: true 61 | } 62 | } 63 | } 64 | case POST_TEAMS_SUCCESS: 65 | return { 66 | ...state, 67 | account: { 68 | ...state.account, 69 | team: { 70 | ...state.account.team, 71 | myTeams: [...state.account.team.myTeams, action.payload], 72 | pending: false 73 | } 74 | }, 75 | home: { 76 | ...state.home, 77 | team: { 78 | ...state.home.team, 79 | myTeams: [...state.home.team.myTeams, action.payload] 80 | } 81 | } 82 | } 83 | case POST_TEAMS_FAILED: 84 | return { 85 | ...state, 86 | account: { 87 | ...state.account, 88 | team: { 89 | ...state.account.team, 90 | pending: false 91 | } 92 | } 93 | } 94 | case PUT_TEAMS_REQUEST: 95 | return { 96 | ...state, 97 | account: { 98 | ...state.account, 99 | team: { 100 | ...state.account.team, 101 | pending: true 102 | } 103 | } 104 | } 105 | case PUT_TEAMS_SUCCESS: 106 | const data = state.account.team.myTeams.map(item => { 107 | if (item.objectId === action.payload.objectId) { 108 | return Object.assign(item, action.payload) 109 | } else { 110 | return item 111 | } 112 | }) 113 | return { 114 | ...state, 115 | account: { 116 | ...state.account, 117 | team: { 118 | ...state.account.team, 119 | myTeams: data 120 | } 121 | }, 122 | home: { 123 | ...state.home, 124 | team: { 125 | ...state.home.team, 126 | myTeams: data 127 | } 128 | } 129 | } 130 | case PUT_TEAMS_FAILED: 131 | return { 132 | ...state, 133 | account: { 134 | ...state.account, 135 | team: { 136 | ...state.account.team, 137 | pending: false 138 | } 139 | } 140 | } 141 | case DELETE_TEAM_MEMBER_REQUEST: 142 | return state 143 | case DELETE_TEAM_MEMBER_SUCCESS: 144 | return state 145 | case DELETE_TEAM_MEMBER_FAILED: 146 | return state 147 | case DELETE_TEAM_REQUEST: 148 | return state 149 | case DELETE_TEAM_SUCCESS: 150 | const teams = state.account.team.myTeams.filter( 151 | x => x.objectId !== action.payload.objectId 152 | ) 153 | return { 154 | ...state, 155 | account: { 156 | ...state.account, 157 | team: { 158 | ...state.account.team, 159 | myTeams: teams 160 | } 161 | }, 162 | home: { 163 | ...state.home, 164 | team: { 165 | ...state.home.team, 166 | myTeams: teams 167 | } 168 | } 169 | } 170 | case DELETE_TEAM_FAILED: 171 | return state 172 | case GET_MY_TEAMS_REQUEST: 173 | return state 174 | case GET_MY_TEAMS_SUCCESS: 175 | return { 176 | ...state, 177 | account: { 178 | ...state.account, 179 | team: { 180 | ...state.account.team, 181 | myTeams: [].concat(action.payload) 182 | } 183 | } 184 | } 185 | case GET_MY_TEAMS_FAILED: 186 | return state 187 | case GET_IN_TEAMS_REQUEST: 188 | return state 189 | case GET_IN_TEAMS_SUCCESS: 190 | return { 191 | ...state, 192 | account: { 193 | ...state.account, 194 | team: { 195 | ...state.account.team, 196 | inTeams: [].concat(action.payload) 197 | } 198 | } 199 | } 200 | case GET_IN_TEAMS_FAILED: 201 | return state 202 | case GET_HOME_TEAM_LIST_REQUEST: 203 | return { 204 | ...state, 205 | home: { 206 | ...state.home, 207 | team: { 208 | ...state.home.team, 209 | isFetching: true, 210 | isRefreshing: action.payload.isRefreshing || false, 211 | list: action.payload.isRefreshing ? [] : state.home.team.list, 212 | page: action.payload.isRefreshing 213 | ? 1 214 | : action.payload.page ? action.payload.page : 1 215 | } 216 | } 217 | } 218 | case GET_HOME_TEAM_LIST_SUCCESS: 219 | return { 220 | ...state, 221 | home: { 222 | ...state.home, 223 | team: { 224 | ...state.home.team, 225 | list: state.home.team.list.concat(action.payload), 226 | isFetching: false, 227 | isRefreshing: false, 228 | isLoadMore: action.payload.length < 20 ? false : true 229 | } 230 | } 231 | } 232 | case GET_HOME_TEAM_LIST_FAILED: 233 | return { 234 | ...state, 235 | home: { 236 | ...state.home, 237 | team: { 238 | ...state.home.team, 239 | isFetching: false, 240 | isRefreshing: false 241 | } 242 | } 243 | } 244 | case GET_HOME_TEAM_DETAIL_REQUEST: 245 | return state 246 | case GET_HOME_TEAM_DETAIL_SUCCESS: 247 | return { 248 | ...state, 249 | home: { 250 | ...state.home, 251 | team: { 252 | ...state.home.team, 253 | current: action.payload 254 | } 255 | } 256 | } 257 | case GET_HOME_TEAM_DETAIL_FAILED: 258 | return state 259 | default: 260 | return state 261 | } 262 | } 263 | 264 | export { teamReducer } 265 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | import { Constants } from 'expo' 2 | 3 | export const STATUS_BAR_HEIGHT = Constants.statusBarHeight 4 | 5 | export const TEAMPOSITIONS = [ 6 | { 7 | value: 'DPS', 8 | label: '突击', 9 | desc: '' 10 | }, 11 | { 12 | value: 'Flex', 13 | label: '自由人', 14 | desc: '' 15 | }, 16 | { 17 | value: 'Tank', 18 | label: '坦克', 19 | desc: '' 20 | }, 21 | { 22 | value: 'Support', 23 | label: '辅助', 24 | desc: '' 25 | } 26 | ] 27 | export const RANKS = [ 28 | { 29 | value: 'bronze', 30 | label: '青铜', 31 | score: '1-1499' 32 | }, 33 | { 34 | value: 'silver', 35 | label: '白银', 36 | score: '1500-1999' 37 | }, 38 | { 39 | value: 'gold', 40 | label: '黄金', 41 | score: '2000-2499' 42 | }, 43 | { 44 | value: 'platinum', 45 | label: '白金', 46 | score: '2500-2999' 47 | }, 48 | { 49 | value: 'diamond', 50 | label: '钻石', 51 | score: '3000-3499' 52 | }, 53 | { 54 | value: 'master', 55 | label: '大师', 56 | score: '3500-3999' 57 | }, 58 | { 59 | value: 'grandmaster', 60 | label: '宗师', 61 | score: '4000-5000' 62 | }, 63 | { 64 | value: 'top500', 65 | label: '500强', 66 | score: '4000-5000' 67 | } 68 | ] 69 | 70 | export const HEROS = [ 71 | { 72 | value: 'doomfist', 73 | label: '末日铁拳', 74 | image: 75 | 'http://overwatch.nos.netease.com/1/assets/images/hero/doomfist/icon-portrait.png', 76 | position: 'offense', 77 | checked: false 78 | }, 79 | { 80 | value: 'genji', 81 | label: '源氏', 82 | image: 83 | 'http://overwatch.nos.netease.com/1/assets/images/hero/genji/icon-portrait.png', 84 | position: 'offense', 85 | checked: false 86 | }, 87 | { 88 | value: 'mccree', 89 | label: '麦克雷', 90 | image: 91 | 'http://overwatch.nos.netease.com/1/assets/images/hero/mccree/icon-portrait.png', 92 | position: 'offense', 93 | checked: false 94 | }, 95 | { 96 | value: 'pharah', 97 | label: '法老之鹰', 98 | image: 99 | 'http://overwatch.nos.netease.com/1/assets/images/hero/pharah/icon-portrait.png', 100 | position: 'offense', 101 | checked: false 102 | }, 103 | { 104 | value: 'reaper', 105 | label: '死神', 106 | image: 107 | 'http://overwatch.nos.netease.com/1/assets/images/hero/reaper/icon-portrait.png', 108 | position: 'offense', 109 | checked: false 110 | }, 111 | { 112 | value: 'soldier-76', 113 | label: '士兵:76', 114 | image: 115 | 'http://overwatch.nos.netease.com/1/assets/images/hero/soldier-76/icon-portrait.png', 116 | position: 'offense', 117 | checked: false 118 | }, 119 | { 120 | value: 'sombra', 121 | label: '黑影', 122 | image: 123 | 'http://overwatch.nos.netease.com/1/assets/images/hero/sombra/icon-portrait.png', 124 | position: 'offense', 125 | checked: false 126 | }, 127 | { 128 | value: 'tracer', 129 | label: '猎空', 130 | image: 131 | 'http://overwatch.nos.netease.com/1/assets/images/hero/tracer/icon-portrait.png', 132 | position: 'offense', 133 | checked: false 134 | }, 135 | { 136 | value: 'bastion', 137 | label: '堡垒', 138 | image: 139 | 'http://overwatch.nos.netease.com/1/assets/images/hero/bastion/icon-portrait.png', 140 | position: 'defense', 141 | checked: false 142 | }, 143 | { 144 | value: 'hanzo', 145 | label: '半藏', 146 | image: 147 | 'http://overwatch.nos.netease.com/1/assets/images/hero/hanzo/icon-portrait.png', 148 | position: 'defense', 149 | checked: false 150 | }, 151 | { 152 | value: 'junkrat', 153 | label: '狂鼠', 154 | image: 155 | 'http://overwatch.nos.netease.com/1/assets/images/hero/junkrat/icon-portrait.png', 156 | position: 'offense', 157 | checked: false 158 | }, 159 | { 160 | value: 'mei', 161 | label: '美', 162 | image: 163 | 'http://overwatch.nos.netease.com/1/assets/images/hero/mei/icon-portrait.png', 164 | position: 'offense', 165 | checked: false 166 | }, 167 | { 168 | value: 'torbjorn', 169 | label: '托比昂', 170 | image: 171 | 'http://overwatch.nos.netease.com/1/assets/images/hero/torbjorn/icon-portrait.png', 172 | position: 'offense', 173 | checked: false 174 | }, 175 | { 176 | value: 'widowmaker', 177 | label: '黑百合', 178 | image: 179 | 'http://overwatch.nos.netease.com/1/assets/images/hero/widowmaker/icon-portrait.png', 180 | position: 'offense', 181 | checked: false 182 | }, 183 | { 184 | value: 'dva', 185 | label: 'D.Va', 186 | image: 187 | 'http://overwatch.nos.netease.com/1/assets/images/hero/dva/icon-portrait.png', 188 | position: 'tank', 189 | checked: false 190 | }, 191 | { 192 | value: 'orisa', 193 | label: '奥丽莎', 194 | image: 195 | 'http://overwatch.nos.netease.com/1/assets/images/hero/orisa/icon-portrait.png', 196 | position: 'tank', 197 | checked: false 198 | }, 199 | { 200 | value: 'reinhardt', 201 | label: '莱因哈特', 202 | image: 203 | 'http://overwatch.nos.netease.com/1/assets/images/hero/reinhardt/icon-portrait.png', 204 | position: 'tank', 205 | checked: false 206 | }, 207 | { 208 | value: 'roadhog', 209 | label: '路霸', 210 | image: 211 | 'http://overwatch.nos.netease.com/1/assets/images/hero/roadhog/icon-portrait.png', 212 | position: 'tank', 213 | checked: false 214 | }, 215 | { 216 | value: 'winston', 217 | label: '温斯顿', 218 | image: 219 | 'http://overwatch.nos.netease.com/1/assets/images/hero/winston/icon-portrait.png', 220 | position: 'tank', 221 | checked: false 222 | }, 223 | { 224 | value: 'zarya', 225 | label: '查莉娅', 226 | image: 227 | 'http://overwatch.nos.netease.com/1/assets/images/hero/zarya/icon-portrait.png', 228 | position: 'tank', 229 | checked: false 230 | }, 231 | { 232 | value: 'ana', 233 | label: '安娜', 234 | image: 235 | 'http://overwatch.nos.netease.com/1/assets/images/hero/ana/icon-portrait.png', 236 | position: 'support', 237 | checked: false 238 | }, 239 | { 240 | value: 'lucio', 241 | label: '卢西奥', 242 | image: 243 | 'http://overwatch.nos.netease.com/1/assets/images/hero/lucio/icon-portrait.png', 244 | position: 'support', 245 | checked: false 246 | }, 247 | { 248 | value: 'mercy', 249 | label: '天使', 250 | image: 251 | 'http://overwatch.nos.netease.com/1/assets/images/hero/mercy/icon-portrait.png', 252 | position: 'support', 253 | checked: false 254 | }, 255 | { 256 | value: 'moira', 257 | label: '莫伊拉', 258 | image: 259 | 'http://overwatch.nos.netease.com/1/assets/images/hero/moira/icon-portrait.png', 260 | position: 'support', 261 | checked: false 262 | }, 263 | { 264 | value: 'symmetra', 265 | label: '秩序之光', 266 | image: 267 | 'http://overwatch.nos.netease.com/1/assets/images/hero/symmetra/icon-portrait.png', 268 | position: 'support', 269 | checked: false 270 | }, 271 | { 272 | value: 'zenyatta', 273 | label: '禅雅塔', 274 | image: 275 | 'http://overwatch.nos.netease.com/1/assets/images/hero/zenyatta/icon-portrait.png', 276 | position: 'support', 277 | checked: false 278 | } 279 | ] 280 | 281 | -------------------------------------------------------------------------------- /src/reducers/user.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_CURRENTUSER_REQUEST, 3 | GET_CURRENTUSER_SUCCESS, 4 | GET_CURRENTUSER_FAILED, 5 | POST_LOGIN_REQUEST, 6 | POST_LOGIN_SUCCESS, 7 | POST_LOGIN_FAILED, 8 | POST_LOGOUT_REQUEST, 9 | POST_LOGOUT_SUCCESS, 10 | POST_LOGOUT_FAILED, 11 | POST_SIGNUP_REQUEST, 12 | POST_SIGNUP_SUCCESS, 13 | POST_SIGNUP_FAILED, 14 | PUT_USERINFO_REQUEST, 15 | PUT_USERINFO_SUCCESS, 16 | PUT_USERINFO_FAILED, 17 | GET_USERINFO_REQUEST, 18 | GET_USERINFO_SUCCESS, 19 | GET_USERINFO_FAILED, 20 | GET_HOME_USERINFO_LIST_REQUEST, 21 | GET_HOME_USERINFO_LIST_SUCCESS, 22 | GET_HOME_USERINFO_LIST_FAILED, 23 | GET_HOME_USERINFO_DETAIL_REQUEST, 24 | GET_HOME_USERINFO_DETAIL_SUCCESS, 25 | GET_HOME_USERINFO_DETAIL_FAILED 26 | } from '../constants/actionTypes' 27 | import { HEROS } from '../constants' 28 | 29 | const initialUserState = { 30 | home: { 31 | loginError: '', 32 | signupError: '', 33 | userinfo: { 34 | list: [], 35 | current: {}, 36 | isFetching: false, 37 | fetchingText: '加载中', 38 | isLoadMore: false, 39 | isRefreshing: false, 40 | page: 1, 41 | pagesize: 20 42 | } 43 | }, 44 | account: { 45 | user: {}, 46 | userinfo: { 47 | isLoaded: false, 48 | position: 'DPS', 49 | rank: 'top500', 50 | pending: false 51 | } 52 | } 53 | } 54 | 55 | function userReducer(state = initialUserState, action) { 56 | switch (action.type) { 57 | case GET_CURRENTUSER_REQUEST: 58 | return state 59 | case GET_CURRENTUSER_SUCCESS: 60 | return { 61 | ...state, 62 | account: { ...state.account, user: action.payload } 63 | } 64 | case GET_CURRENTUSER_FAILED: 65 | return state 66 | case POST_LOGIN_REQUEST: 67 | return { ...state, home: { ...state.home, loginError: '' } } 68 | case POST_LOGIN_SUCCESS: 69 | return { 70 | ...state, 71 | home: { ...state.home, loginError: '' }, 72 | account: { ...state.account, user: action.payload } 73 | } 74 | case POST_LOGIN_FAILED: 75 | return { 76 | ...state, 77 | home: { ...state.home, loginError: action.payload.rawMessage } 78 | } 79 | case POST_LOGOUT_REQUEST: 80 | return state 81 | case POST_LOGOUT_SUCCESS: 82 | return { 83 | ...state, 84 | account: { 85 | ...state.account, 86 | user: {}, 87 | userinfo: { 88 | isLoaded: false, 89 | position: 'DPS', 90 | rank: 'top500', 91 | pending: false 92 | } 93 | } 94 | } 95 | case POST_LOGOUT_FAILED: 96 | return state 97 | case POST_SIGNUP_REQUEST: 98 | return { 99 | ...state, 100 | home: { ...state.home, signupError: '' } 101 | } 102 | case POST_SIGNUP_SUCCESS: 103 | return { 104 | ...state, 105 | home: { ...state.home, loginError: '' }, 106 | account: { ...state.account, user: {} } 107 | } 108 | case POST_SIGNUP_FAILED: 109 | return { 110 | ...state, 111 | home: { ...state.home, signupError: action.payload.rawMessage } 112 | } 113 | 114 | case PUT_USERINFO_REQUEST: 115 | return { 116 | ...state, 117 | account: { 118 | ...state.account, 119 | userinfo: { 120 | ...state.account.userinfo, 121 | pending: true 122 | } 123 | } 124 | } 125 | case PUT_USERINFO_SUCCESS: 126 | return { 127 | ...state, 128 | account: { 129 | ...state.account, 130 | userinfo: { 131 | ...state.account.userinfo, 132 | ...action.payload, 133 | heros: merge(HEROS, action.payload.heros), 134 | pending: false, 135 | isLoaded: true 136 | } 137 | } 138 | } 139 | case PUT_USERINFO_FAILED: 140 | return { 141 | ...state, 142 | account: { 143 | ...state.account, 144 | userinfo: { 145 | ...state.account.userinfo, 146 | pending: false 147 | } 148 | } 149 | } 150 | case GET_USERINFO_REQUEST: 151 | return state 152 | case GET_USERINFO_SUCCESS: 153 | return { 154 | ...state, 155 | account: { 156 | ...state.account, 157 | userinfo: { 158 | ...state.account.userinfo, 159 | ...action.payload, 160 | heros: merge(HEROS, action.payload.heros), 161 | isLoaded: true 162 | } 163 | } 164 | } 165 | case GET_USERINFO_FAILED: 166 | return state 167 | case GET_HOME_USERINFO_LIST_REQUEST: 168 | return { 169 | ...state, 170 | home: { 171 | ...state.home, 172 | userinfo: { 173 | ...state.home.userinfo, 174 | isFetching: true, 175 | isRefreshing: action.payload.isRefreshing || false, 176 | list: action.payload.isRefreshing ? [] : state.home.userinfo.list, 177 | page: action.payload.isRefreshing 178 | ? 1 179 | : action.payload.page ? action.payload.page : 1 180 | } 181 | } 182 | } 183 | case GET_HOME_USERINFO_LIST_SUCCESS: 184 | return { 185 | ...state, 186 | home: { 187 | ...state.home, 188 | userinfo: { 189 | ...state.home.userinfo, 190 | list: state.home.userinfo.list.concat(action.payload), 191 | isFetching: false, 192 | isRefreshing: false, 193 | isLoadMore: action.payload.length < 20 ? false : true 194 | } 195 | } 196 | } 197 | case GET_HOME_USERINFO_LIST_FAILED: 198 | return { 199 | ...state, 200 | home: { 201 | ...state.home, 202 | userinfo: { 203 | ...state.home.userinfo, 204 | isFetching: false, 205 | isRefreshing: false 206 | } 207 | } 208 | } 209 | case GET_HOME_USERINFO_DETAIL_REQUEST: 210 | return { 211 | ...state, 212 | home: { 213 | ...state.home, 214 | userinfo: { 215 | ...state.home.userinfo, 216 | isFetching: true 217 | } 218 | } 219 | } 220 | case GET_HOME_USERINFO_DETAIL_SUCCESS: 221 | return { 222 | ...state, 223 | home: { 224 | ...state.home, 225 | userinfo: { 226 | ...state.home.userinfo, 227 | isFetching: false, 228 | current: action.payload 229 | } 230 | } 231 | } 232 | case GET_HOME_USERINFO_DETAIL_FAILED: 233 | return { 234 | ...state, 235 | home: { 236 | ...state.home, 237 | userinfo: { 238 | ...state.home.userinfo, 239 | isFetching: false 240 | } 241 | } 242 | } 243 | default: 244 | return state 245 | } 246 | } 247 | 248 | function merge(o1, o2) { 249 | if (o2) { 250 | const result = o1.map(item1 => { 251 | return Object.assign( 252 | item1, 253 | o2.find(item2 => { 254 | return item2 && item1.value === item2.value 255 | }) 256 | ) 257 | }) 258 | return result 259 | } 260 | } 261 | 262 | export { userReducer } 263 | --------------------------------------------------------------------------------