├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── actions ├── account.js ├── broadcastSystemNotifications.js ├── dashboard.js ├── homeRecommendations.js ├── invitationCodes.js ├── question.js ├── reportedQuestions.js └── users.js ├── auth.js ├── components ├── account │ └── signin.js ├── homeRecommendations │ ├── AddHomeRecommendationModal.js │ ├── HomeRecommendationsList.js │ ├── UserHomeRecommendation.js │ └── UserSelector.js ├── invitationCodes │ ├── InvitationCodesList.js │ └── MarkInvitationCodeSendedModal.js ├── notification │ └── BroadcastSystemNotificationsList.js ├── public │ ├── ActiveNavItem.js │ ├── BaseFormModal.js │ ├── BaseInfoModal.js │ └── Pagination.js ├── question │ ├── AnswersList.js │ ├── AskQuestionModal.js │ ├── AskedQuestionsList.js │ ├── DraftsList.js │ ├── PendingQuestionsList.js │ ├── QuestionCommentsList.js │ ├── QuestionDetailsModal.js │ ├── QuestionsList.js │ └── UpdateQuestionModal.js ├── reportedQuestions │ └── ReportedQuestionsList.js └── users │ ├── UserModal.js │ ├── UserPageHeader.js │ ├── UserTabs.js │ └── UsersList.js ├── containers ├── AnswersPage.js ├── App.js ├── AuthPage.js ├── BroadcastSystemNotificationsPage.js ├── DashboardPage.js ├── DevTools.js ├── HomeRecommendationsPage.js ├── InvitationCodesPage.js ├── PendingAnonymousQuestionsPage.js ├── QuestionsPage.js ├── ReportsPage.js ├── Root.dev.js ├── Root.js ├── Root.prod.js ├── UserPage.js ├── UsersPage.js └── VisitorQuestionsReviewPage.js ├── fabfile.py ├── filters.js ├── fis-conf.js ├── index.html ├── index.js ├── models ├── Answer.js ├── BroadcastSystemNotification.js ├── HomeRecommendation.js ├── InvitationCode.js ├── LikeQuestion.js ├── Question.js ├── QuestionComment.js ├── ReportQuestion.js ├── User.js ├── UserNotification.js ├── UserNotificationSender.js ├── UserTag.js ├── UserTagAssociation.js └── index.js ├── nginx.conf ├── package.json ├── qrsync.conf.json ├── reducers ├── account.js ├── answers.js ├── broadcastSystemNotifications.js ├── dashboard.js ├── homeRecommendations.js ├── index.js ├── invitationCodes.js ├── pendingAnonymousQuestions.js ├── questions.js ├── reportedQuestions.js └── users.js ├── routes.js ├── server.js ├── static ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── images │ └── logo.png ├── scripts │ └── fetch.js └── styles │ ├── bootstrap.min.css │ ├── bootstrap.theme.scss │ ├── font-awesome.min.css │ ├── hint.min.css │ └── react-modal.css ├── store ├── index.js ├── store.dev.js └── store.prod.js ├── utils.js ├── webpack.config.dev.js └── webpack.config.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-1", "react"], 3 | "plugins": ["transform-object-rest-spread", "jsx-control-statements", "transform-decorators-legacy"], 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | *.log 4 | *.pyc 5 | output/ 6 | webpack-output/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Zhipeng Liu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #React Redux Example 2 | 3 | **Attention**: this repo is just for learning, it cannot run really. 4 | 5 | ##Development 6 | 7 | ```sh 8 | npm install 9 | npm start 10 | ``` 11 | 12 | ##Deploy 13 | 14 | ```sh 15 | npm run deploy 16 | ``` 17 | -------------------------------------------------------------------------------- /actions/account.js: -------------------------------------------------------------------------------- 1 | import { push } from 'react-router-redux' 2 | import * as auth from '../auth' 3 | import { initLeanCloud, deinitLeanCloud } from '../utils' 4 | 5 | /** 6 | * 登录 7 | */ 8 | 9 | export const LOG_IN_START = "LOG_IN_START" 10 | export const LOG_IN_SUCCESS = "LOG_IN_SUCCESS" 11 | export const LOG_IN_ERROR = "LOG_IN_ERROR" 12 | 13 | export function logInStart() { 14 | return {type: LOG_IN_START} 15 | } 16 | 17 | export function logInSuccess(username) { 18 | return {type: LOG_IN_SUCCESS, username} 19 | } 20 | 21 | export function logInError(error) { 22 | return {type: LOG_IN_ERROR, error} 23 | } 24 | 25 | export function logIn(password) { 26 | return dispatch => { 27 | dispatch(logInStart()) 28 | 29 | auth.logIn(password).then(function () { 30 | initLeanCloud() 31 | dispatch(logInSuccess()) 32 | dispatch(push('/')) 33 | }, function (error) { 34 | window.alert('密码错误') 35 | dispatch(logInError(error)) 36 | }) 37 | } 38 | } 39 | 40 | /** 41 | * 登出 42 | */ 43 | 44 | export const LOG_OUT = "LOG_OUT" 45 | 46 | export function logOut() { 47 | return dispatch => { 48 | auth.logOut() 49 | deinitLeanCloud() 50 | dispatch(push('/auth')) 51 | dispatch({type: LOG_OUT}) 52 | } 53 | } 54 | 55 | /** 56 | * 初始化登陆状态 57 | */ 58 | 59 | //export const INIT_AUTH_STATE = "INIT_AUTH_STATE" 60 | // 61 | //export function initAuthState() { 62 | // return {type: INIT_AUTH_STATE, authed: auth.loggedIn()} 63 | //} 64 | -------------------------------------------------------------------------------- /actions/broadcastSystemNotifications.js: -------------------------------------------------------------------------------- 1 | import AV from 'avoscloud-sdk' 2 | import { BroadcastSystemNotification } from '../models' 3 | 4 | /** 5 | * 获取所有系统通知 6 | */ 7 | 8 | export const FETCH_BROADCAST_SYSTEM_NOTIFICATIONS_START = "FETCH_BROADCAST_SYSTEM_NOTIFICATIONS_START" 9 | export const FETCH_BROADCAST_SYSTEM_NOTIFICATIONS_SUCCESS = "FETCH_BROADCAST_SYSTEM_NOTIFICATIONS_SUCCESS" 10 | export const FETCH_BROADCAST_SYSTEM_NOTIFICATIONS_ERROR = "FETCH_BROADCAST_SYSTEM_NOTIFICATIONS_ERROR" 11 | 12 | export function fetchBroadcastSystemNotificationsStart() { 13 | return {type: FETCH_BROADCAST_SYSTEM_NOTIFICATIONS_START} 14 | } 15 | 16 | export function fetchBroadcastSystemNotificationsSuccess(notifications) { 17 | return {type: FETCH_BROADCAST_SYSTEM_NOTIFICATIONS_SUCCESS, notifications} 18 | } 19 | 20 | export function fetchBroadcastSystemNotificationsError(error) { 21 | return {type: FETCH_BROADCAST_SYSTEM_NOTIFICATIONS_ERROR, error} 22 | } 23 | 24 | export function fetchBroadcastSystemNotifications(page = 1, perPage = 15) { 25 | return dispatch => { 26 | dispatch(fetchBroadcastSystemNotificationsStart()) 27 | 28 | BroadcastSystemNotification.fetchAll(page, perPage).then(function (notifications) { 29 | dispatch(fetchBroadcastSystemNotificationsSuccess(notifications)) 30 | }, function (error) { 31 | dispatch(fetchBroadcastSystemNotificationsError(error)) 32 | }) 33 | } 34 | } 35 | 36 | /** 37 | * 推送系统通知给所有人 38 | */ 39 | 40 | export const PUSH_BROADCAST_SYSTEM_NOTIFICATION_START = "PUSH_BROADCAST_SYSTEM_NOTIFICATION_START" 41 | export const PUSH_BROADCAST_SYSTEM_NOTIFICATION_SUCCESS = "PUSH_BROADCAST_SYSTEM_NOTIFICATION_SUCCESS" 42 | export const PUSH_BROADCAST_SYSTEM_NOTIFICATION_ERROR = "PUSH_BROADCAST_SYSTEM_NOTIFICATION_ERROR" 43 | 44 | export function pushBroadcastSystemNotificationStart() { 45 | return {type: PUSH_BROADCAST_SYSTEM_NOTIFICATION_START} 46 | } 47 | 48 | export function pushBroadcastSystemNotificationSuccess(notification) { 49 | return {type: PUSH_BROADCAST_SYSTEM_NOTIFICATION_SUCCESS, notification} 50 | } 51 | 52 | export function pushBroadcastSystemNotificationError(error) { 53 | return {type: PUSH_BROADCAST_SYSTEM_NOTIFICATION_ERROR, error} 54 | } 55 | 56 | export function pushBroadcastSystemNotification(content) { 57 | return dispatch => { 58 | dispatch(pushBroadcastSystemNotificationStart()) 59 | 60 | BroadcastSystemNotification.push(content).then(function (notification) { 61 | dispatch(pushBroadcastSystemNotificationSuccess(notification)) 62 | }, function (error) { 63 | dispatch(pushBroadcastSystemNotificationError(error)) 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /actions/dashboard.js: -------------------------------------------------------------------------------- 1 | import { fetchDashboardData as _fetchDashboardData } from '../models' 2 | 3 | /** 4 | * 获取dashboard数据 5 | */ 6 | 7 | export const FETCH_DASHBOARD_DATA_START = "FETCH_DASHBOARD_DATA_START" 8 | export const FETCH_DASHBOARD_DATA_SUCCESS = "FETCH_DASHBOARD_DATA_SUCCESS" 9 | export const FETCH_DASHBOARD_DATA_ERROR = "FETCH_DASHBOARD_DATA_ERROR" 10 | 11 | export function fetchDashboardDataStart() { 12 | return {type: FETCH_DASHBOARD_DATA_START} 13 | } 14 | 15 | export function fetchDashboardDataSuccess(usersCount, questionsCount, answersCount, reportsCount) { 16 | return {type: FETCH_DASHBOARD_DATA_SUCCESS, usersCount, questionsCount, answersCount, reportsCount} 17 | } 18 | 19 | export function fetchDashboardDataError(error) { 20 | return {type: FETCH_DASHBOARD_DATA_ERROR, error} 21 | } 22 | 23 | export function fetchDashboardData() { 24 | return dispatch => { 25 | dispatch(fetchDashboardDataStart()) 26 | 27 | _fetchDashboardData().then(function (usersCount, questionsCount, answersCount, reportsCount) { 28 | dispatch(fetchDashboardDataSuccess(usersCount, questionsCount, answersCount, reportsCount)) 29 | }, function (error) { 30 | dispatch(fetchDashboardDataError(error)) 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /actions/homeRecommendations.js: -------------------------------------------------------------------------------- 1 | import { HomeRecommendation } from '../models' 2 | 3 | /** 4 | * 获取首页推荐 5 | */ 6 | 7 | export const FETCH_HOME_RECOMMENDATIONS_START = "FETCH_HOME_RECOMMENDATIONS_START" 8 | export const FETCH_HOME_RECOMMENDATIONS_SUCCESS = "FETCH_HOME_RECOMMENDATIONS_SUCCESS" 9 | export const FETCH_HOME_RECOMMENDATIONS_ERROR = "FETCH_HOME_RECOMMENDATIONS_ERROR" 10 | 11 | export function fetchHomeRecommendationsStart() { 12 | return {type: FETCH_HOME_RECOMMENDATIONS_START} 13 | } 14 | 15 | export function fetchHomeRecommendationsSuccess(recommendations, page, totalCount) { 16 | return {type: FETCH_HOME_RECOMMENDATIONS_SUCCESS, recommendations, page, totalCount} 17 | } 18 | 19 | export function fetchHomeRecommendationsError(error) { 20 | return {type: FETCH_HOME_RECOMMENDATIONS_ERROR, error} 21 | } 22 | 23 | export function fetchHomeRecommendations(page = 1, perPage = 15) { 24 | return dispatch => { 25 | dispatch(fetchHomeRecommendationsStart()) 26 | 27 | HomeRecommendation.fetchAll(page, perPage).then(function (recommendations, totalCount) { 28 | dispatch(fetchHomeRecommendationsSuccess(recommendations, page, totalCount)) 29 | }, function (error) { 30 | dispatch(fetchHomeRecommendationsError(error)) 31 | }) 32 | } 33 | } 34 | 35 | /** 36 | * 移除首页推荐 37 | */ 38 | 39 | export const REMOVE_HOME_RECOMMENDATION_START = "REMOVE_HOME_RECOMMENDATION_START" 40 | export const REMOVE_HOME_RECOMMENDATION_SUCCESS = "REMOVE_HOME_RECOMMENDATION_SUCCESS" 41 | export const REMOVE_HOME_RECOMMENDATION_ERROR = "REMOVE_HOME_RECOMMENDATION_ERROR" 42 | 43 | export function removeHomeRecommendationStart() { 44 | return {type: REMOVE_HOME_RECOMMENDATION_START} 45 | } 46 | 47 | export function removeHomeRecommendationSuccess(recommendation) { 48 | return {type: REMOVE_HOME_RECOMMENDATION_SUCCESS, recommendation} 49 | } 50 | 51 | export function removeHomeRecommendationError(error) { 52 | return {type: REMOVE_HOME_RECOMMENDATION_ERROR, error} 53 | } 54 | 55 | export function removeHomeRecommendation(recommendation) { 56 | return dispatch => { 57 | dispatch(removeHomeRecommendationStart()) 58 | 59 | recommendation.destroy().then(function () { 60 | dispatch(removeHomeRecommendationSuccess(recommendation)) 61 | }, function (error) { 62 | dispatch(removeHomeRecommendationError(error)) 63 | }) 64 | } 65 | } 66 | 67 | /** 68 | * 更换首页推荐用户的背景图片 69 | */ 70 | 71 | export const UPDATE_HOME_RECOMMENDATION_USER_BACKGROUND_IMAGE_START = "UPDATE_HOME_RECOMMENDATION_USER_BACKGROUND_IMAGE_START" 72 | export const UPDATE_HOME_RECOMMENDATION_USER_BACKGROUND_IMAGE_SUCCESS = "UPDATE_HOME_RECOMMENDATION_USER_BACKGROUND_IMAGE_SUCCESS" 73 | export const UPDATE_HOME_RECOMMENDATION_USER_BACKGROUND_IMAGE_ERROR = "UPDATE_HOME_RECOMMENDATION_USER_BACKGROUND_IMAGE_ERROR" 74 | 75 | export function updateHomeRecommendationUserBackgroundImageStart() { 76 | return {type: UPDATE_HOME_RECOMMENDATION_USER_BACKGROUND_IMAGE_START} 77 | } 78 | 79 | export function updateHomeRecommendationUserBackgroundImageSuccess(recommendation) { 80 | return {type: UPDATE_HOME_RECOMMENDATION_USER_BACKGROUND_IMAGE_SUCCESS, recommendation} 81 | } 82 | 83 | export function updateHomeRecommendationUserBackgroundImageError(error) { 84 | return {type: UPDATE_HOME_RECOMMENDATION_USER_BACKGROUND_IMAGE_ERROR, error} 85 | } 86 | 87 | export function updateHomeRecommendationUserBackgroundImage(recommendation, file) { 88 | return dispatch => { 89 | dispatch(updateHomeRecommendationUserBackgroundImageStart()) 90 | 91 | recommendation.updateUserBackgroundImage(file).then(function () { 92 | dispatch(updateHomeRecommendationUserBackgroundImageSuccess(recommendation)) 93 | }, function (error) { 94 | dispatch(updateHomeRecommendationUserBackgroundImageError(error)) 95 | }) 96 | } 97 | } 98 | 99 | /** 100 | * 添加首页推荐 101 | */ 102 | 103 | export const ADD_HOME_RECOMMENDATION_START = "ADD_HOME_RECOMMENDATION_START" 104 | export const ADD_HOME_RECOMMENDATION_SUCCESS = "ADD_HOME_RECOMMENDATION_SUCCESS" 105 | export const ADD_HOME_RECOMMENDATION_ERROR = "ADD_HOME_RECOMMENDATION_ERROR" 106 | 107 | export function addHomeRecommendationStart() { 108 | return {type: ADD_HOME_RECOMMENDATION_START} 109 | } 110 | 111 | export function addHomeRecommendationSuccess(recommendation) { 112 | return {type: ADD_HOME_RECOMMENDATION_SUCCESS, recommendation} 113 | } 114 | 115 | export function addHomeRecommendationError(error) { 116 | return {type: ADD_HOME_RECOMMENDATION_ERROR, error} 117 | } 118 | 119 | export function addHomeRecommendation(kind, object) { 120 | return dispatch => { 121 | dispatch(addHomeRecommendationStart()) 122 | 123 | HomeRecommendation.add(kind, object).then(function (recommendation) { 124 | dispatch(addHomeRecommendationSuccess(recommendation)) 125 | }, function (error) { 126 | dispatch(addHomeRecommendationError(error)) 127 | }) 128 | } 129 | } 130 | 131 | /** 132 | * 置顶首页推荐对象 133 | */ 134 | 135 | export const TOP_HOME_RECOMMENDATION_START = "TOP_HOME_RECOMMENDATION_START" 136 | export const TOP_HOME_RECOMMENDATION_SUCCESS = "TOP_HOME_RECOMMENDATION_SUCCESS" 137 | export const TOP_HOME_RECOMMENDATION_ERROR = "TOP_HOME_RECOMMENDATION_ERROR" 138 | 139 | export function topHomeRecommendationStart() { 140 | return {type: TOP_HOME_RECOMMENDATION_START} 141 | } 142 | 143 | export function topHomeRecommendationSuccess(recommendation) { 144 | return {type: TOP_HOME_RECOMMENDATION_SUCCESS, recommendation} 145 | } 146 | 147 | export function topHomeRecommendationError(error) { 148 | return {type: TOP_HOME_RECOMMENDATION_ERROR, error} 149 | } 150 | 151 | export function topHomeRecommendation(recommendation) { 152 | return dispatch => { 153 | dispatch(topHomeRecommendationStart()) 154 | 155 | recommendation.top().then(function (recommendation) { 156 | dispatch(topHomeRecommendationSuccess(recommendation)) 157 | }, function (error) { 158 | dispatch(topHomeRecommendationError(error)) 159 | }) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /actions/invitationCodes.js: -------------------------------------------------------------------------------- 1 | import { InvitationCode } from '../models' 2 | 3 | /** 4 | * 获取邀请码 5 | */ 6 | 7 | export const FETCH_INVITATION_CODES_START = "FETCH_INVITATION_CODES_START" 8 | export const FETCH_INVITATION_CODES_SUCCESS = "FETCH_INVITATION_CODES_SUCCESS" 9 | export const FETCH_INVITATION_CODES_ERROR = "FETCH_INVITATION_CODES_ERROR" 10 | 11 | export function fetchInvitationCodesStart() { 12 | return {type: FETCH_INVITATION_CODES_START} 13 | } 14 | 15 | export function fetchInvitationCodesSuccess(page, codes, totalCount) { 16 | return {type: FETCH_INVITATION_CODES_SUCCESS, page, codes, totalCount} 17 | } 18 | 19 | export function fetchInvitationCodesError(error) { 20 | return {type: FETCH_INVITATION_CODES_ERROR, error} 21 | } 22 | 23 | export function fetchInvitationCodes(page = 1, perPage = 15) { 24 | return dispatch => { 25 | dispatch(fetchInvitationCodesStart()) 26 | 27 | InvitationCode.fetchAll(page, perPage).then(function (codes, totalCount) { 28 | dispatch(fetchInvitationCodesSuccess(page, codes, totalCount)) 29 | }, function (error) { 30 | dispatch(fetchInvitationCodesError(error)) 31 | }) 32 | } 33 | } 34 | 35 | /** 36 | * 标记邀请码为已发送 37 | */ 38 | 39 | export const MARK_INVITATION_CODE_SENDED_START = "MARK_INVITATION_CODE_SENDED_START" 40 | export const MARK_INVITATION_CODE_SENDED_SUCCESS = "MARK_INVITATION_CODE_SENDED_SUCCESS" 41 | export const MARK_INVITATION_CODE_SENDED_ERROR = "MARK_INVITATION_CODE_SENDED_ERROR" 42 | 43 | export function markInvitationSendedStart() { 44 | return {type: MARK_INVITATION_CODE_SENDED_START} 45 | } 46 | 47 | export function markInvitationSendedSuccess(code) { 48 | return {type: MARK_INVITATION_CODE_SENDED_SUCCESS, code} 49 | } 50 | 51 | export function markInvitationSendedError(error) { 52 | return {type: MARK_INVITATION_CODE_SENDED_ERROR, error} 53 | } 54 | 55 | export function markInvitationSended(code, sendedTo) { 56 | return dispatch => { 57 | dispatch(markInvitationSendedStart()) 58 | 59 | code.markSended(sendedTo).then(function () { 60 | dispatch(markInvitationSendedSuccess(code)) 61 | }, function (error) { 62 | dispatch(markInvitationSendedError(error)) 63 | }) 64 | } 65 | } 66 | 67 | /** 68 | * 生成新邀请码 69 | */ 70 | 71 | export const GENERATE_INVITATION_CODES_START = "GENERATE_INVITATION_CODES_START" 72 | export const GENERATE_INVITATION_CODES_SUCCESS = "GENERATE_INVITATION_CODES_SUCCESS" 73 | export const GENERATE_INVITATION_CODES_ERROR = "GENERATE_INVITATION_CODES_ERROR" 74 | 75 | export function generateInvitationCodesStart() { 76 | return {type: GENERATE_INVITATION_CODES_START} 77 | } 78 | 79 | export function generateInvitationCodesSuccess(count, codes) { 80 | return {type: GENERATE_INVITATION_CODES_SUCCESS, count, codes} 81 | } 82 | 83 | export function generateInvitationCodesError(error) { 84 | return {type: GENERATE_INVITATION_CODES_ERROR, error} 85 | } 86 | 87 | export function generateInvitationCodes(count = 5) { 88 | return dispatch => { 89 | dispatch(generateInvitationCodesStart()) 90 | 91 | InvitationCode.generateCodes(count).then(function (codes) { 92 | dispatch(generateInvitationCodesSuccess(count, codes)) 93 | }, function (error) { 94 | dispatch(generateInvitationCodesError(error)) 95 | }); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /actions/question.js: -------------------------------------------------------------------------------- 1 | import { Question } from '../models' 2 | 3 | /** 4 | * 获取全部提问 5 | */ 6 | 7 | export const FETCH_QUESTIONS_START = "FETCH_QUESTIONS_START" 8 | export const FETCH_QUESTIONS_SUCCESS = "FETCH_QUESTIONS_SUCCESS" 9 | export const FETCH_QUESTIONS_ERROR = "FETCH_QUESTIONS_ERROR" 10 | 11 | export function fetchQuestionsStart() { 12 | return {type: FETCH_QUESTIONS_START} 13 | } 14 | 15 | export function fetchQuestionsSuccess(questions, page, totalCount) { 16 | return {type: FETCH_QUESTIONS_SUCCESS, questions, page, totalCount} 17 | } 18 | 19 | export function fetchQuestionsError(error) { 20 | return {type: FETCH_QUESTIONS_ERROR, error} 21 | } 22 | 23 | export function fetchQuestions(page = 1, perPage = 15) { 24 | return dispatch => { 25 | dispatch(fetchQuestionsStart()) 26 | 27 | Question.fetchAll(page, perPage).then(function (questions, totalCount) { 28 | dispatch(fetchQuestionsSuccess(questions, page, totalCount)) 29 | }, function (error) { 30 | dispatch(fetchQuestionsError(error)) 31 | }) 32 | } 33 | } 34 | 35 | /** 36 | * 获取匿名提问 37 | */ 38 | 39 | export const FETCH_PENDING_ANONYMOUS_QUESTIONS_START = "FETCH_PENDING_ANONYMOUS_QUESTIONS_START" 40 | export const FETCH_PENDING_ANONYMOUS_QUESTIONS_SUCCESS = "FETCH_PENDING_ANONYMOUS_QUESTIONS_SUCCESS" 41 | export const FETCH_PENDING_ANONYMOUS_QUESTIONS_ERROR = "FETCH_PENDING_ANONYMOUS_QUESTIONS_ERROR" 42 | 43 | export function fetchPendingAnonymousQuestionsStart() { 44 | return {type: FETCH_PENDING_ANONYMOUS_QUESTIONS_START} 45 | } 46 | 47 | export function fetchPendingAnonymousQuestionsSuccess(questions, page, totalCount) { 48 | return {type: FETCH_PENDING_ANONYMOUS_QUESTIONS_SUCCESS, questions, page, totalCount} 49 | } 50 | 51 | export function fetchPendingAnonymousQuestionsError(error) { 52 | return {type: FETCH_PENDING_ANONYMOUS_QUESTIONS_ERROR, error} 53 | } 54 | 55 | export function fetchPendingAnonymousQuestions(page = 1, perPage = 15) { 56 | return dispatch => { 57 | dispatch(fetchPendingAnonymousQuestionsStart()) 58 | 59 | Question.fetchPendingAnonymousQuestions(page, perPage).then(function (questions, totalCount) { 60 | dispatch(fetchPendingAnonymousQuestionsSuccess(questions, page, totalCount)) 61 | }, function (error) { 62 | dispatch(fetchPendingAnonymousQuestionsError(error)) 63 | }) 64 | } 65 | } 66 | 67 | /** 68 | * 获取全部回答 69 | */ 70 | 71 | export const FETCH_ANSWERS_START = "FETCH_ANSWERS_START" 72 | export const FETCH_ANSWERS_SUCCESS = "FETCH_ANSWERS_SUCCESS" 73 | export const FETCH_ANSWERS_ERROR = "FETCH_ANSWERS_ERROR" 74 | 75 | export function fetchAnswersStart() { 76 | return {type: FETCH_ANSWERS_START} 77 | } 78 | 79 | export function fetchAnswersSuccess(answers, page, totalCount) { 80 | return {type: FETCH_ANSWERS_SUCCESS, answers, page, totalCount} 81 | } 82 | 83 | export function fetchAnswersError(error) { 84 | return {type: FETCH_ANSWERS_ERROR, error} 85 | } 86 | 87 | export function fetchAnswers(page = 1, perPage = 15) { 88 | return dispatch => { 89 | dispatch(fetchAnswersStart()) 90 | 91 | Question.fetchAnswers(page, perPage).then(function (answers, totalCount) { 92 | dispatch(fetchAnswersSuccess(answers, page, totalCount)) 93 | }, function (error) { 94 | dispatch(fetchAnswersError(error)) 95 | }) 96 | } 97 | } 98 | 99 | /** 100 | * 更新问题 101 | */ 102 | 103 | export const UPDATE_QUESTION_START = "UPDATE_QUESTION_START" 104 | export const UPDATE_QUESTION_SUCCESS = "UPDATE_QUESTION_SUCCESS" 105 | export const UPDATE_QUESTION_ERROR = "UPDATE_QUESTION_ERROR" 106 | 107 | export function updateQuestionStart() { 108 | return {type: UPDATE_QUESTION_START} 109 | } 110 | 111 | export function updateQuestionSuccess(question) { 112 | return {type: UPDATE_QUESTION_SUCCESS, question} 113 | } 114 | 115 | export function updateQuestionError(error) { 116 | return {type: UPDATE_QUESTION_ERROR, error} 117 | } 118 | 119 | export function updateQuestion(question, title) { 120 | return dispatch => { 121 | dispatch(updateQuestionStart()) 122 | 123 | question.update(title).then(function (question) { 124 | dispatch(updateQuestionSuccess(question)) 125 | }, function (error) { 126 | dispatch(updateQuestionError(error)) 127 | }) 128 | } 129 | } 130 | 131 | /** 132 | * 删除问题 133 | */ 134 | 135 | export const DELETE_QUESTION_START = "DELETE_QUESTION_START" 136 | export const DELETE_QUESTION_SUCCESS = "DELETE_QUESTION_SUCCESS" 137 | export const DELETE_QUESTION_ERROR = "DELETE_QUESTION_ERROR" 138 | 139 | export function deleteQuestionStart() { 140 | return {type: DELETE_QUESTION_START} 141 | } 142 | 143 | export function deleteQuestionSuccess(question) { 144 | return {type: DELETE_QUESTION_SUCCESS, question} 145 | } 146 | 147 | export function deleteQuestionError(error) { 148 | return {type: DELETE_QUESTION_ERROR, error} 149 | } 150 | 151 | export function deleteQuestion(question) { 152 | return dispatch => { 153 | dispatch(deleteQuestionStart()) 154 | 155 | question.destroy().then(function () { 156 | dispatch(deleteQuestionSuccess(question)) 157 | }, function (error) { 158 | dispatch(deleteQuestionError(error)) 159 | }) 160 | } 161 | } 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /actions/reportedQuestions.js: -------------------------------------------------------------------------------- 1 | import { ReportQuestion } from '../models' 2 | 3 | /** 4 | * 获取举报内容 5 | */ 6 | 7 | export const FETCH_REPORTED_QUESTIONS_START = "FETCH_REPORTED_QUESTIONS_START" 8 | export const FETCH_REPORTED_QUESTIONS_SUCCESS = "FETCH_REPORTED_QUESTIONS_SUCCESS" 9 | export const FETCH_REPORTED_QUESTIONS_ERROR = "FETCH_REPORTED_QUESTIONS_ERROR" 10 | 11 | export function fetchReportedQuestionsStart() { 12 | return {type: FETCH_REPORTED_QUESTIONS_START} 13 | } 14 | 15 | export function fetchReportedQuestionsSuccess(questions, page, totalCount) { 16 | return {type: FETCH_REPORTED_QUESTIONS_SUCCESS, questions, page, totalCount} 17 | } 18 | 19 | export function fetchReportedQuestionsError(error) { 20 | return {type: FETCH_REPORTED_QUESTIONS_ERROR, error} 21 | } 22 | 23 | export function fetchReportedQuestions(page = 1, perPage = 15) { 24 | return dispatch => { 25 | dispatch(fetchReportedQuestionsStart()) 26 | 27 | ReportQuestion.fetchAll(page, perPage).then(function (reportedQuestions, totalCount) { 28 | dispatch(fetchReportedQuestionsSuccess(reportedQuestions, page, totalCount)) 29 | }, function (error) { 30 | dispatch(fetchReportedQuestionsError(error)) 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /actions/users.js: -------------------------------------------------------------------------------- 1 | import { User } from '../models' 2 | 3 | /** 4 | * 获取用户 5 | */ 6 | 7 | export const FETCH_USERS_START = "FETCH_USERS_START" 8 | export const FETCH_USERS_SUCCESS = "FETCH_USERS_SUCCESS" 9 | export const FETCH_USERS_ERROR = "FETCH_USERS_ERROR" 10 | 11 | export function fetchUsersStart(page = 1) { 12 | return {type: FETCH_USERS_START, page: page} 13 | } 14 | 15 | export function fetchUsersSuccess(users, page, totalCount) { 16 | return {type: FETCH_USERS_SUCCESS, users, page, totalCount} 17 | } 18 | 19 | export function fetchUsersError(error) { 20 | return {type: FETCH_USERS_ERROR, error} 21 | } 22 | 23 | export function fetchUsers(page = 1, perPage = 15) { 24 | return dispatch => { 25 | dispatch(fetchUsersStart(page)) 26 | 27 | User.fetchAll(page, perPage).then(function (users, totalCount) { 28 | dispatch(fetchUsersSuccess(users, page, totalCount)) 29 | }, function (error) { 30 | dispatch(fetchUsersError(error)) 31 | }) 32 | } 33 | } 34 | 35 | /** 36 | * 开通用户主页 37 | */ 38 | 39 | export const OPEN_USER_QA_PAGE_START = "OPEN_USER_QA_PAGE_START" 40 | export const OPEN_USER_QA_PAGE_SUCCESS = "OPEN_USER_QA_PAGE_SUCCESS" 41 | export const OPEN_USER_QA_PAGE_ERROR = "OPEN_USER_QA_PAGE_ERROR" 42 | 43 | export function openUserQAPageStart() { 44 | return {type: OPEN_USER_QA_PAGE_START} 45 | } 46 | 47 | export function openUserQAPageSuccess(user) { 48 | return {type: OPEN_USER_QA_PAGE_SUCCESS, user} 49 | } 50 | 51 | export function openUserQAPageError(error) { 52 | return {type: OPEN_USER_QA_PAGE_ERROR, error} 53 | } 54 | 55 | export function openUserQAPage(user) { 56 | return (dispatch) => { 57 | dispatch(openUserQAPageStart()) 58 | 59 | user.openQAPage().then(function () { 60 | dispatch(openUserQAPageSuccess(user)) 61 | }, function (error) { 62 | dispatch(openUserQAPageError(error)) 63 | }) 64 | } 65 | } 66 | 67 | /** 68 | * 关闭用户主页 69 | */ 70 | 71 | export const CLOSE_USER_QA_PAGE_START = "CLOSE_USER_QA_PAGE_START" 72 | export const CLOSE_USER_QA_PAGE_SUCCESS = "CLOSE_USER_QA_PAGE_SUCCESS" 73 | export const CLOSE_USER_QA_PAGE_ERROR = "CLOSE_USER_QA_PAGE_ERROR" 74 | 75 | export function closeUserQAPageStart() { 76 | return {type: CLOSE_USER_QA_PAGE_START} 77 | } 78 | 79 | export function closeUserQAPageSuccess(user) { 80 | return {type: CLOSE_USER_QA_PAGE_SUCCESS, user} 81 | } 82 | 83 | export function closeUserQAPageError(error) { 84 | return {type: CLOSE_USER_QA_PAGE_ERROR, error} 85 | } 86 | 87 | export function closeUserQAPage(user) { 88 | return (dispatch) => { 89 | dispatch(closeUserQAPageStart()) 90 | 91 | user.closeQAPage().then(function () { 92 | dispatch(closeUserQAPageSuccess(user)) 93 | }, function (error) { 94 | dispatch(closeUserQAPageError(error)) 95 | }) 96 | } 97 | } 98 | 99 | /** 100 | * 编辑用户资料 101 | */ 102 | 103 | export const UPDATE_USER_START = "UPDATE_USER_START" 104 | export const UPDATE_USER_SUCCESS = "UPDATE_USER_SUCCESS" 105 | export const UPDATE_USER_ERROR = "UPDATE_USER_ERROR" 106 | 107 | export function updateUserStart() { 108 | return {type: UPDATE_USER_START} 109 | } 110 | 111 | export function updateUserSuccess(user) { 112 | return {type: UPDATE_USER_SUCCESS, user} 113 | } 114 | 115 | export function updateUserError(error) { 116 | return {type: UPDATE_USER_ERROR, error} 117 | } 118 | 119 | export function updateUser(user, name, title, achievements, desc, mobilePhoneNumber, tags) { 120 | return (dispatch) => { 121 | dispatch(updateUserStart()) 122 | 123 | user.update(name, title, achievements, desc, mobilePhoneNumber, tags).then(function (user) { 124 | dispatch(updateUserSuccess(user)) 125 | }, function (error) { 126 | dispatch(updateUserError(error)) 127 | }) 128 | } 129 | } 130 | 131 | 132 | /** 133 | * 搜索用户 134 | */ 135 | 136 | export const SEARCH_USERS_START = "SEARCH_USERS_START" 137 | export const SEARCH_USERS_SUCCESS = "SEARCH_USERS_SUCCESS" 138 | export const SEARCH_USERS_ERROR = "SEARCH_USERS_ERROR" 139 | 140 | export function searchUsersStart() { 141 | return {type: SEARCH_USERS_START} 142 | } 143 | 144 | export function searchUsersSuccess(users, page, totalCount) { 145 | return {type: SEARCH_USERS_SUCCESS, users, page, totalCount} 146 | } 147 | 148 | export function searchUsersError(error) { 149 | return {type: SEARCH_USERS_ERROR, error} 150 | } 151 | 152 | export function searchUsers(keyword, page = 1, perPage = 15) { 153 | return dispatch => { 154 | dispatch(searchUsersStart()) 155 | 156 | User.searchByName(keyword, page, perPage).then(function (users, totalCount) { 157 | dispatch(searchUsersSuccess(users, page, totalCount)) 158 | }, function (error) { 159 | dispatch(searchUsersError(error)) 160 | }) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /auth.js: -------------------------------------------------------------------------------- 1 | import AV from 'avoscloud-sdk' 2 | 3 | export function logIn(password) { 4 | return fetch('https://12345.上山打老虎', { 5 | method: "POST", 6 | headers: { 7 | "Content-Type": "application/x-www-form-urlencoded" 8 | }, 9 | body: `password=${password}` 10 | }).then(function (response) { 11 | return response.json() 12 | }).then(function (jsonResponse) { 13 | return new AV.Promise(function (resolve, reject) { 14 | if (jsonResponse.authed) { 15 | localStorage.setItem('appId', jsonResponse.appId) 16 | localStorage.setItem('appKey', jsonResponse.appKey) 17 | localStorage.setItem('appMasterKey', jsonResponse.appMasterKey) 18 | 19 | resolve('LogIn Successfully.') 20 | } else { 21 | reject('Password is incorrect.') 22 | } 23 | }) 24 | }) 25 | } 26 | 27 | export function loggedIn() { 28 | return localStorage.getItem('appId') && localStorage.getItem('appKey') && localStorage.getItem('appMasterKey') 29 | } 30 | 31 | export function logOut() { 32 | localStorage.removeItem('appId') 33 | localStorage.removeItem('appKey') 34 | localStorage.removeItem('appMasterKey') 35 | } 36 | -------------------------------------------------------------------------------- /components/account/signin.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | -------------------------------------------------------------------------------- /components/homeRecommendations/AddHomeRecommendationModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import BaseModal from '../public/BaseFormModal' 4 | import UserSelector from './UserSelector' 5 | import { User } from '../../models' 6 | 7 | @Radium 8 | export default class AddHomeRecommendationModal extends React.Component { 9 | static propTypes = { 10 | closeModal: React.PropTypes.func.isRequired, 11 | addHomeRecommendation: React.PropTypes.func.isRequired, 12 | modalIsOpen: React.PropTypes.bool.isRequired 13 | } 14 | 15 | state = { 16 | searchUsers: [], 17 | targetHomeRecommendation: null 18 | } 19 | 20 | handleSearch = () => { 21 | const searchWord = this.refs.searchWord.value.trim() 22 | 23 | if (searchWord === "") { 24 | this.setState({searchUsers: []}) 25 | } else { 26 | User.searchOpenedUserByName(searchWord).then(function (users) { 27 | this.setState({searchUsers: users}) 28 | }.bind(this)) 29 | } 30 | } 31 | 32 | selectUser = (user) => { 33 | this.refs.searchWord.value = "" 34 | this.setState({ 35 | searchUsers: [], 36 | targetHomeRecommendation: user 37 | }) 38 | } 39 | 40 | handleSubmit = () => { 41 | const { addHomeRecommendation } = this.props 42 | 43 | if (!this.state.targetHomeRecommendation) { 44 | window.alert("请选择推荐对象") 45 | return 46 | } 47 | 48 | this.closeModal() 49 | addHomeRecommendation("user", this.state.targetHomeRecommendation) 50 | } 51 | 52 | closeModal = ()=> { 53 | const { closeModal } = this.props 54 | 55 | this.refs.searchWord.value = "" 56 | 57 | this.setState({ 58 | searchUsers: [], 59 | targetHomeRecommendation: null 60 | }) 61 | 62 | closeModal() 63 | } 64 | 65 | render() { 66 | const { closeModal, modalIsOpen } = this.props 67 | 68 | return ( 69 | 74 |
75 |
76 | 77 |
78 | 79 | 80 | 83 | 84 |
85 |
86 | 87 | 90 | 91 |
92 | 93 |

94 | {this.state.targetHomeRecommendation 95 | ? "用户 - " + this.state.targetHomeRecommendation.get('name') 96 | : "无"}

97 |
98 | 99 |
100 | ) 101 | } 102 | } 103 | 104 | const styles = {} 105 | -------------------------------------------------------------------------------- /components/homeRecommendations/HomeRecommendationsList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { friendlyTimeWithLineBreak } from '../../filters' 4 | import UserHomeRecommendation from './UserHomeRecommendation' 5 | 6 | @Radium 7 | export default class HomeRecommendationsList extends React.Component { 8 | static propTypes = { 9 | homeRecommendations: React.PropTypes.array.isRequired, 10 | removeHomeRecommendation: React.PropTypes.func.isRequired, 11 | selectImageFile: React.PropTypes.func.isRequired, 12 | topHomeRecommendation: React.PropTypes.func.isRequired 13 | } 14 | 15 | removeHomeRecommendation = (homeRecommendation) => { 16 | const { removeHomeRecommendation } = this.props 17 | 18 | if (window.confirm('确认删除该首页推荐?')) { 19 | removeHomeRecommendation(homeRecommendation) 20 | } 21 | } 22 | 23 | render() { 24 | const { homeRecommendations, selectImageFile, topHomeRecommendation } = this.props 25 | const rows = homeRecommendations.map(homeRecommendation => { 26 | let homeRecommendationNode = null 27 | 28 | if (homeRecommendation.get('kind') === 'user') { 29 | homeRecommendationNode = () 30 | } 31 | 32 | return ( 33 | 34 | {generateKindString(homeRecommendation.get('kind'))} 35 | {homeRecommendationNode} 36 | {friendlyTimeWithLineBreak(homeRecommendation.createdAt)} 37 | 38 |
39 | 42 | 45 | 48 | 49 | {/**/} 53 |
54 | 55 | 56 | ) 57 | }) 58 | 59 | return ( 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | {rows} 70 |
类型推荐对象推荐于操作
71 | ) 72 | } 73 | } 74 | 75 | function generateKindString(kind) { 76 | switch (kind) { 77 | case "user": 78 | return "用户" 79 | default: 80 | return "" 81 | } 82 | } 83 | 84 | const styles = { 85 | kindCell: { 86 | minWidth: '100px' 87 | }, 88 | userCell: { 89 | paddingBottom: '12px' 90 | }, 91 | timeCell: { 92 | minWidth: '100px' 93 | }, 94 | reorder: { 95 | ':hover': { 96 | cursor: "move" 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /components/homeRecommendations/UserHomeRecommendation.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { browserHistory } from 'react-router' 4 | 5 | @Radium 6 | export default class UserHomeRecommendation extends React.Component { 7 | static propTypes = { 8 | homeRecommendation: React.PropTypes.object.isRequired 9 | } 10 | 11 | render() { 12 | const { homeRecommendation } = this.props 13 | const user = homeRecommendation.get('user') 14 | const backgroundImageUrl = user.get('userCardBackgroundImage') 15 | ? user.get('userCardBackgroundImage').url() 16 | : user.get('avatar').url() 17 | 18 | return ( 19 |
20 |
{user.get('achievements')}
21 |
browserHistory.push(`/user/${user.id}`)} 22 | style={[ 23 | {backgroundImage: 'linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8)), url(' + backgroundImageUrl + ')'}, 24 | styles.backgroundImageWap 25 | ]}> 26 |
27 |
{user.get('name')}
28 |
{user.get('title')}
29 |
30 |
31 |
32 | {user.get('tags').join(', ')} 33 | {user.get('answeredQuestionsCount')} 问答 34 |
35 |
36 | ) 37 | } 38 | } 39 | 40 | const styles = { 41 | achievements: { 42 | fontWeight: 'bold' 43 | }, 44 | backgroundImageWap: { 45 | marginTop: '5px', 46 | width: '250px', 47 | height: '125px', 48 | color: 'white', 49 | backgroundPosition: 'center', 50 | backgroundRepeat: 'no-repeat', 51 | backgroundSize: 'cover', 52 | position: 'relative', 53 | ':hover': { 54 | cursor: 'pointer' 55 | } 56 | }, 57 | nameTitleWap: { 58 | position: 'absolute', 59 | bottom: '10px', 60 | left: '12px' 61 | }, 62 | name: { 63 | fontWeight: '1000', 64 | fontSize: '15px' 65 | }, 66 | title: { 67 | fontSize: '12px', 68 | marginTop: '3px' 69 | }, 70 | bottomWap: { 71 | marginTop: '5px', 72 | fontSize: '12px', 73 | color: 'gray' 74 | }, 75 | tags: {}, 76 | answeredQuestionsCount: { 77 | marginLeft: '20px' 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /components/homeRecommendations/UserSelector.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | 4 | @Radium 5 | export default class UserSelector extends React.Component { 6 | static propTypes = { 7 | users: React.PropTypes.array.isRequired, 8 | selectUser: React.PropTypes.func.isRequired 9 | } 10 | 11 | render() { 12 | const { users, selectUser } = this.props 13 | const userNodes = users.map((user, index, array) => { 14 | return ( 15 |
19 |
20 | 22 | {user.get('name')} 23 |
24 |
25 | 28 |
29 |
30 | ) 31 | }) 32 | 33 | return ( 34 |
{userNodes}
35 | ) 36 | } 37 | } 38 | 39 | const styles = { 40 | wap: { 41 | maxHeight: '200px', 42 | overflowY: 'auto' 43 | }, 44 | row: { 45 | ':hover': { 46 | backgroundColor: '#EEE' 47 | } 48 | }, 49 | userName: { 50 | marginLeft: '5px' 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /components/invitationCodes/InvitationCodesList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { browserHistory, Link } from 'react-router' 4 | import { friendlyTimeWithLineBreak } from '../../filters' 5 | 6 | @Radium 7 | export default class InvitationCodesList extends React.Component { 8 | static propTypes = { 9 | invitationCodes: React.PropTypes.array.isRequired, 10 | openModal: React.PropTypes.func.isRequired 11 | } 12 | 13 | render() { 14 | const { invitationCodes, openModal } = this.props 15 | const rows = invitationCodes.map(code => { 16 | return ( 17 | 18 | {code.get('code')} 19 | {generateCodeKind(code)} 20 | {generateCodeCreator(code)} 21 | {code.get('sended') ? () : null} 22 | {friendlyTimeWithLineBreak(code.get('sendedAt'))} 23 | {code.get('sendedTo')} 24 | {friendlyTimeWithLineBreak(code.createdAt)} 25 | {friendlyTimeWithLineBreak(code.get('usedAt'))} 26 | 27 | {code.get('user') 28 | ? {code.get('user').get('name')} 29 | : null} 30 | 31 | 32 | 33 |
34 | 37 |
38 |
39 | 40 | 41 | ) 42 | }) 43 | 44 | return ( 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {rows} 61 |
邀请码类型创建者已发送发送于发送对象创建于使用于注册用户操作
62 | ) 63 | } 64 | } 65 | 66 | function generateCodeKind(code) { 67 | let kind = "" 68 | 69 | switch (code.get('kind')) { 70 | case 1: 71 | kind = "提问被回答" 72 | break 73 | case 2: 74 | kind = "用户生成" 75 | break 76 | case 3: 77 | kind = "管理员生成" 78 | break 79 | } 80 | 81 | return kind 82 | } 83 | 84 | function generateCodeCreator(code) { 85 | switch (code.get('kind')) { 86 | case 1: 87 | return "system" 88 | case 2: 89 | return ( 90 | {code.get('createdBy').get('name')} 91 | ) 92 | case 3: 93 | return "admin" 94 | default: 95 | return "" 96 | } 97 | } 98 | 99 | const styles = { 100 | unusedCodeCell: { 101 | fontWeight: 'bold' 102 | }, 103 | usedCodeCell: { 104 | textDecoration: 'line-through', 105 | color: 'lightgray' 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /components/invitationCodes/MarkInvitationCodeSendedModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import BaseModal from '../public/BaseFormModal' 4 | 5 | @Radium 6 | export default class MarkInvitationCodeSendedModal extends React.Component { 7 | state = { 8 | sendedTo: "", 9 | code: null 10 | } 11 | 12 | static propTypes = { 13 | code: React.PropTypes.object, 14 | closeModal: React.PropTypes.func.isRequired, 15 | markSended: React.PropTypes.func.isRequired, 16 | modalIsOpen: React.PropTypes.bool.isRequired 17 | } 18 | 19 | handleSendedToChange = (e) => { 20 | this.setState({sendedTo: e.target.value}) 21 | } 22 | 23 | handleSubmit = () => { 24 | const { markSended, closeModal } = this.props 25 | 26 | closeModal() 27 | markSended(this.state.code, this.state.sendedTo) 28 | this.setState({code: null, sendedTo: ""}) 29 | } 30 | 31 | componentWillReceiveProps = (nextProps) => { 32 | if (nextProps.code && nextProps.modalIsOpen) { 33 | this.setState({ 34 | code: nextProps.code 35 | }) 36 | } 37 | } 38 | 39 | render() { 40 | const { closeModal, modalIsOpen } = this.props 41 | return ( 42 | 47 |
48 |
49 | 50 |

{this.state.code ? this.state.code.get('code') : null}

51 |
52 |
53 | 54 | 56 |

注:人名,手机号,邮箱... 按需填写,格式不限

57 |
58 |
59 |
60 | ) 61 | } 62 | } 63 | 64 | const styles = {} 65 | -------------------------------------------------------------------------------- /components/notification/BroadcastSystemNotificationsList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { friendlyTimeWithLineBreak } from '../../filters' 4 | 5 | @Radium 6 | export default class BroadcastSystemNotificationsList extends React.Component { 7 | static propTypes = { 8 | notifications: React.PropTypes.array.isRequired 9 | } 10 | 11 | render() { 12 | const { notifications } = this.props 13 | const rows = notifications.map(notification => { 14 | return ( 15 | 16 | 17 | {notification.get('content')} 18 | 19 | 20 | {friendlyTimeWithLineBreak(notification.createdAt)} 21 | 22 | 23 | ) 24 | }) 25 | 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {rows} 35 |
推送于内容
36 | ) 37 | } 38 | } 39 | 40 | const styles = {} -------------------------------------------------------------------------------- /components/public/ActiveNavItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { Link } from 'react-router' 4 | 5 | @Radium 6 | export default class ActiveNavItem extends React.Component { 7 | static propTypes = { 8 | path: React.PropTypes.string.isRequired, 9 | text: React.PropTypes.string.isRequired, 10 | currentPath: React.PropTypes.string.isRequired 11 | } 12 | 13 | render() { 14 | return ( 15 |
  • 16 | {this.props.text} 17 |
  • 18 | ) 19 | } 20 | } 21 | 22 | const styles = {} 23 | -------------------------------------------------------------------------------- /components/public/BaseFormModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import Modal from "react-modal" 4 | 5 | @Radium 6 | export default class BaseFormModal extends React.Component { 7 | static defaultProps = { 8 | submitText: "提交" 9 | } 10 | 11 | static propTypes = { 12 | title: React.PropTypes.string.isRequired, 13 | closeModal: React.PropTypes.func.isRequired, 14 | handleSubmit: React.PropTypes.func.isRequired, 15 | modalIsOpen: React.PropTypes.bool.isRequired, 16 | children: React.PropTypes.element.isRequired, 17 | submitText: React.PropTypes.string 18 | } 19 | 20 | render() { 21 | const { title, closeModal, handleSubmit, submitText, modalIsOpen } = this.props 22 | 23 | return ( 24 | 29 | 30 |
    31 |
    32 | 36 |

    {title}

    37 |
    38 |
    39 | {this.props.children} 40 |
    41 |
    42 | 43 | 44 |
    45 |
    46 |
    47 | ) 48 | } 49 | } 50 | 51 | const styles = { 52 | overlay: { 53 | zIndex: 10000 54 | }, 55 | content: { 56 | position: null, 57 | top: null, 58 | left: null, 59 | right: null, 60 | bottom: null, 61 | border: null, 62 | background: null, 63 | overflow: null, 64 | WebkitOverflowScrolling: null, 65 | borderRadius: null, 66 | padding: null, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /components/public/BaseInfoModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import Modal from "react-modal" 4 | 5 | @Radium 6 | export default class BaseInfoModal extends React.Component { 7 | static propTypes = { 8 | title: React.PropTypes.string.isRequired, 9 | closeModal: React.PropTypes.func.isRequired, 10 | modalIsOpen: React.PropTypes.bool.isRequired, 11 | children: React.PropTypes.element.isRequired, 12 | } 13 | 14 | render() { 15 | const { title, closeModal, modalIsOpen } = this.props 16 | 17 | return ( 18 | 23 | 24 |
    25 |
    26 | 30 |

    {title}

    31 |
    32 |
    33 | {this.props.children} 34 |
    35 |
    36 | 37 |
    38 |
    39 |
    40 | ) 41 | } 42 | } 43 | 44 | const styles = { 45 | overlay: { 46 | zIndex: 10000 47 | }, 48 | content: { 49 | position: null, 50 | top: null, 51 | left: null, 52 | right: null, 53 | bottom: null, 54 | border: null, 55 | background: null, 56 | overflow: null, 57 | WebkitOverflowScrolling: null, 58 | borderRadius: null, 59 | padding: null, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /components/public/Pagination.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import _ from 'lodash' 4 | 5 | @Radium 6 | export default class Pagination extends React.Component { 7 | static defaultProps = { 8 | currentPage: 1, 9 | perPage: 15, 10 | size: 'md' 11 | } 12 | 13 | static propTypes = { 14 | totalCount: React.PropTypes.number.isRequired, 15 | currentPage: React.PropTypes.number.isRequired, 16 | perPage: React.PropTypes.number, 17 | redirect: React.PropTypes.func.isRequired, 18 | size: React.PropTypes.oneOf(['sm', 'md', 'lg']) 19 | } 20 | 21 | get totalPage() { 22 | const { totalCount, perPage } = this.props 23 | 24 | return Math.ceil(totalCount / perPage) 25 | } 26 | 27 | redirectToOtherPage = (page) => { 28 | const { redirect, currentPage } = this.props 29 | 30 | if (page < 1 || page > this.totalPage || page === currentPage) { 31 | return 32 | } 33 | 34 | redirect(page) 35 | } 36 | 37 | render() { 38 | const { redirect, totalCount, currentPage, perPage } = this.props 39 | const totalPage = Math.ceil(totalCount / perPage) 40 | const sizeClassName = this.props.size === 'md' ? '' : `pagination-${this.props.size}` 41 | const pageNumbers = _.range(1, totalPage + 1).map(function (pageNumber) { 42 | return ( 43 |
  • 44 | this.redirectToOtherPage(pageNumber)}> 45 | {pageNumber} 46 | 47 |
  • 48 | ) 49 | }.bind(this)) 50 | 51 | return ( 52 | 69 | ) 70 | } 71 | } 72 | 73 | const styles = {} 74 | -------------------------------------------------------------------------------- /components/question/AnswersList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { Link } from 'react-router' 4 | import { friendlyTimeWithLineBreak, truncate } from '../../filters' 5 | 6 | @Radium 7 | export default class AnswersList extends React.Component { 8 | static propTypes = { 9 | answeredQuestions: React.PropTypes.array.isRequired, 10 | } 11 | 12 | render() { 13 | const { answeredQuestions } = this.props 14 | const rows = answeredQuestions.map(question => { 15 | const asker = question.get('asker') 16 | const user = question.get('user') 17 | 18 | return ( 19 | 20 | 21 | 22 | {asker ? ({asker.get('name')}) : "游客"} 23 | 24 | : 25 | {question.get('title')} 26 | 27 | {question.get('anonymous') ? : null} 28 | 29 | {user.get('name')} 30 | : 31 | {truncate(question.get('answer'), 60)} 32 | 33 | {question.get('commentsCount')} 34 | {question.get('likesCount')} 35 | {friendlyTimeWithLineBreak(question.createdAt)} 36 | {friendlyTimeWithLineBreak(question.get('answeredAt'))} 37 | 38 | ) 39 | }) 40 | 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {rows} 55 |
    问题匿名回答回复点赞提问于回答于
    56 | ) 57 | } 58 | } 59 | 60 | const styles = { 61 | titleCell: { 62 | maxWidth: '200px' 63 | }, 64 | answerCell: { 65 | maxWidth: '200px' 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /components/question/AskQuestionModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import Modal from "react-modal" 4 | import BaseModal from "../public/BaseFormModal" 5 | 6 | @Radium 7 | export default class AskQuestionModal extends React.Component { 8 | static propTypes = { 9 | askQuestion: React.PropTypes.func.isRequired, 10 | modalIsOpen: React.PropTypes.bool.isRequired, 11 | closeModal: React.PropTypes.func.isRequired 12 | } 13 | 14 | state = { 15 | title: "" 16 | } 17 | 18 | handleTitleChange = (e) => { 19 | this.setState({title: e.target.value}) 20 | } 21 | 22 | handleSubmit = () => { 23 | const { askQuestion, closeModal } = this.props 24 | 25 | this.setState({title: ''}) 26 | closeModal() 27 | askQuestion(this.state.title) 28 | } 29 | 30 | render() { 31 | const { modalIsOpen, closeModal } = this.props 32 | 33 | return ( 34 | 39 |
    40 |
    41 | 42 | 44 |
    45 |
    46 |
    47 | ) 48 | } 49 | } 50 | 51 | const styles = {} -------------------------------------------------------------------------------- /components/question/AskedQuestionsList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { Link } from 'react-router' 4 | import { friendlyTimeWithLineBreak, truncate } from '../../filters' 5 | 6 | @Radium 7 | export default class AskedQuestions extends React.Component { 8 | static propTypes = { 9 | questions: React.PropTypes.array.isRequired, 10 | openQuestionDetailsModal: React.PropTypes.func.isRequired, 11 | deleteQuestion: React.PropTypes.func.isRequired 12 | } 13 | 14 | render() { 15 | const { questions, openQuestionDetailsModal, deleteQuestion } = this.props 16 | const rows = questions.map(question => { 17 | const user = question.get('user') 18 | 19 | return ( 20 | 21 | 22 | 向  23 | 24 | {{user.get('name')}} 25 | 26 |  提问: 27 | {question.get('title')} 28 | 29 | {question.get('anonymous') ? : null} 30 | {question.get('answered') ? : null} 31 | 32 | {truncate(question.get('answer'), 60)} 33 | 34 | {question.get('commentsCount')} 35 | {question.get('likesCount')} 36 | {friendlyTimeWithLineBreak(question.createdAt)} 37 | {question.get('answered') ? friendlyTimeWithLineBreak(question.get('answeredAt')) : null} 38 | 39 |
    40 | 43 | 46 |
    47 | 48 | 49 | ) 50 | }) 51 | 52 | return ( 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {rows} 68 |
    问题匿名已回答回答回复点赞提问于回答于操作
    69 | ) 70 | } 71 | } 72 | 73 | const styles = { 74 | titleCell: { 75 | maxWidth: '200px' 76 | }, 77 | answerCell: { 78 | maxWidth: '200px' 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /components/question/DraftsList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { Link } from 'react-router' 4 | import { friendlyTimeWithLineBreak, truncate } from '../../filters' 5 | 6 | @Radium 7 | export default class DraftsList extends React.Component { 8 | static propTypes = { 9 | questions: React.PropTypes.array.isRequired, 10 | openQuestionDetailsModal: React.PropTypes.func.isRequired, 11 | deleteQuestion: React.PropTypes.func.isRequired 12 | } 13 | 14 | render() { 15 | const { questions, openQuestionDetailsModal, deleteQuestion } = this.props 16 | const rows = questions.map(question => { 17 | const asker = question.get('asker') 18 | 19 | return ( 20 | 21 | 22 | 23 | {asker ? ({asker.get('name')}) : "游客"} 24 | 25 | : 26 | {question.get('title')} 27 | 28 | {question.get('anonymous') ? : null} 29 | 30 | {truncate(question.get('draft'), 60)} 31 | 32 | {friendlyTimeWithLineBreak(question.createdAt)} 33 | {friendlyTimeWithLineBreak(question.get('draftedAt'))} 34 | 35 |
    36 | 39 | 42 |
    43 | 44 | 45 | ) 46 | }) 47 | 48 | return ( 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {rows} 61 |
    问题匿名草稿提问于最后编辑于操作
    62 | ) 63 | } 64 | } 65 | 66 | const styles = { 67 | titleCell: { 68 | maxWidth: '200px' 69 | }, 70 | answerCell: { 71 | maxWidth: '200px' 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /components/question/PendingQuestionsList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { Link } from 'react-router' 4 | import { friendlyTimeWithLineBreak, truncate } from '../../filters' 5 | 6 | @Radium 7 | export default class PendingQuestionsList extends React.Component { 8 | static propTypes = { 9 | questions: React.PropTypes.array.isRequired, 10 | deleteQuestion: React.PropTypes.func.isRequired, 11 | openUpdateQuestionModal: React.PropTypes.func.isRequired, 12 | openQuestionDetailsModal: React.PropTypes.func.isRequired, 13 | } 14 | 15 | render() { 16 | const { questions, deleteQuestion, openUpdateQuestionModal, openQuestionDetailsModal } = this.props 17 | const rows = questions.map(question => { 18 | const asker = question.get('asker') 19 | return ( 20 | 21 | 22 | 23 | {asker ? ({asker.get('name')}) : "游客"} 24 | 25 | : 26 | {question.get('title')} 27 | 28 | 29 | {question.get('drafted') ? truncate(question.get('draft'), 60) : null} 30 | 31 | {friendlyTimeWithLineBreak(question.createdAt)} 32 | 33 |
    34 | 37 | 40 | 43 |
    44 | 45 | 46 | ) 47 | }) 48 | 49 | return ( 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | {rows} 60 |
    问题草稿提问于操作
    61 | ) 62 | } 63 | } 64 | 65 | const styles = { 66 | titleCell: { 67 | maxWidth: '200px' 68 | }, 69 | draftCell: { 70 | maxWidth: '200px' 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /components/question/QuestionCommentsList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { Link } from 'react-router' 4 | import { friendlyTime } from '../../filters' 5 | 6 | @Radium 7 | export default class QuestionCommentsList extends React.Component { 8 | static propTypes = { 9 | comments: React.PropTypes.array.isRequired, 10 | removeComment: React.PropTypes.func.isRequired 11 | } 12 | 13 | handleRemove = (comment) => { 14 | const { removeComment } = this.props 15 | 16 | if (!window.confirm('确认删除?')) { 17 | return 18 | } 19 | 20 | removeComment(comment) 21 | } 22 | 23 | render() { 24 | const { comments } = this.props 25 | const rows = comments.map((comment) => { 26 | const user = comment.get('user') 27 | 28 | return ( 29 |
    30 |
    {friendlyTime(comment.createdAt)}
    31 | 32 |
    33 | 34 | {user.get('name')} 35 | 36 | :{comment.get('content')} 37 |
    38 | 39 | 43 |
    44 | ) 45 | }) 46 | 47 | return ( 48 |
    49 | {rows} 50 |
    51 | ) 52 | } 53 | } 54 | 55 | const styles = { 56 | wap: { 57 | padding: '5px 50px 5px 0', 58 | position: 'relative', 59 | ':hover': { 60 | backgroundColor: '#F2F2F2' 61 | } 62 | }, 63 | timeWap: { 64 | fontSize: '12px', 65 | color: '#AAAAAA' 66 | }, 67 | content: {}, 68 | btnRemove: { 69 | position: 'absolute', 70 | right: '5px', 71 | top: '5px' 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /components/question/QuestionDetailsModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import Modal from "react-modal" 4 | import { Link } from 'react-router' 5 | import BaseModal from "../public/BaseInfoModal" 6 | import QuestionCommentsList from './QuestionCommentsList' 7 | import Pagination from '../public/Pagination' 8 | import { friendlyTime, truncate } from '../../filters' 9 | 10 | const perPage = 10 11 | 12 | @Radium 13 | export default class QuestionDetailsModal extends React.Component { 14 | static propTypes = { 15 | question: React.PropTypes.object, 16 | modalIsOpen: React.PropTypes.bool.isRequired, 17 | closeModal: React.PropTypes.func.isRequired 18 | } 19 | 20 | state = { 21 | comments: [], 22 | commentsCurrentPage: 1, 23 | totalCommentsCount: this.props.question ? this.props.question.get('commentsCount') : 0 24 | } 25 | 26 | removeComment = (comment) => { 27 | comment.destroy().then(function () { 28 | this.setState({ 29 | totalCommentsCount: this.state.totalCommentsCount - 1, 30 | comments: this.state.comments.filter(item => item.id !== comment.id) 31 | }) 32 | }.bind(this)) 33 | } 34 | 35 | componentWillReceiveProps(nextProps) { 36 | this.fetchData(1, nextProps.question) 37 | } 38 | 39 | componentDidMount() { 40 | this.fetchData(1, this.props.question) 41 | } 42 | 43 | fetchData = (page, question) => { 44 | if (!question) { 45 | this.setState({ 46 | comments: [] 47 | }) 48 | return 49 | } 50 | 51 | this.setState({ 52 | totalCommentsCount: question.get('commentsCount') 53 | }) 54 | 55 | question.fetchComments(page, perPage).then(function (comments) { 56 | this.setState({comments, commentsCurrentPage: page}) 57 | }.bind(this)) 58 | } 59 | 60 | render() { 61 | const { question, modalIsOpen, closeModal } = this.props 62 | const asker = question ? question.get('asker') : null 63 | const user = question ? question.get('user') : null 64 | 65 | return ( 66 | 70 |
    71 | 72 |
    {friendlyTime(question.createdAt)}
    73 | 74 |

    75 | 76 | {asker ? 77 | {asker.get('name')} : "游客"} 78 | 79 | :{question.get('title')} 80 |

    81 | 82 | 83 |
    {friendlyTime(question.get('answeredAt'))}
    84 | 85 |

    86 | 87 | {user.get('name')} 88 | 89 | :{truncate(question.get('answer'), 60)} 90 |

    91 |
    92 | 93 | 94 |
    {friendlyTime(question.get('draftedAt'))}
    95 | 96 |

    97 | 98 | {user.get('name')} 99 | 100 | :草稿 101 | {truncate(question.get('draft'), 60)} 102 |

    103 |
    104 | 105 |

    {question.get('likesCount')} 点赞,{this.state.totalCommentsCount} 评论

    106 | 107 |
    108 | 109 | {this.state.totalCommentsCount !== 0 ? 110 | (
    111 | 114 | this.fetchData(page, question)} 118 | perPage={perPage} 119 | size="sm"/> 120 |
    ) : "无"} 121 |
    122 |
    123 |
    124 | ) 125 | } 126 | } 127 | 128 | const styles = { 129 | timeWap: { 130 | fontSize: '12px', 131 | color: '#AAAAAA' 132 | }, 133 | hr: { 134 | margin: '10px 0', 135 | borderTop: '1px solid #E8E8E8' 136 | }, 137 | draftFlag: { 138 | marginRight: '5px' 139 | } 140 | } -------------------------------------------------------------------------------- /components/question/QuestionsList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { Link } from 'react-router' 4 | import { friendlyTimeWithLineBreak, truncate } from '../../filters' 5 | 6 | @Radium 7 | export default class QuestionsList extends React.Component { 8 | static defaultProps = { 9 | showQuestionUser: true 10 | } 11 | 12 | static propTypes = { 13 | questions: React.PropTypes.array.isRequired, 14 | openUpdateQuestionModal: React.PropTypes.func.isRequired, 15 | openQuestionDetailsModal: React.PropTypes.func.isRequired, 16 | showQuestionUser: React.PropTypes.bool, 17 | deleteQuestion: React.PropTypes.func.isRequired 18 | } 19 | 20 | render() { 21 | const { questions, openUpdateQuestionModal, openQuestionDetailsModal, deleteQuestion } = this.props 22 | const rows = questions.map(question => { 23 | const asker = question.get('asker') 24 | const user = question.get('user') 25 | 26 | return ( 27 | 28 | 29 | 30 | {asker ? ({asker.get('name')}) : "游客"} 31 | 32 | : 33 | {question.get('title')} 34 | 35 | {question.get('anonymous') ? : null} 36 | {this.props.showQuestionUser ? 37 | ({user.get('name')}) : null} 38 | 39 | {truncate(question.get('answer'), 60)} 40 | 41 | {question.get('commentsCount')} 42 | {question.get('likesCount')} 43 | {friendlyTimeWithLineBreak(question.createdAt)} 44 | {friendlyTimeWithLineBreak(question.get('answeredAt'))} 45 | 46 |
    47 | 50 | 54 | 57 |
    58 | 59 | 60 | ) 61 | }) 62 | 63 | return ( 64 | 65 | 66 | 67 | 68 | 69 | {this.props.showQuestionUser ? : null} 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | {rows} 79 |
    问题匿名被提问者回答回复点赞提问于回答于操作
    80 | ) 81 | } 82 | } 83 | 84 | const styles = { 85 | titleCell: { 86 | maxWidth: '200px' 87 | }, 88 | answerCell: { 89 | maxWidth: '200px' 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /components/question/UpdateQuestionModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import Modal from "react-modal" 4 | import BaseModal from "../public/BaseFormModal" 5 | 6 | @Radium 7 | export default class UpdateQuestionModal extends React.Component { 8 | static propTypes = { 9 | question: React.PropTypes.object, 10 | updateQuestion: React.PropTypes.func.isRequired, 11 | modalIsOpen: React.PropTypes.bool.isRequired, 12 | closeModal: React.PropTypes.func.isRequired 13 | } 14 | 15 | state = { 16 | title: this.props.question ? this.props.question.get('title') : "" 17 | } 18 | 19 | componentWillReceiveProps = (nextProps) => { 20 | this.setState({ 21 | title: nextProps.question ? nextProps.question.get('title') : "" 22 | }) 23 | } 24 | 25 | handleTitleChange = (e) => { 26 | this.setState({title: e.target.value}) 27 | } 28 | 29 | handleSubmit = () => { 30 | const { updateQuestion, closeModal } = this.props 31 | 32 | updateQuestion(this.props.question, this.state.title) 33 | closeModal() 34 | } 35 | 36 | render() { 37 | const { modalIsOpen, closeModal } = this.props 38 | 39 | return ( 40 | 45 |
    46 |
    47 | 48 | 50 |
    51 |
    52 |
    53 | ) 54 | } 55 | } 56 | 57 | const styles = {} -------------------------------------------------------------------------------- /components/reportedQuestions/ReportedQuestionsList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import { Link } from 'react-router' 4 | import { friendlyTimeWithLineBreak, truncate } from '../../filters' 5 | 6 | @Radium 7 | export default class ReportedQuestionsList extends React.Component { 8 | static propTypes = { 9 | reportedQuestions: React.PropTypes.array.isRequired, 10 | openUpdateQuestionModal: React.PropTypes.func.isRequired, 11 | openQuestionDetailsModal: React.PropTypes.func.isRequired, 12 | deleteQuestion: React.PropTypes.func.isRequired 13 | } 14 | 15 | render() { 16 | const { reportedQuestions, openQuestionDetailsModal, openUpdateQuestionModal, deleteQuestion } = this.props 17 | const rows = reportedQuestions.map(reportedQuestion => { 18 | const question = reportedQuestion.get('question') 19 | const reporter = reportedQuestion.get('reporter') 20 | 21 | return ( 22 | 23 | 24 | {reporter 25 | ? {reporter.get('name')} 26 | : "游客"} 27 | 28 | 29 | 30 | {question.get('asker') ? 31 | 32 | {question.get('user').get('name')} 33 | : "游客"} 34 | 35 | : 36 | {question.get('title')} 37 | 38 | 39 | 40 | 41 | {question.get('user').get('name')} 42 | 43 | 44 | : 45 | {truncate(question.get('answer'), 60)} 46 | 47 | {friendlyTimeWithLineBreak(reportedQuestion.createdAt)} 48 | 49 |
    50 | 53 | 56 | 59 |
    60 | 61 | 62 | ) 63 | }) 64 | 65 | return ( 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {rows} 77 |
    举报者问题回答举报于操作
    78 | ) 79 | } 80 | } 81 | 82 | const styles = { 83 | titleCell: { 84 | maxWidth: '200px' 85 | }, 86 | answerCell: { 87 | maxWidth: '200px' 88 | } 89 | } -------------------------------------------------------------------------------- /components/users/UserModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Radium from 'radium' 3 | import Modal from "react-modal" 4 | import BaseModal from "../public/BaseFormModal" 5 | 6 | @Radium 7 | export default class UserModal extends React.Component { 8 | static propTypes = { 9 | user: React.PropTypes.object, 10 | modalIsOpen: React.PropTypes.bool.isRequired, 11 | updateUser: React.PropTypes.func.isRequired, 12 | closeModal: React.PropTypes.func.isRequired 13 | } 14 | 15 | state = { 16 | name: "", 17 | title: "", 18 | achievements: "", 19 | desc: "", 20 | mobilePhoneNumber: "", 21 | tags: "" 22 | } 23 | 24 | componentWillReceiveProps = (nextProps) => { 25 | if (nextProps.user && nextProps.modalIsOpen) { 26 | this.setState({ 27 | name: nextProps.user.get('name'), 28 | title: nextProps.user.get('title'), 29 | achievements: nextProps.user.get('achievements'), 30 | desc: nextProps.user.get('desc'), 31 | mobilePhoneNumber: nextProps.user.get('mobilePhoneNumber'), 32 | tags: nextProps.user.get('tags').join(', ') 33 | }) 34 | } 35 | } 36 | 37 | handleNameChange = (e) => { 38 | this.setState({name: e.target.value}) 39 | } 40 | 41 | handleTitleChange = (e) => { 42 | this.setState({title: e.target.value}) 43 | } 44 | 45 | handleDescChange = (e) => { 46 | this.setState({desc: e.target.value}) 47 | } 48 | 49 | handleAchievementsChange = (e) => { 50 | this.setState({achievements: e.target.value}) 51 | } 52 | 53 | handleMobilePhoneNumberChange = (e) => { 54 | this.setState({mobilePhoneNumber: e.target.value}) 55 | } 56 | 57 | handleTagsChange = (e) => { 58 | this.setState({tags: e.target.value}) 59 | } 60 | 61 | handleSubmit = () => { 62 | const { user, updateUser, closeModal } = this.props 63 | const tags = this.state.tags.replace(/ /g, '').split(',') 64 | 65 | closeModal() 66 | updateUser(user, this.state.name, this.state.title, this.state.achievements, this.state.desc, 67 | this.state.mobilePhoneNumber, tags) 68 | } 69 | 70 | render() { 71 | const { modalIsOpen, closeModal } = this.props 72 | 73 | return ( 74 | 79 |
    80 |
    81 | 82 | 84 |
    85 |
    86 | 87 | 89 |
    90 |
    91 | 92 | 94 |
    95 |
    96 | 97 | 99 |

    注:使用英文标点 , 分隔标签

    100 |
    101 |
    102 | 103 |