├── .babelrc ├── .editorconfig ├── .gitignore ├── .npmrc ├── .nvmrc ├── README.md ├── app.js ├── client-dva ├── .babelrc ├── component │ ├── footer │ │ ├── footer.js │ │ └── footer.less │ └── header │ │ ├── header.js │ │ └── header.less ├── container │ ├── home │ │ ├── home.js │ │ └── home.less │ ├── today │ │ ├── today.js │ │ └── today.less │ └── wrap │ │ ├── wrap.js │ │ └── wrap.less ├── model │ ├── count.js │ └── myInfo.js ├── package-lock.json ├── package.json ├── page │ └── main.js ├── style │ ├── common.less │ ├── iconfont.less │ ├── index.less │ └── reset.less ├── template │ └── index.html ├── util │ └── index.js └── webpack.config.js ├── client-react ├── .babelrc ├── .nvmrc ├── component │ ├── feedCard │ │ ├── feedCard.jsx │ │ └── feedCard.less │ ├── footer │ │ ├── index.jsx │ │ └── index.less │ ├── header │ │ ├── index.jsx │ │ └── index.less │ ├── scrollList │ │ ├── scrollList.jsx │ │ └── scrollList.less │ ├── userCard │ │ ├── userCard.jsx │ │ └── userCard.less │ └── userInfo │ │ ├── userInfo.jsx │ │ └── userInfo.less ├── config │ ├── develop.config.js │ ├── index.js │ └── production.config.js ├── container │ ├── detail │ │ ├── detail.jsx │ │ └── detail.less │ ├── index │ │ ├── index.jsx │ │ └── index.less │ ├── mine │ │ ├── mine.jsx │ │ └── mine.less │ ├── new │ │ ├── new.jsx │ │ └── new.less │ ├── rank │ │ ├── rank.jsx │ │ └── rank.less │ ├── today │ │ ├── today.jsx │ │ └── today.less │ ├── user │ │ ├── user.jsx │ │ └── user.less │ └── wrap │ │ ├── wrap.jsx │ │ └── wrap.less ├── lib │ └── date.js ├── package-lock.json ├── package.json ├── page │ ├── login │ │ ├── login.jsx │ │ └── login.less │ └── main │ │ └── main.js ├── redux │ ├── actionTypes.js │ ├── actions │ │ ├── index.js │ │ └── listActions.js │ ├── index.js │ └── reducer │ │ ├── index.js │ │ ├── listReducers.js │ │ └── myInfo.js ├── style │ ├── common.less │ ├── iconfont.less │ ├── index.less │ └── reset.less ├── template │ ├── index.html │ └── login.html ├── util │ └── index.js └── webpack.config.js ├── client-vue ├── .babelrc ├── .nvmrc ├── component │ ├── feedCard │ │ └── feedCard.vue │ ├── footer │ │ └── footer.vue │ ├── header │ │ └── header.vue │ └── scrollList │ │ └── scrollList.vue ├── container │ ├── frame │ │ ├── frame.less │ │ └── frame.vue │ ├── index │ │ └── index.vue │ ├── mine │ │ └── mine.vue │ ├── rank │ │ └── rank.vue │ ├── today │ │ └── today.vue │ └── wrap.vue ├── package-lock.json ├── package.json ├── page │ └── main │ │ └── main.js ├── style │ ├── common.less │ ├── iconfont.less │ ├── index.less │ └── reset.less ├── template │ └── main.html ├── util │ └── util.js ├── vuex │ ├── index.js │ ├── modules │ │ ├── main.js │ │ ├── rank.js │ │ └── today.js │ └── mutation-types.js └── webpack.config.js ├── middleware ├── auth.js └── sleep.js ├── mock ├── api │ ├── detail.json │ ├── myInfo.json │ ├── myList.json │ ├── otherList.json │ ├── otherUser.json │ ├── rankList.json │ ├── todayList.json │ └── user.json ├── generateMockData.js └── mock.sh ├── package-lock.json ├── package.json ├── pm2.prod.config.js ├── postcss.config.js ├── router ├── api.js ├── index.js ├── login.js └── page.js ├── static └── img │ └── avatar │ └── benqijiemu.jpg └── util └── util.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "compact": false, 3 | "presets": [ 4 | ["latest", { "modules": false }], 5 | "babel-preset-stage-0", 6 | "react" 7 | ], 8 | "plugins": ["babel-plugin-transform-decorators-legacy"], 9 | "env": { 10 | "development": { 11 | "presets": [ 12 | "react-hmre" 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: 2 | http://EditorConfig.org 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Unix-style newlines with a newline ending every file 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.{js,jsx,scss,vue}] 16 | indent_size = 2 17 | 18 | # Tab indentation (no size specified) 19 | [Makefile] 20 | indent_style = tab 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | dist/ 4 | .DS_Store 5 | .idea 6 | .project 7 | tmp.json 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npm.taobao.org/ 2 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # earlyjoy 2 | 😳The course for react stack. earlyjoy,enjoy it! 3 | 4 | # 概述 5 | 6 | 这个项目后端使用koa2,前端使用了react。 7 | 8 | 前端技术栈为 9 | - 基础列库:react 10 | - 路由:react-router4 11 | - 数据管理:react-redux + redux-thunk中间件 + redux-devtool 12 | - 路由信息同步:react-router-redux 13 | - 样式:weui + less + postcss 14 | 15 | # 运行方式 16 | 17 | ## 安装nodejs7.6+ 18 | Mac:安装nvm,使用nvm安装响应版本Nodejs 19 | Windows:Node.js官方安装最新版本 20 | 21 | 使用npm安装依赖: 22 | npm install 23 | 24 | ## 开发环境运行方式 25 | Node.js端: 26 | npm run dev-node // 如果报错了 很可能是Node.js版本不够高 27 | 28 | Borwser端: 29 | npm run dev-web // 会自动打开浏览器 30 | 31 | ## 部署环境运行方式 32 | 33 | 编译代码: 34 | npm run build 35 | 36 | Node.js服务器: 37 | npm run start 38 | 39 | # 文档快链 40 | 41 | - react-router 42 | https://reacttraining.cn/ 43 | 44 | - redux 45 | http://www.redux.org.cn/ 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/5/10. 3 | */ 4 | const Koa = require('koa'); 5 | const app = new Koa(); 6 | const port = 8333; 7 | const path = require('path'); 8 | const convert = require('koa-convert'); 9 | 10 | const cors = require('koa-cors'); 11 | const serve = require('koa-static-server'); 12 | const bodyParser = require('koa-bodyparser'); 13 | 14 | let isDev = process.env.NODE_ENV === 'develop'; // 是否是开发环境 15 | 16 | app.use(bodyParser()); // 解析HTTP请求体 17 | app.use(convert(cors())); // 允许跨域 18 | // app.use(require('./middleware/auth.js')); // 开启统一鉴权 19 | 20 | isDev && app.use(require('./middleware/sleep.js')); // 开发环境 延迟模拟 21 | 22 | require('./router')(app); // 初始化路由信息 23 | 24 | app.use(serve({rootDir: path.join(__dirname, './static'), rootPath: '/static/'})); // 本地静态服务器,主要给图片使用 25 | 26 | !isDev && app.use(serve({rootDir: path.join(__dirname, './dist'), rootPath: '/'})); // 线上的静态路由 27 | 28 | 29 | app.listen(port, () => { 30 | console.log(`earlyjoy已经启动,监听${port}端口`); 31 | }); 32 | -------------------------------------------------------------------------------- /client-dva/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "browsers": ["Android >= 4.0", "iOS >= 7"] 6 | } 7 | }], 8 | "babel-preset-stage-0", 9 | "react" 10 | ], 11 | "plugins": ["transform-decorators-legacy"], 12 | "ignore": [ 13 | "node_modules" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /client-dva/component/footer/footer.js: -------------------------------------------------------------------------------- 1 | import './footer.less'; 2 | import React, { Component } from 'react'; 3 | import { Link } from 'dva/router'; 4 | 5 | export default class extends Component { 6 | constructor () { 7 | super(); 8 | } 9 | 10 | render () { 11 | return ( 12 | 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client-dva/component/footer/footer.less: -------------------------------------------------------------------------------- 1 | .footer { 2 | display: flex; 3 | position: fixed; 4 | width: 100%; 5 | left: 0; 6 | bottom: 0; 7 | height: 50px; 8 | border-top: 1px solid #ccc; 9 | background-color: #fff; 10 | z-index: 10; 11 | .footer-item { 12 | flex: 1; 13 | } 14 | .footer-item a { 15 | flex: 1; 16 | display: flex; 17 | height: 100%; 18 | flex-direction: column; 19 | align-items: center; 20 | justify-content: center; 21 | color: #666; 22 | .footer-item-text { 23 | font-size: 12px; 24 | } 25 | .iconfont { 26 | 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client-dva/component/header/header.js: -------------------------------------------------------------------------------- 1 | import './header.less'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'dva'; 4 | 5 | @connect((state) => { 6 | return { 7 | routing: state.routing 8 | } 9 | }) 10 | export default class extends Component { 11 | constructor () { 12 | super(); 13 | debugger 14 | } 15 | 16 | getTitle () { 17 | // let { location: { pathname } } = this.props.routing; 18 | console.log(this.props.routing, 'header.js router change'); 19 | let pathname = ''; 20 | switch (pathname) { 21 | case '/index': 22 | return '首页'; 23 | break; 24 | default: 25 | return '我是早鸟'; 26 | } 27 | } 28 | 29 | render () { 30 | return ( 31 |
32 |
33 | {this.getTitle()} 34 |
35 |
36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client-dva/component/header/header.less: -------------------------------------------------------------------------------- 1 | .header { 2 | display: flex; 3 | height: 50px; 4 | background-color: #fff; 5 | align-items: center; 6 | justify-content: center; 7 | border-bottom: 1px solid #f0f0f0; 8 | position: fixed; 9 | width: 100%; 10 | top: 0; 11 | left: 0; 12 | z-index: 10; 13 | .inner { 14 | color: #333; 15 | font-size: 20px; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client-dva/container/home/home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'dva'; 3 | import { Link } from 'dva/router'; 4 | 5 | @connect((state) => { 6 | return { 7 | myinfo: state.myinfo, 8 | count: state.count, 9 | }; 10 | }) 11 | export default class extends Component { 12 | constructor () { 13 | super(); 14 | this.state = { 15 | 16 | }; 17 | } 18 | 19 | componentWillMount () { 20 | this.props.dispatch({ 21 | type: 'myinfo/getUserName', 22 | payload: 123, 23 | }); 24 | } 25 | 26 | render () { 27 | let {userName} = this.props.myinfo; 28 | return ( 29 |
30 | 123 31 | {userName} 32 |
33 | ) 34 | } 35 | } 36 | import './home.less'; 37 | -------------------------------------------------------------------------------- /client-dva/container/home/home.less: -------------------------------------------------------------------------------- 1 | .main-page { 2 | padding-top: 100px; 3 | .new-wrap { 4 | height: 70px; 5 | padding: 10px 10px 10px 20px; 6 | cursor: pointer; 7 | margin: 20px 0; 8 | .add-icon { 9 | font-size: 30px; 10 | font-weight: bold; 11 | margin-right: 10px; 12 | } 13 | .add-text { 14 | vertical-align: 1px; 15 | } 16 | } 17 | 18 | .recently-wrap { 19 | a:last-child .feed-card-item{ 20 | margin-bottom: 20px; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client-dva/container/today/today.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'dva'; 3 | import { Link } from 'dva/router'; 4 | 5 | @connect() 6 | export default class extends Component { 7 | constructor () { 8 | super(); 9 | this.state = { 10 | 11 | }; 12 | } 13 | 14 | componentWillMount () { 15 | } 16 | 17 | render () { 18 | return ( 19 |
20 | today 21 |
22 | ) 23 | } 24 | } 25 | import './today.less'; 26 | -------------------------------------------------------------------------------- /client-dva/container/today/today.less: -------------------------------------------------------------------------------- 1 | .main-page { 2 | padding-top: 100px; 3 | .new-wrap { 4 | height: 70px; 5 | padding: 10px 10px 10px 20px; 6 | cursor: pointer; 7 | margin: 20px 0; 8 | .add-icon { 9 | font-size: 30px; 10 | font-weight: bold; 11 | margin-right: 10px; 12 | } 13 | .add-text { 14 | vertical-align: 1px; 15 | } 16 | } 17 | 18 | .recently-wrap { 19 | a:last-child .feed-card-item{ 20 | margin-bottom: 20px; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client-dva/container/wrap/wrap.js: -------------------------------------------------------------------------------- 1 | import './wrap.less'; 2 | import React, { Component } from 'react'; 3 | import Header from '../../component/header/header'; 4 | import Footer from '../../component/footer/footer'; 5 | import { connect } from 'dva'; 6 | 7 | @connect((state) => { 8 | return { 9 | routing: state.routing 10 | } 11 | }) 12 | export default class extends Component { 13 | constructor () { 14 | super(); 15 | this.state = { 16 | 17 | }; 18 | } 19 | 20 | componentDidMount () { 21 | } 22 | 23 | render () { 24 | return ( 25 |
26 |
27 | {this.props.children} 28 |
30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client-dva/container/wrap/wrap.less: -------------------------------------------------------------------------------- 1 | @import "../../style/index.less"; 2 | 3 | html, body { 4 | height: 100%; 5 | } 6 | 7 | .doc { 8 | height: 100%; 9 | } 10 | 11 | .app-container { 12 | height: 100%; 13 | position: relative; 14 | } 15 | 16 | .page-wrap { 17 | height: 100%; 18 | padding-top: 50px; 19 | padding-bottom: 50px; 20 | overflow: auto; 21 | } 22 | -------------------------------------------------------------------------------- /client-dva/model/count.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespace: 'count', 3 | state: 0, 4 | reducers: { 5 | add (count) { 6 | debugger 7 | return count + 1 8 | }, 9 | minus(count) { return count - 1 }, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /client-dva/model/myInfo.js: -------------------------------------------------------------------------------- 1 | import {ajax} from "../util"; 2 | 3 | export default { 4 | namespace: 'myinfo', 5 | state: { 6 | userName: null, 7 | avatar: null, 8 | continued: null, 9 | getupTime: null, 10 | rank: null, 11 | uid: null, 12 | }, 13 | reducers: { 14 | userName (state, {payload}) { 15 | // console.log(arguments); 16 | // debugger 17 | return { 18 | ...state, 19 | ...payload 20 | } 21 | } 22 | }, 23 | effects: { 24 | *getUserName({payload}, {call, put}) { 25 | let getUserNameAPI = () => { 26 | // console.log(arguments); 27 | return ajax({ 28 | url: `//localhost:8333/api/myinfo`, 29 | method: 'get' 30 | }).then(res => { 31 | return res; 32 | }).catch(err => {debugger}); 33 | }; 34 | 35 | let res = yield call(getUserNameAPI, 123, 111, 111) 36 | // debugger 37 | // console.log(payload, res); 38 | // yield put({type: 'count/add', payload: 'add'}); 39 | yield put({type: 'userName', payload: res}) 40 | }, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client-dva/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client-dva", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "dev:client": "cross-env NODE_ENV=develop webpack-dev-server --config ./webpack.config.js", 8 | "build:client": "cross-env NODE_ENV=production webpack -p --config ./webpack.config.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^8.6.4", 15 | "babel-core": "^6.26.3", 16 | "babel-loader": "^7.1.4", 17 | "babel-plugin-transform-decorators-legacy": "^1.3.5", 18 | "babel-polyfill": "^6.26.0", 19 | "babel-preset-env": "^1.7.0", 20 | "babel-preset-react": "^6.24.1", 21 | "babel-preset-react-hmre": "^1.1.1", 22 | "babel-preset-stage-0": "^6.24.1", 23 | "cross-env": "^5.2.0", 24 | "css-loader": "^0.28.11", 25 | "html-webpack-plugin": "^3.2.0", 26 | "less": "^3.0.4", 27 | "less-loader": "^4.1.0", 28 | "open-browser-webpack-plugin": "0.0.5", 29 | "postcss-loader": "^2.1.5", 30 | "style-loader": "^0.21.0", 31 | "webpack": "^4.14.0", 32 | "webpack-cli": "^3.0.8", 33 | "webpack-dev-server": "^3.1.4" 34 | }, 35 | "dependencies": { 36 | "dva": "^2.3.1", 37 | "history": "^4.7.2", 38 | "react": "^16.4.1", 39 | "react-dom": "^16.4.1", 40 | "weui": "^1.1.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client-dva/page/main.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import 'weui'; 3 | import React from 'react'; 4 | import dva, {connect} from 'dva'; 5 | import {Router, Route, routerRedux, Switch} from 'dva/router'; 6 | const { ConnectedRouter } = routerRedux; 7 | import createHashHistory from 'history/createHashHistory'; 8 | import Wrap from '../container/wrap/wrap'; 9 | import Home from '../container/home/home'; 10 | import Today from '../container/today/today' 11 | 12 | import count from '../model/count'; 13 | import myinfo from '../model/myInfo'; 14 | 15 | const app = dva({ 16 | history: createHashHistory(), 17 | }); 18 | 19 | app.model(count); 20 | app.model(myinfo); 21 | 22 | app.router(({history}) => { 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ) 35 | }); 36 | 37 | app.start('.doc'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /client-dva/style/common.less: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f8f8f8; 3 | } 4 | 5 | .clearfix:after { 6 | content: '.'; 7 | display: block; 8 | height: 0; 9 | clear: both; 10 | visibility: hidden; 11 | } 12 | -------------------------------------------------------------------------------- /client-dva/style/iconfont.less: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('//at.alicdn.com/t/font_jmy7uvur11dims4i.eot?t=1495043119978'); /* IE9*/ 4 | src: url('//at.alicdn.com/t/font_jmy7uvur11dims4i.eot?t=1495043119978#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('//at.alicdn.com/t/font_jmy7uvur11dims4i.woff?t=1495043119978') format('woff'), /* chrome, firefox */ 6 | url('//at.alicdn.com/t/font_jmy7uvur11dims4i.ttf?t=1495043119978') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('//at.alicdn.com/t/font_jmy7uvur11dims4i.svg?t=1495043119978#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-tianjia:before { content: "\e62d"; } 19 | 20 | .icon-liebiao:before { content: "\e606"; } 21 | 22 | .icon-paixingbang:before { content: "\e641"; } 23 | 24 | .icon-gerenzhongxin:before { content: "\e6ab"; } 25 | 26 | .icon-zhuye:before { content: "\e648"; } 27 | 28 | .icon-zan:before { content: "\e870"; } 29 | 30 | .icon-zan1:before { content: "\e871"; } 31 | -------------------------------------------------------------------------------- /client-dva/style/index.less: -------------------------------------------------------------------------------- 1 | @import "./reset.less"; 2 | @import "./iconfont.less"; 3 | @import "./common.less"; 4 | -------------------------------------------------------------------------------- /client-dva/style/reset.less: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | ul { 8 | list-style: none; 9 | } 10 | 11 | a, a:link, a:visited, a:hover, a:active { 12 | color: #000; 13 | } 14 | -------------------------------------------------------------------------------- /client-dva/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 我是早鸟-dva 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /client-dva/util/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/5/23. 3 | */ 4 | let ajax = ({ method='', url='', async=true, data={}, headers={} }) => { 5 | let xhr = new XMLHttpRequest(); 6 | xhr.open(method, url, async); 7 | 8 | for ( let key in headers ) { 9 | xhr.setRequestHeader(key, headers[key]); 10 | } 11 | if ( method.toUpperCase() === 'POST' ) { 12 | xhr.setRequestHeader('content-type', 'application/json'); 13 | } 14 | 15 | let sendString = typeof data === 'string' ? data : JSON.stringify(data); 16 | xhr.send(sendString); 17 | 18 | return new Promise((resolve, reject) => { 19 | xhr.onload = function () { 20 | resolve(JSON.parse(xhr.responseText)); 21 | }; 22 | xhr.onerror = function () { 23 | reject(xhr.responseText); 24 | }; 25 | // xhr.onreadystatechange = function () { 26 | // if ( xhr.status === 200 && xhr.readyState === 4 ) { 27 | // resolve(xhr.responseText); 28 | // } else { 29 | // reject(xhr.responseText); 30 | // } 31 | // } 32 | }); 33 | }; 34 | 35 | export { ajax } 36 | export default { 37 | ajax 38 | } 39 | -------------------------------------------------------------------------------- /client-dva/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const OpenBrowserPlugin = require('open-browser-webpack-plugin'); 5 | 6 | let isDev = process.env.NODE_ENV === 'develop'; // 是否是开发环境 7 | const host = 'localhost'; 8 | const port = 8603; 9 | 10 | module.exports = { 11 | mode: 'development', 12 | entry: { 13 | main: './page/main.js', 14 | }, 15 | 16 | output: { 17 | path: path.resolve(__dirname, '../dist'), 18 | publicPath: isDev ? `http://${host}:${port}/` : '/', 19 | filename: '[name].bundle.js', 20 | }, 21 | 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.(jsx?)$/, 26 | use: ['babel-loader'], 27 | exclude: path.resolve(__dirname, 'node_modules'), 28 | }, 29 | { 30 | test: /\.(less|css)/, 31 | use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'], 32 | } 33 | ], 34 | }, 35 | 36 | resolve: { 37 | alias: { 38 | '@': path.resolve(__dirname), 39 | }, 40 | extensions: ['.js', '.jsx'], 41 | }, 42 | 43 | devtool: isDev ? 'cheap-module-eval-source-map' : '', 44 | context: __dirname, 45 | devServer: { 46 | hot: true, 47 | port: port, 48 | headers: { 49 | 'Access-Control-Allow-Origin': '*' 50 | }, 51 | }, 52 | 53 | plugins: [ 54 | new OpenBrowserPlugin({ url: `http://${host}:${port}/` }), 55 | new webpack.HotModuleReplacementPlugin(), 56 | new webpack.DefinePlugin({ 57 | 'process.env.NODE_ENV': 58 | isDev ? JSON.stringify('develop') : JSON.stringify('production') 59 | }), 60 | new HtmlWebpackPlugin({ 61 | template: './template/index.html', 62 | filename: 'index.html', 63 | chunks: ['main'], 64 | inject: true 65 | }), 66 | ] 67 | }; 68 | -------------------------------------------------------------------------------- /client-react/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "compact": false, 3 | "presets": [ 4 | ["latest", { "modules": false }], 5 | "babel-preset-stage-0", 6 | "react" 7 | ], 8 | "plugins": ["babel-plugin-transform-decorators-legacy"], 9 | "env": { 10 | "development": { 11 | "presets": [ 12 | "react-hmre" 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client-react/.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /client-react/component/feedCard/feedCard.jsx: -------------------------------------------------------------------------------- 1 | import './feedCard.less'; 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | export default class extends Component { 7 | constructor () { 8 | super(); 9 | } 10 | 11 | static propTypes = { 12 | item: PropTypes.object, 13 | route: PropTypes.string 14 | }; 15 | 16 | static defaultProps = { 17 | item: {}, 18 | route: '' 19 | }; 20 | 21 | render () { 22 | let { img, text, time } = this.props.item; 23 | let { route } = this.props; 24 | 25 | return ( 26 | 27 |
28 |
29 | 30 |
31 |
32 | {text} 33 | {time} 34 |
35 |
36 | 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client-react/component/feedCard/feedCard.less: -------------------------------------------------------------------------------- 1 | .feed-card-item { 2 | height: 70px; 3 | display: flex; 4 | align-items: center; 5 | margin-top: 10px; 6 | padding: 10px; 7 | .img-wrap { 8 | flex: none; 9 | margin-right: 10px; 10 | height: 100%; 11 | display: flex; 12 | align-items: center; 13 | img { 14 | height: 50px; 15 | } 16 | } 17 | .info { 18 | flex: 1; 19 | display: flex; 20 | flex-direction: column; 21 | .text { 22 | 23 | } 24 | .time { 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client-react/component/footer/index.jsx: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | import React, { Component } from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | export default class extends Component { 6 | constructor () { 7 | super(); 8 | } 9 | 10 | render () { 11 | return ( 12 | 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client-react/component/footer/index.less: -------------------------------------------------------------------------------- 1 | .footer { 2 | display: flex; 3 | position: fixed; 4 | width: 100%; 5 | left: 0; 6 | bottom: 0; 7 | height: 50px; 8 | border-top: 1px solid #ccc; 9 | background-color: #fff; 10 | z-index: 10; 11 | .footer-item { 12 | flex: 1; 13 | } 14 | .footer-item a { 15 | flex: 1; 16 | display: flex; 17 | height: 100%; 18 | flex-direction: column; 19 | align-items: center; 20 | justify-content: center; 21 | color: #666; 22 | .footer-item-text { 23 | font-size: 12px; 24 | } 25 | .iconfont { 26 | 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client-react/component/header/index.jsx: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | 5 | @connect(state => ({ 6 | routing: state.routing 7 | })) 8 | export default class extends Component { 9 | constructor () { 10 | super(); 11 | } 12 | 13 | getTitle () { 14 | let { location: { pathname } } = this.props.routing; 15 | switch (pathname) { 16 | case '/index': 17 | return '首页'; 18 | break; 19 | default: 20 | return '我是早鸟'; 21 | } 22 | } 23 | 24 | render () { 25 | return ( 26 |
27 |
28 | {this.getTitle()} 29 |
30 |
31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client-react/component/header/index.less: -------------------------------------------------------------------------------- 1 | .header { 2 | display: flex; 3 | height: 50px; 4 | background-color: #fff; 5 | align-items: center; 6 | justify-content: center; 7 | border-bottom: 1px solid #f0f0f0; 8 | position: fixed; 9 | width: 100%; 10 | top: 0; 11 | left: 0; 12 | z-index: 10; 13 | .inner { 14 | color: #333; 15 | font-size: 20px; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client-react/component/scrollList/scrollList.jsx: -------------------------------------------------------------------------------- 1 | import './scrollList.less'; 2 | import React, { Component, Children } from 'react'; 3 | import { findDOMNode } from 'react-dom'; 4 | import PropTypes from 'prop-types'; 5 | 6 | export default class extends Component { 7 | constructor () { 8 | super(); 9 | this.state = { 10 | isBindScrollEvent: false, 11 | status: 'init' // init wait 12 | }; 13 | this.elementScrollHandlerBind = this.elementScrollHandler.bind(this); 14 | } 15 | 16 | static propTypes = { 17 | hasMore: PropTypes.bool, // 是否还有更多 18 | loading: PropTypes.bool, // 是否能够加载,请求中的状态不允许加载 19 | onLoad: PropTypes.func, // 加载的函数 20 | element: PropTypes.object, // 监听的元素 21 | extra: PropTypes.number, // 额外的底边距 22 | isEmpty: PropTypes.bool // 是否为空 23 | }; 24 | 25 | static defaultProps = { 26 | hasMore: true, 27 | loading: true, 28 | onLoad: ()=>{}, 29 | element: null, 30 | extra: 10 31 | }; 32 | 33 | elementScrollHandler (e) { 34 | let { scrollHeight, clientHeight, scrollTop } = e.target; 35 | let { loading, onLoad, hasMore, extra } = this.props; 36 | 37 | console.log('loading:', loading, 'hasMore', hasMore); 38 | if ( clientHeight + scrollTop + extra >= scrollHeight && !loading && hasMore ) { 39 | onLoad(); 40 | } 41 | } 42 | 43 | componentWillReceiveProps (nextProps) { 44 | this.tryToBindScrollEvent(nextProps.element); 45 | } 46 | 47 | componentDidMount () { 48 | this.tryToBindScrollEvent(this.props.element); 49 | } 50 | 51 | tryToBindScrollEvent (element) { 52 | let { isBindScrollEvent } = this.state; 53 | 54 | if ( element && isBindScrollEvent === false ) { 55 | element.addEventListener('scroll', this.elementScrollHandlerBind, false); 56 | this.setState({ isBindScrollEvent: true }); 57 | } 58 | } 59 | 60 | componentWillUnmount () { 61 | let { element } = this.props; 62 | if ( element ) { 63 | element.removeEventListener('scroll', this.elementScrollHandlerBind, false); 64 | } 65 | } 66 | 67 | render () { 68 | let { loading, hasMore, isEmpty } = this.props; 69 | 70 | return ( 71 |
72 | {this.props.children} 73 | { 74 | loading && 75 |
76 | 77 | 正在加载 78 |
79 | } 80 | { 81 | !hasMore && 82 |
83 | 84 |
85 | } 86 | { 87 | isEmpty && 88 |
89 | 暂无数据 90 |
91 | } 92 |
93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /client-react/component/scrollList/scrollList.less: -------------------------------------------------------------------------------- 1 | .scroll-list { 2 | .weui-loadmore__tips { 3 | background-color: transparent; 4 | } 5 | .weui-loadmore_line { 6 | margin-top: 40px; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client-react/component/userCard/userCard.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeknull/earlyjoy/048cb592b1635344eafd52413122137d6e470327/client-react/component/userCard/userCard.jsx -------------------------------------------------------------------------------- /client-react/component/userCard/userCard.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeknull/earlyjoy/048cb592b1635344eafd52413122137d6e470327/client-react/component/userCard/userCard.less -------------------------------------------------------------------------------- /client-react/component/userInfo/userInfo.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/7/9. 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | 7 | export default class extends Component { 8 | constructor () { 9 | super(); 10 | } 11 | 12 | static propTypes = { 13 | userName: PropTypes.string, 14 | avatar: PropTypes.string, 15 | getupTime: PropTypes.string, 16 | rank: PropTypes.number, 17 | continued: PropTypes.number, 18 | uid: PropTypes.number, 19 | 20 | }; 21 | 22 | static defaultProps = { 23 | userName: null, 24 | avatar: null, 25 | getupTime: null, 26 | rank: null, 27 | continued: null, 28 | uid: null, 29 | }; 30 | 31 | render () { 32 | let { userName, avatar, getupTime, rank, continued, uid } = this.props; 33 | 34 | return ( 35 |
36 | 37 |
{userName}
38 |
39 | {`在好友中排名第${rank}名`}, 40 | {`坚持了${continued}天`}。 41 |
42 |
43 | ) 44 | } 45 | } 46 | import './userInfo.less'; 47 | 48 | -------------------------------------------------------------------------------- /client-react/component/userInfo/userInfo.less: -------------------------------------------------------------------------------- 1 | ._user-info-wrap { 2 | text-align: center; 3 | margin: 20px; 4 | .avatar { 5 | width: 100px; 6 | height: 100px; 7 | border-radius: 50%; 8 | } 9 | .user-name { 10 | font-size: 22px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client-react/config/develop.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/7/9. 3 | */ 4 | export default { 5 | requestPrefix: 'http://localhost:8333' 6 | } 7 | -------------------------------------------------------------------------------- /client-react/config/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/7/9. 3 | */ 4 | import devConfig from './develop.config.js'; 5 | import prodConfig from './production.config.js'; 6 | 7 | let Config = {}; 8 | 9 | if (process.env.NODE_ENV === 'production') { 10 | Config = prodConfig; 11 | } else if (process.env.NODE_ENV === 'develop') { 12 | Config = devConfig; 13 | } else { 14 | throw new Error('请设置正确的NODE_ENV,为production或者develop'); 15 | } 16 | 17 | export default Config; 18 | 19 | 20 | -------------------------------------------------------------------------------- /client-react/config/production.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/7/9. 3 | */ 4 | export default { 5 | requestPrefix: 'http://localhost:8333' 6 | } 7 | -------------------------------------------------------------------------------- /client-react/container/detail/detail.jsx: -------------------------------------------------------------------------------- 1 | import './detail.less'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import actions from '../../redux/actions/index.js'; 5 | import { Link } from 'react-router-dom'; 6 | 7 | @connect((state) => ({ 8 | detail: state.detail 9 | }), { ...actions }) 10 | export default class extends Component { 11 | constructor () { 12 | super(); 13 | } 14 | 15 | componentWillMount () { 16 | let detailId = this.props.match.params.id; 17 | this.props.getDetail(detailId); 18 | } 19 | 20 | render () { 21 | let { detail: { 22 | img, userName, userId, rank, continued, time, like, liked 23 | }} = this.props; 24 | 25 | console.log(this.props.detail.time); 26 | 27 | return ( 28 |
29 |
30 | 31 |
32 |
33 |
34 |
35 |
昵称
36 |
{userName}
37 |
38 |
39 |
40 |
41 |
42 | 起床时间 43 |
44 |
45 | {time} 46 |
47 |
48 |
49 |
50 | 好友排名 51 |
52 |
53 | 第{rank}名 54 |
55 |
56 |
57 |
58 | 连续起床天数 59 |
60 |
61 | {continued}天 62 |
63 |
64 |
65 |
66 | 赞 67 |
68 |
69 | {like}个 70 |
71 |
72 |
73 |
74 |
75 | { 76 | liked ? 77 | 78 | 79 | 取消赞 80 | 81 | : 82 | 83 | 84 | 点赞 85 | 86 | } 87 |
88 |
89 |
90 | 91 |
92 | 93 | 94 | 95 |
96 |
97 | ) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /client-react/container/detail/detail.less: -------------------------------------------------------------------------------- 1 | .detail-page { 2 | padding-top: 80px; 3 | .avatar-wrap { 4 | text-align: center; 5 | img { 6 | width: 100px; 7 | height: 100px; 8 | border-radius: 50%; 9 | } 10 | } 11 | .user-name { 12 | margin-top: 20px; 13 | text-align: center; 14 | font-size: 22px; 15 | color: #333; 16 | } 17 | 18 | .detail-info { 19 | margin-top: 20px; 20 | .detail-time { 21 | color: #333; 22 | } 23 | .like-wrap { 24 | width: 100%; 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; 28 | .like-inner { 29 | display: flex; 30 | align-items: center; 31 | justify-content: center; 32 | } 33 | .iconfont { 34 | font-size: 22px; 35 | color: #333; 36 | margin-right: 10px; 37 | } 38 | } 39 | } 40 | 41 | .detail-btn-wrap { 42 | margin-top: 20px; 43 | padding: 0 15px; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client-react/container/index/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import actions from '../../redux/actions/index.js'; 4 | import { Link } from 'react-router-dom'; 5 | import ScrollList from '../../component/scrollList/scrollList.jsx'; 6 | import FeedCard from '../../component/feedCard/feedCard.jsx'; 7 | import UserInfo from '@/component/userInfo/userInfo.jsx'; 8 | 9 | @connect((state) => { 10 | return { 11 | myInfo: state.myInfo, 12 | myListInfo: state.myListInfo 13 | } 14 | },{...actions}) 15 | export default class extends Component { 16 | constructor () { 17 | super(); 18 | this.state = { 19 | 20 | }; 21 | } 22 | 23 | componentWillMount () { 24 | } 25 | 26 | componentDidMount () { 27 | let { userName, loading: isLoadingMyInfo } = this.props.myInfo; 28 | let isMyListInit = this.props.myListInfo.isInit; 29 | 30 | !userName && this.props.getMyInfo(); 31 | !isMyListInit && this.props.getMyList(); 32 | } 33 | 34 | render () { 35 | let { list, hasMore, loading, isEmpty } = this.props.myListInfo; 36 | 37 | return ( 38 |
39 | 40 | 41 |
42 | + 43 | 添加今日状态 44 |
45 | 46 |
最近状态
47 | 54 |
55 | { 56 | list.map((item, index) => { 57 | return ( 58 | 59 | ) 60 | }) 61 | } 62 |
63 |
64 |
65 | ) 66 | } 67 | } 68 | import './index.less'; 69 | -------------------------------------------------------------------------------- /client-react/container/index/index.less: -------------------------------------------------------------------------------- 1 | .main-page { 2 | padding-top: 100px; 3 | .new-wrap { 4 | height: 70px; 5 | padding: 10px 10px 10px 20px; 6 | cursor: pointer; 7 | margin: 20px 0; 8 | .add-icon { 9 | font-size: 30px; 10 | font-weight: bold; 11 | margin-right: 10px; 12 | } 13 | .add-text { 14 | vertical-align: 1px; 15 | } 16 | } 17 | 18 | .recently-wrap { 19 | a:last-child .feed-card-item{ 20 | margin-bottom: 20px; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client-react/container/mine/mine.jsx: -------------------------------------------------------------------------------- 1 | import './mine.less'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import actions from '../../redux/actions/index.js'; 5 | 6 | @connect(state => ({ 7 | myInfo: state.myInfo 8 | }), { ...actions }) 9 | export default class extends Component { 10 | constructor (props) { 11 | super(); 12 | this.state = { 13 | 14 | }; 15 | this.myInfoBak = null; 16 | } 17 | 18 | componentWillMount () { 19 | let { userName } = this.props.myInfo; 20 | if ( userName ) { 21 | this.myInfoBak = this.props.myInfo; 22 | } 23 | this.props.getMyInfo(); 24 | } 25 | 26 | componentWillUpdate (nextProps, nextState) { 27 | let { userName } = nextProps.myInfo; 28 | if ( userName && !this.myInfoBak ) { 29 | this.myInfoBak = nextProps.myInfo; 30 | } 31 | } 32 | 33 | componentWillUnmount () { 34 | let { userName, avatar, getupTime } = this.props.myInfo; 35 | 36 | if ( userName ) { 37 | console.log('componentWillUnmount', this.myInfoBak); 38 | this.props.recoverMyInfo(this.myInfoBak); 39 | } 40 | } 41 | 42 | render () { 43 | let { userName, avatar, getupTime } = this.props.myInfo; 44 | console.log('xx'); 45 | 46 | return ( 47 |
48 |
49 | 50 |
51 |
52 | {userName} 53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 | { this.props.setGetUp(e.target.value) }} 64 | /> 65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 | 77 |
78 |
79 |
80 | 81 |
82 | 83 |
84 |
85 | ) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /client-react/container/mine/mine.less: -------------------------------------------------------------------------------- 1 | .mine-page { 2 | .avatar-wrap { 3 | text-align: center; 4 | margin-top: 30px; 5 | img { 6 | width: 100px; 7 | height: 100px; 8 | border-radius: 50%; 9 | } 10 | } 11 | .user-name { 12 | margin-top: 20px; 13 | text-align: center; 14 | color: #333; 15 | font-size: 22px; 16 | } 17 | 18 | .mine-btn-wrap { 19 | margin-top: 15px; 20 | padding: 0 15px; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client-react/container/new/new.jsx: -------------------------------------------------------------------------------- 1 | import './new.less'; 2 | import React, { Component } from 'react'; 3 | import { formatDate } from '../../lib/date.js'; 4 | 5 | export default class extends Component { 6 | constructor () { 7 | super(); 8 | this.state = { 9 | 10 | }; 11 | } 12 | 13 | getNowTime () { 14 | let date = new Date(); 15 | return `${date.getHours()}:${date.getMinutes()}`; 16 | } 17 | 18 | render () { 19 | return ( 20 |
21 |
22 | 23 |
图片状态
24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 | 上传文件不大于10MB,最好为正方形 32 |
33 |
34 |
35 |
36 | 37 |
时间
38 |
39 |
40 |
41 |
当前时间
42 |
43 |
44 | {}} disabled={true} value={formatDate(Date.now())}/> 45 |
46 |
47 |
48 |
49 |
起床时间
50 |
51 |
52 | {}} disabled={true} value={'8:30'}/> 53 |
54 |
55 |
56 |
57 |
状态
58 |
59 |
60 | {}} disabled={true} value={'准时起床'}/> 61 |
62 |
63 |
64 | 65 |
说点什么
66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 | 74 |
75 | 76 |
77 |
78 |
79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /client-react/container/new/new.less: -------------------------------------------------------------------------------- 1 | .new-page { 2 | .new-page-inner { 3 | margin-top: 20px; 4 | } 5 | 6 | .file-up-wrap { 7 | .weui-uploader__input-box { 8 | margin-bottom: 0; 9 | } 10 | .file-up-tip { 11 | height: 77px; 12 | display: flex; 13 | align-items: center; 14 | color: #333; 15 | } 16 | } 17 | 18 | .cur-time { 19 | margin-top: 30px; 20 | } 21 | 22 | .status-text { 23 | margin-top: 30px; 24 | textarea { 25 | outline: none; 26 | resize: none; 27 | border: 1px solid #f0f0f0; 28 | width: 100%; 29 | height: 100px; 30 | padding: 5px; 31 | } 32 | } 33 | 34 | 35 | .ensure-btn-wrap { 36 | padding: 15px; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client-react/container/rank/rank.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router-dom'; 4 | import actions from '../../redux/actions/index.js'; 5 | import ScrollList from '@/component/scrollList/scrollList.jsx'; 6 | 7 | @connect((state) => ({ 8 | rankListInfo: state.rankListInfo 9 | }), { ...actions }) 10 | export default class extends Component { 11 | constructor () { 12 | super(); 13 | this.state = { 14 | 15 | }; 16 | } 17 | 18 | componentWillMount () { 19 | 20 | } 21 | 22 | componentDidMount () { 23 | let isRankListInit = this.props.rankListInfo.isInit; 24 | !isRankListInit && this.props.getRankList(); 25 | } 26 | 27 | render () { 28 | let { 29 | list, offset, limit, 30 | hasMore, loading, isEmpty, isInit 31 | } = this.props.rankListInfo; 32 | 33 | return ( 34 |
35 | 42 |
    43 | { 44 | list.map((item, index) => { 45 | let { avatar, continued, getupTime ,rank, uid ,userName } = item; 46 | 47 | return ( 48 | 49 |
  • 50 |
    {rank}
    51 | 52 |
    {userName}
    53 |
    {continued}天
    54 |
  • 55 | 56 | ) 57 | }) 58 | } 59 |
60 |
61 |
62 | ) 63 | } 64 | } 65 | import './rank.less'; 66 | -------------------------------------------------------------------------------- /client-react/container/rank/rank.less: -------------------------------------------------------------------------------- 1 | .rank-page { 2 | .rank-list { 3 | padding-top: 15px; 4 | } 5 | .rank-item { 6 | display: flex; 7 | align-items: center; 8 | height: 70px; 9 | background-color: #fff; 10 | margin-bottom: 15px; 11 | .rank-level { 12 | font-size: 32px; 13 | color: #666; 14 | width: 50px; 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | } 19 | .rank-avatar { 20 | width: 50px; 21 | height: 50px; 22 | border-radius: 50%; 23 | border: 1px solid #efefef; 24 | margin-right: 10px; 25 | } 26 | .rank-user-name { 27 | color: #333; 28 | } 29 | .rank-continued { 30 | flex: 1; 31 | text-align: right; 32 | margin-right: 30px; 33 | color: #333; 34 | font-size: 22px; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client-react/container/today/today.jsx: -------------------------------------------------------------------------------- 1 | import './today.less'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import actions from '../../redux/actions/index.js'; 5 | import FeedCard from '../../component/feedCard/feedCard.jsx'; 6 | import ScrollList from '../../component/scrollList/scrollList.jsx'; 7 | 8 | @connect((state) => ({ 9 | todayListInfo: state.todayListInfo 10 | }), { 11 | ...actions 12 | }) 13 | export default class extends Component { 14 | constructor () { 15 | super(); 16 | this.state = { 17 | 18 | }; 19 | } 20 | 21 | componentWillMount () { 22 | let isToadyListIsInit = this.props.todayListInfo.isInit; 23 | !isToadyListIsInit && this.props.getToadyList(); 24 | } 25 | 26 | render () { 27 | let { list, hasMore, loading, isEmpty } = this.props.todayListInfo; 28 | 29 | return ( 30 |
31 | 38 |
39 | { 40 | list.map((item, index) => { 41 | return ( 42 | 47 | ) 48 | }) 49 | } 50 |
51 |
52 |
53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client-react/container/today/today.less: -------------------------------------------------------------------------------- 1 | .today-page { 2 | .today-list { 3 | a:last-child .feed-card-item { 4 | margin-bottom: 20px; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /client-react/container/user/user.jsx: -------------------------------------------------------------------------------- 1 | import './user.less'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import actions from '../../redux/actions/index.js'; 5 | import FeedCard from '../../component/feedCard/feedCard.jsx'; 6 | import UserInfo from '@/component/userInfo/userInfo.jsx'; 7 | import ScrollList from '@/component/scrollList/scrollList.jsx'; 8 | 9 | let obj = { 10 | x: 'x', 11 | y: 'x', 12 | }; 13 | 14 | @connect(state => ({ 15 | otherInfo: state.otherInfo, 16 | otherListInfo: state.otherListInfo 17 | }), { ...actions }) 18 | export default class extends Component { 19 | constructor () { 20 | super(); 21 | } 22 | 23 | componentWillMount () { 24 | let uid = this.props.match.params.id; 25 | this.props.getOtherUser(uid); 26 | this.props.getOtherList(uid); 27 | } 28 | 29 | render () { 30 | let { list, hasMore, isEmpty, loading } = this.props.otherListInfo; 31 | return ( 32 |
33 | 34 | 41 |
42 | { 43 | list.map((item, index) => { 44 | return ( 45 | 46 | ) 47 | }) 48 | } 49 |
50 |
51 |
52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client-react/container/user/user.less: -------------------------------------------------------------------------------- 1 | .user-page { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /client-react/container/wrap/wrap.jsx: -------------------------------------------------------------------------------- 1 | import './wrap.less'; 2 | import React, { Component } from 'react'; 3 | import Header from '../../component/header/index.jsx'; 4 | import Footer from '../../component/footer/index.jsx'; 5 | 6 | export default class extends Component { 7 | constructor () { 8 | super(); 9 | this.state = { 10 | 11 | }; 12 | } 13 | 14 | render () { 15 | return ( 16 |
17 |
18 | {this.props.children} 19 |
20 |
21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client-react/container/wrap/wrap.less: -------------------------------------------------------------------------------- 1 | @import "../../style/index.less"; 2 | 3 | html, body { 4 | height: 100%; 5 | } 6 | 7 | .doc { 8 | height: 100%; 9 | } 10 | 11 | .app-container { 12 | height: 100%; 13 | position: relative; 14 | } 15 | 16 | .page-wrap { 17 | height: 100%; 18 | padding-top: 50px; 19 | padding-bottom: 50px; 20 | overflow: auto; 21 | } 22 | -------------------------------------------------------------------------------- /client-react/lib/date.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/5/30. 3 | */ 4 | 5 | export let formatDate = (timestamp) => { 6 | let date = new Date(timestamp); 7 | return `${date.getFullYear()}.${date.getMonth()+1}.${date.getDate()} ${date.getHours()}:${date.getMinutes()}` 8 | }; 9 | -------------------------------------------------------------------------------- /client-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "dev-web": "cross-env NODE_ENV=develop webpack-dev-server --config ./webpack.config.js", 11 | "build-web": "cross-env NODE_ENV=production webpack -p --config ./webpack.config.js" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "lodash.clonedeep": "^4.5.0", 18 | "prop-types": "^15.6.0", 19 | "react": "^16.0.0", 20 | "react-dom": "^16.0.0", 21 | "react-redux": "^5.0.6", 22 | "react-router-dom": "^4.2.2", 23 | "react-router-redux": "^5.0.0-alpha.6", 24 | "redux": "^3.7.2", 25 | "redux-thunk": "^2.2.0", 26 | "weui": "^1.1.2" 27 | }, 28 | "devDependencies": { 29 | "autoprefixer": "^7.1.4", 30 | "babel-core": "^6.26.0", 31 | "babel-loader": "^7.1.2", 32 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 33 | "babel-polyfill": "^6.26.0", 34 | "babel-preset-latest": "^6.24.1", 35 | "babel-preset-react": "^6.24.1", 36 | "babel-preset-react-hmre": "^1.1.1", 37 | "babel-preset-stage-0": "^6.24.1", 38 | "cross-env": "^5.0.5", 39 | "css-loader": "^0.28.7", 40 | "html-webpack-plugin": "^2.30.1", 41 | "less": "^2.7.2", 42 | "less-loader": "^4.0.5", 43 | "open-browser-webpack-plugin": "0.0.5", 44 | "postcss-loader": "^2.0.6", 45 | "style-loader": "^0.18.2", 46 | "webpack": "^3.6.0", 47 | "webpack-dashboard": "^1.0.0-5", 48 | "webpack-dev-server": "^2.9.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client-react/page/login/login.jsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import React, { Component } from 'react'; 3 | import { ajax } from '../../util/index.js'; 4 | 5 | let Login = class extends Component { 6 | constructor () { 7 | super(); 8 | this.state = { 9 | username: null, 10 | password: null 11 | } 12 | } 13 | 14 | userNameHandler (e) { 15 | let username = e.target.value; 16 | this.setState({ username }); 17 | } 18 | 19 | passwordHandler (e) { 20 | let password = e.target.value; 21 | this.setState({ password }); 22 | } 23 | 24 | loginHandler () { 25 | let { username, password } = this.state; 26 | 27 | if ( username && password ) { 28 | ajax({ 29 | url: 'http://localhost:8333/api/login', 30 | method: 'post', 31 | data: { username, password } 32 | }).then((val) => { 33 | // debugger 34 | location.href = 'http://localhost:8333'; 35 | }).catch((err) => { 36 | console.log(err); 37 | }); 38 | } else { 39 | alert('请输入账号密码'); 40 | } 41 | } 42 | 43 | render () { 44 | return ( 45 |
46 |
47 |

登陆早起打卡

48 |
49 |
账号
50 |
51 |
52 |
53 | 57 |
58 |
59 |
60 | 61 |
密码
62 |
63 |
64 |
65 | 69 |
70 |
71 |
72 | 73 |
74 | 77 | 确定 78 | 79 |
80 |
81 | ) 82 | } 83 | }; 84 | 85 | ReactDOM.render(, document.querySelector('.doc')); 86 | import './login.less'; 87 | import 'weui'; 88 | 89 | -------------------------------------------------------------------------------- /client-react/page/login/login.less: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body { 7 | background-color: #f8f8f8; 8 | } 9 | 10 | .login-page { 11 | 12 | } 13 | 14 | .login-title { 15 | height: 50px; 16 | background-color: #fff; 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | color: #666; 21 | } 22 | -------------------------------------------------------------------------------- /client-react/page/main/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/5/10. 3 | */ 4 | import 'weui'; 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | 8 | // 路由信息【react-router-dom】 9 | import { HashRouter, Route } from 'react-router-dom'; 10 | 11 | // 创建hash路由信息【react-router-redux】 12 | import { ConnectedRouter } from 'react-router-redux'; 13 | import createHistory from 'history/createHashHistory'; 14 | const history = createHistory(); 15 | 16 | // 创建redux store【react-redux】 17 | import { Provider } from 'react-redux'; 18 | import store from '../../redux/index.js'; 19 | window._store = store; // 方便调试 并不是好的写法 20 | 21 | // 引入组件 22 | import Index from '../../container/index/index.jsx'; 23 | import Today from '../../container/today/today.jsx'; 24 | import Rank from '../../container/rank/rank.jsx'; 25 | import Mine from '../../container/mine/mine.jsx'; 26 | import Wrap from '../../container/wrap/wrap.jsx'; 27 | import New from '../../container/new/new.jsx'; 28 | import Detail from '../../container/detail/detail.jsx'; 29 | import User from '../../container/user/user.jsx'; 30 | 31 | ReactDOM.render( 32 | 33 | {/*HashRouter*/} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ,document.querySelector('.doc') 48 | ); 49 | -------------------------------------------------------------------------------- /client-react/redux/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const MY_INFO = 'my_info'; // 获取我的个人信息 2 | export const GET_UP_TIME = 'get_up_time'; // 起床时间 3 | export const MY_INFO_LOADING = 'my_info_loading'; // 设置个人信息loading状态 4 | export const RECOVER_MYINFO = 'recover_myinfo'; // 恢复个人信息 5 | 6 | export const MY_LIST = 'my_list'; // 获取我的列表 7 | export const MY_LIST_LOADING = 'my_list_loading'; // 我的列表读取状态 8 | 9 | export const TODAY_LIST = 'toady_list'; // 获取今日列表信息 10 | export const TODAY_LIST_LOADING = 'today_list_loading'; // 获取今日列表信息状态 11 | 12 | export const RANK_LIST = 'rank_list'; // 获取排行列表 13 | export const RANK_LIST_LOADING = 'rank_list_loading'; // 获取排行列表 14 | 15 | export const OTHER_LIST = 'other_list'; // 他人的列表 16 | export const OTHER_LIST_LOADING = 'other_list_loading'; // 他人列表的loading态 17 | export const OTHER_LIST_CLEAR = 'other_list_clear'; // 清空他人的列表 因为会多次进入这个页面 每次的数据都不用 18 | 19 | export const OTHER_USER = 'other_user'; // 获取其他人的信息 20 | export const OTHER_USER_CLEAR = 'other_user_clear'; // 清空他人的信息 21 | 22 | export const DETAIL_INFO = 'detail_info'; // 获取详情 23 | export const DETAIL_INFO_CLEAR = 'detail_info_clear'; // 清空详情数据 24 | -------------------------------------------------------------------------------- /client-react/redux/actions/index.js: -------------------------------------------------------------------------------- 1 | import * as CONSTANTS from '../actionTypes.js'; 2 | import { ajax } from '../../util/index.js'; 3 | import listActions from './listActions.js'; 4 | let requestPrefix = 'http://localhost:8333'; 5 | let requestPrefix2 = 'http://localhost:8333'; 6 | 7 | // 获取我的信息 8 | export let getMyInfo = () => (dispatch, getState) => { 9 | let { userName } = getState().myInfo; 10 | if (userName) { return void 0; } 11 | 12 | // 请求开始阶段 13 | dispatch({ type: CONSTANTS.MY_INFO_LOADING, loading: true }); 14 | 15 | ajax({ 16 | url: `${requestPrefix2}/api/myinfo`, 17 | method: 'get' 18 | }).then(res => { 19 | dispatch({ 20 | type: CONSTANTS.MY_INFO, 21 | loading: false, 22 | ...res 23 | }); 24 | }).catch(err => { 25 | dispatch({ type: CONSTANTS.MY_INFO_LOADING, loading: false }); 26 | console.log(err); 27 | }) 28 | }; 29 | 30 | 31 | // 恢复个人信息 32 | export let recoverMyInfo = (myInfo) => { 33 | return { 34 | type: CONSTANTS.RECOVER_MYINFO, 35 | myInfo: myInfo 36 | } 37 | }; 38 | 39 | // 设置起床时间 40 | export let setGetUp = (value) => { 41 | return { 42 | type: CONSTANTS.GET_UP_TIME, 43 | value: value 44 | } 45 | }; 46 | 47 | // 获取详情信息 48 | export let getDetail = (id) => dispatch => { 49 | ajax({ 50 | url: `${requestPrefix}/api/detail/${id}`, 51 | method: 'get' 52 | }).then((res) => { 53 | dispatch({ 54 | type: CONSTANTS.DETAIL_INFO, 55 | info: res 56 | }) 57 | }).catch((err) => { 58 | console.log(err); 59 | }) 60 | }; 61 | 62 | // 获取他人信息 63 | export let getOtherUser = (id) => dispatch => { 64 | ajax({ 65 | url: `${requestPrefix}/api/otheruser/${id}`, 66 | method: 'get' 67 | }).then((res) => { 68 | dispatch({ 69 | type: CONSTANTS.OTHER_USER, 70 | info: res 71 | }) 72 | }).catch((err) => { 73 | console.log(err); 74 | }) 75 | }; 76 | 77 | // 清空他人信息 78 | export let clearOtherUser = (id) => { 79 | return { 80 | type: CONSTANTS.OTHER_USER_CLEAR, 81 | uid: id 82 | } 83 | }; 84 | 85 | export default { 86 | getMyInfo, 87 | recoverMyInfo, 88 | setGetUp, 89 | getDetail, 90 | getOtherUser, 91 | clearOtherUser, 92 | ...listActions 93 | } 94 | -------------------------------------------------------------------------------- /client-react/redux/actions/listActions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/7/9. 3 | */ 4 | import * as CONSTANTS from '../actionTypes.js'; 5 | import { ajax } from '../../util/index.js'; 6 | import Config from '@/config'; 7 | let requestPrefix = Config.requestPrefix; 8 | 9 | // 获取我的状态列表 10 | export let getMyList = () => (dispatch, getState)=> { 11 | let {limit, offset, hasMore} = getState().myListInfo; 12 | if ( !hasMore ) { return void 0; } 13 | 14 | // 请求开始阶段 15 | dispatch({ type: CONSTANTS.MY_LIST_LOADING, loading: true }); 16 | 17 | // 请求中 18 | ajax({ 19 | url: `${requestPrefix}/api/mylist`, 20 | method: 'post', 21 | data: { offset, limit } 22 | }).then(res => { 23 | // 请求结束 24 | dispatch({ 25 | type: CONSTANTS.MY_LIST, 26 | list: res.list, 27 | hasMore: res.hasMore, 28 | loading: false 29 | }) 30 | }).catch(err => { 31 | // 请求结束 32 | dispatch({ type: CONSTANTS.MY_LIST_LOADING, loading: false }); 33 | console.log(err); 34 | }) 35 | }; 36 | 37 | // 获取今日列表 38 | export let getToadyList = () => (dispatch, getState) => { 39 | let {limit, offset, hasMore} = getState().todayListInfo; 40 | if ( !hasMore ) { return void 0; } 41 | 42 | dispatch({type: CONSTANTS.TODAY_LIST_LOADING, loading: true}); 43 | ajax({ 44 | url: `${requestPrefix}/api/todaylist`, 45 | method: 'post', 46 | data: { offset, limit } 47 | }).then(res => { 48 | dispatch({ 49 | type: CONSTANTS.TODAY_LIST, 50 | list: res.list, 51 | hasMore: res.hasMore, 52 | loading: false 53 | }); 54 | }).catch((err) => { 55 | dispatch({type: CONSTANTS.TODAY_LIST_LOADING, loading: false}); 56 | console.log(err); 57 | }); 58 | }; 59 | 60 | // 获取好友排行列表 61 | export let getRankList = () => (dispatch, getState) => { 62 | let {limit, offset, hasMore} = getState().rankListInfo; 63 | if ( !hasMore ) { return void 0; } 64 | 65 | dispatch({type: CONSTANTS.RANK_LIST_LOADING, loading: true}); 66 | ajax({ 67 | url: `${requestPrefix}/api/rankList`, 68 | method: 'post', 69 | data: { offset, limit } 70 | }).then(res => { 71 | dispatch({ 72 | type: CONSTANTS.RANK_LIST, 73 | list: res.list, 74 | hasMore: res.hasMore, 75 | loading: false 76 | }) 77 | }).catch((err) => { 78 | dispatch({type: CONSTANTS.RANK_LIST_LOADING, loading: false}); 79 | console.log(err); 80 | }) 81 | }; 82 | 83 | // 获取他人的状态列表 84 | export let getOtherList = (uid) => (dispatch, getState) => { 85 | let {offset, limit, hasMore} = getState().otherListInfo; 86 | debugger 87 | if ( !hasMore ) { return void 0; } 88 | 89 | // 请求之前设置loading状态 90 | dispatch({type: CONSTANTS.OTHER_LIST_LOADING, loading: true}); 91 | 92 | // 发起请求 93 | ajax({ 94 | url: `${requestPrefix}/api/otherlist/${uid}`, 95 | method: 'POST', 96 | data: { offset, limit } 97 | }).then((res) => { 98 | // 请求结束 99 | dispatch({ 100 | type: CONSTANTS.OTHER_LIST, 101 | list: res.list, 102 | hasMore: res.hasMore, 103 | loading: false 104 | }) 105 | }).catch((err) => { 106 | // 请求结束 107 | dispatch({type: CONSTANTS.OTHER_LIST_LOADING, loading: false}); 108 | }); 109 | }; 110 | 111 | export default { 112 | getMyList, 113 | getToadyList, 114 | getRankList, 115 | getOtherList 116 | } 117 | -------------------------------------------------------------------------------- /client-react/redux/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose, applyMiddleware } from 'redux'; 2 | 3 | // 所有的 reducer 4 | import allReducer from './reducer/index.js'; 5 | 6 | /* 中间件相关的开始 */ 7 | // 路由信息的中间件 8 | import { routerMiddleware } from 'react-router-redux'; 9 | import createHistory from 'history/createHashHistory'; 10 | const history = createHistory(); 11 | const historyMiddleware = routerMiddleware(history); 12 | // 异步中间件 13 | import thunk from 'redux-thunk'; 14 | // 中间件数组 15 | let middleware = [thunk, historyMiddleware]; 16 | /* 中间件相关的结束 */ 17 | 18 | // composeEnhancers 等同于 compose 19 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 20 | 21 | let store = createStore( 22 | allReducer, 23 | composeEnhancers( 24 | applyMiddleware(...middleware) 25 | ) 26 | ); 27 | 28 | export default store; 29 | -------------------------------------------------------------------------------- /client-react/redux/reducer/index.js: -------------------------------------------------------------------------------- 1 | import * as CONSTANTS from '../actionTypes.js'; 2 | import { combineReducers } from 'redux'; 3 | import { routerReducer } from 'react-router-redux'; 4 | import myInfo from './myInfo.js'; 5 | import listReducers from './listReducers.js'; 6 | 7 | // 获取排行列表 8 | let rankList = (state = [], action) => { 9 | switch ( action.type ) { 10 | case CONSTANTS.RANK_LIST: 11 | return action.list; 12 | } 13 | 14 | return state; 15 | }; 16 | 17 | // 详情处理 18 | let detail = (state={}, action) => { 19 | switch (action.type) { 20 | case CONSTANTS.DETAIL_INFO: 21 | return Object.assign({}, state, action.info); 22 | } 23 | return state; 24 | }; 25 | 26 | // 获取他人信息 27 | let otherInfo = (state={list: []}, action) => { 28 | switch (action.type) { 29 | case CONSTANTS.OTHER_USER: 30 | return Object.assign({}, state, action.info); 31 | case CONSTANTS.OTHER_USER_CLEAR: 32 | return Object.assign({}, { 33 | avatar: null, 34 | continued: null, 35 | getupTime: null, 36 | rank: null, 37 | uid: null, 38 | userName: null 39 | }); 40 | } 41 | 42 | return state; 43 | }; 44 | 45 | export default combineReducers({ 46 | myInfo: myInfo, 47 | rankList: rankList, 48 | detail: detail, 49 | otherInfo: otherInfo, 50 | ...listReducers, // myListInfo, todayListInfo 51 | routing: routerReducer 52 | }); 53 | -------------------------------------------------------------------------------- /client-react/redux/reducer/listReducers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/7/9. 3 | */ 4 | import * as CONSTANTS from '../actionTypes.js'; 5 | import cloneDeep from 'lodash.clonedeep'; 6 | 7 | let defaultListInfo = { 8 | list: [], offset: 0, limit: 10, 9 | hasMore: true, loading: false, isEmpty: false, isInit: false 10 | }; 11 | 12 | // 个人状态列表 13 | let myListInfo = (state = cloneDeep(defaultListInfo), action) => { 14 | switch (action.type) { 15 | case CONSTANTS.MY_LIST: 16 | let newList = [...state.list, ...action.list]; 17 | 18 | return Object.assign({}, state, { 19 | list: newList, 20 | offset: action.list.length + state.offset, 21 | hasMore: action.hasMore, 22 | loading: false, 23 | isEmpty: newList.length === 0, 24 | isInit: true 25 | }); 26 | case CONSTANTS.MY_LIST_LOADING: 27 | return Object.assign({}, state, { 28 | loading: action.loading 29 | }) 30 | } 31 | 32 | return state; 33 | }; 34 | 35 | // 获取今日列表 36 | let todayListInfo = (state = cloneDeep(defaultListInfo), action) => { 37 | switch (action.type) { 38 | case CONSTANTS.TODAY_LIST: 39 | let newList = [...state.list, ...action.list]; 40 | return Object.assign({}, state, { 41 | list: newList, 42 | offset: action.list.length + state.offset, 43 | hasMore: action.hasMore, 44 | loading: false, 45 | isEmpty: newList.length === 0, 46 | isInit: true 47 | }); 48 | case CONSTANTS.TODAY_LIST_LOADING: 49 | return Object.assign({}, state, { 50 | loading: action.loading 51 | }) 52 | } 53 | 54 | return state; 55 | }; 56 | 57 | // 排行榜的列表 58 | let rankListInfo = (state = cloneDeep(defaultListInfo), action) => { 59 | switch (action.type) { 60 | case CONSTANTS.RANK_LIST: 61 | let newList = [...state.list, ...action.list]; 62 | return Object.assign({}, state, { 63 | list: newList, 64 | offset: action.list.length + state.offset, 65 | hasMore: action.hasMore, 66 | loading: false, 67 | isEmpty: newList.length === 0, 68 | isInit: true 69 | }); 70 | case CONSTANTS.RANK_LIST_LOADING: 71 | return Object.assign({}, state, { 72 | loading: action.loading 73 | }) 74 | } 75 | 76 | return state; 77 | }; 78 | 79 | // 他人的状态列表信息 80 | let otherListInfo = (state = cloneDeep(defaultListInfo), action) => { 81 | switch (action.type) { 82 | case CONSTANTS.OTHER_LIST: 83 | let newList = [...state.list, ...action.list]; 84 | return Object.assign({}, state, { 85 | list: newList, 86 | offset: state.offset + action.list.length, 87 | hasMore: action.hasMore, 88 | loading: false, 89 | isEmpty: newList.length === 0, 90 | isInit: true 91 | }); 92 | case CONSTANTS.OTHER_LIST_LOADING: 93 | return Object.assign({}, state, { 94 | loading: action.loading 95 | }); 96 | case CONSTANTS.OTHER_LIST_CLEAR: 97 | return cloneDeep(defaultListInfo); 98 | } 99 | 100 | return state; 101 | }; 102 | 103 | export default { 104 | myListInfo, // 我的状态个人列表 105 | todayListInfo, // 今日列表 106 | rankListInfo, // 排行榜列表 107 | otherListInfo // 他人状态列表 108 | } 109 | -------------------------------------------------------------------------------- /client-react/redux/reducer/myInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/7/9. 3 | */ 4 | import * as CONSTANTS from '../actionTypes.js'; 5 | 6 | // 个人信息 7 | let defaultMyInfo = { 8 | userName: null, 9 | avatar: null, 10 | getupTime: null, 11 | rank: null, 12 | continued: null, 13 | uid: null, 14 | loading: false 15 | }; 16 | let myInfo = (state = defaultMyInfo, action) => { 17 | switch (action.type) { 18 | case CONSTANTS.MY_INFO: 19 | // 获取个人信息 20 | return Object.assign({}, state, { 21 | userName: action.userName, 22 | avatar: action.avatar, 23 | getupTime: action.getupTime, 24 | rank: action.rank, 25 | continued: action.continued, 26 | uid: action.uid, 27 | loading: action.loading 28 | }); 29 | case CONSTANTS.MY_INFO_LOADING: 30 | // 设置loading状态 31 | return Object.assign({}, state, { 32 | loading: action.loading 33 | }); 34 | case CONSTANTS.GET_UP_TIME: 35 | // 设置起床时间 36 | return Object.assign({}, state, { 37 | getupTime: action.value 38 | }); 39 | case CONSTANTS.RECOVER_MYINFO: 40 | // 通过时间旅行设置个人信息 41 | return Object.assign({}, state, { 42 | ...action.myInfo 43 | }) 44 | } 45 | 46 | return state; 47 | }; 48 | 49 | export default myInfo; 50 | -------------------------------------------------------------------------------- /client-react/style/common.less: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f8f8f8; 3 | } 4 | 5 | .clearfix:after { 6 | content: '.'; 7 | display: block; 8 | height: 0; 9 | clear: both; 10 | visibility: hidden; 11 | } 12 | -------------------------------------------------------------------------------- /client-react/style/iconfont.less: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('//at.alicdn.com/t/font_jmy7uvur11dims4i.eot?t=1495043119978'); /* IE9*/ 4 | src: url('//at.alicdn.com/t/font_jmy7uvur11dims4i.eot?t=1495043119978#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('//at.alicdn.com/t/font_jmy7uvur11dims4i.woff?t=1495043119978') format('woff'), /* chrome, firefox */ 6 | url('//at.alicdn.com/t/font_jmy7uvur11dims4i.ttf?t=1495043119978') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('//at.alicdn.com/t/font_jmy7uvur11dims4i.svg?t=1495043119978#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-tianjia:before { content: "\e62d"; } 19 | 20 | .icon-liebiao:before { content: "\e606"; } 21 | 22 | .icon-paixingbang:before { content: "\e641"; } 23 | 24 | .icon-gerenzhongxin:before { content: "\e6ab"; } 25 | 26 | .icon-zhuye:before { content: "\e648"; } 27 | 28 | .icon-zan:before { content: "\e870"; } 29 | 30 | .icon-zan1:before { content: "\e871"; } 31 | -------------------------------------------------------------------------------- /client-react/style/index.less: -------------------------------------------------------------------------------- 1 | @import "./reset.less"; 2 | @import "./iconfont.less"; 3 | @import "./common.less"; 4 | -------------------------------------------------------------------------------- /client-react/style/reset.less: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | ul { 8 | list-style: none; 9 | } 10 | 11 | a, a:link, a:visited, a:hover, a:active { 12 | color: #000; 13 | } 14 | -------------------------------------------------------------------------------- /client-react/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 我是早鸟-react 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /client-react/template/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 登陆-react 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /client-react/util/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/5/23. 3 | */ 4 | let ajax = ({ method='', url='', async=true, data={}, headers={} }) => { 5 | let xhr = new XMLHttpRequest(); 6 | xhr.open(method, url, async); 7 | 8 | for ( let key in headers ) { 9 | xhr.setRequestHeader(key, headers[key]); 10 | } 11 | if ( method.toUpperCase() === 'POST' ) { 12 | xhr.setRequestHeader('content-type', 'application/json'); 13 | } 14 | 15 | let sendString = typeof data === 'string' ? data : JSON.stringify(data); 16 | xhr.send(sendString); 17 | 18 | return new Promise((resolve, reject) => { 19 | xhr.onload = function () { 20 | resolve(JSON.parse(xhr.responseText)); 21 | }; 22 | xhr.onerror = function () { 23 | reject(xhr.responseText); 24 | }; 25 | // xhr.onreadystatechange = function () { 26 | // if ( xhr.status === 200 && xhr.readyState === 4 ) { 27 | // resolve(xhr.responseText); 28 | // } else { 29 | // reject(xhr.responseText); 30 | // } 31 | // } 32 | }); 33 | }; 34 | 35 | export { ajax } 36 | export default { 37 | ajax 38 | } 39 | -------------------------------------------------------------------------------- /client-react/webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/5/10. 3 | */ 4 | const webpack = require('webpack'); 5 | const path = require('path'); 6 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 7 | const OpenBrowserPlugin = require('open-browser-webpack-plugin'); 8 | 9 | let isDev = process.env.NODE_ENV === 'develop'; // 是否是开发环境 10 | const host = 'localhost'; 11 | const port = 8601; 12 | const serverPort = 8333; 13 | 14 | module.exports = { 15 | entry: { 16 | vendor: ['babel-polyfill', 'react', 'react-dom', 'redux', 'react-redux', 'react-router-dom'], 17 | main: './page/main/main.js', 18 | login: './page/login/login.jsx' 19 | }, 20 | output: { 21 | path: path.resolve(__dirname, '../dist'), 22 | publicPath: isDev ? `http://${host}:${port}/` : '/', 23 | filename: '[name].bundle.js' 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.(jsx|js)$/, 29 | exclude: path.resolve(__dirname, '../node_modules/'), 30 | use: 'babel-loader' 31 | }, 32 | { 33 | test: /\.(less|css)$/, 34 | use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'] 35 | } 36 | ] 37 | }, 38 | resolve: { 39 | alias: { 40 | '@': path.join(__dirname) 41 | }, 42 | extensions: ['.js', '.jsx'] 43 | }, 44 | devtool: isDev ? 'cheap-module-eval-source-map' : '', 45 | context: __dirname, 46 | devServer: { 47 | hot: true, 48 | port: port, 49 | headers: { 50 | 'Access-Control-Allow-Origin': '*' 51 | }, 52 | proxy: { 53 | '/api': { 54 | target: `http://${host}:${serverPort}/api`, 55 | pathRewrite: {"^/api" : ""} 56 | }, 57 | '/login': { 58 | target: `http://${host}:${serverPort}/login`, 59 | pathRewrite: {"^/login" : ""} 60 | } 61 | } 62 | }, 63 | plugins: [ 64 | new OpenBrowserPlugin({ url: `http://${host}:${port}/` }), 65 | new webpack.HotModuleReplacementPlugin(), 66 | new webpack.DefinePlugin({ 67 | 'process.env.NODE_ENV': 68 | isDev ? JSON.stringify('develop') : JSON.stringify('production') 69 | }), 70 | new webpack.optimize.CommonsChunkPlugin({ 71 | name: 'vendor', 72 | filename: '[name].bundle.js', 73 | minChunks: Infinity 74 | }), 75 | new HtmlWebpackPlugin({ 76 | template: './template/index.html', 77 | filename: 'index.html', 78 | chunks: ['vendor', 'main'], 79 | inject: true 80 | }), 81 | new HtmlWebpackPlugin({ 82 | template: './template/login.html', 83 | filename: 'login.html', 84 | chunks: ['vendor', 'login'], 85 | inject: true 86 | }) 87 | ] 88 | }; 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /client-vue/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "browsers": ["Android >= 4.0", "iOS >= 7"] 6 | } 7 | }], 8 | "babel-preset-stage-0" 9 | ], 10 | "ignore": [ 11 | "node_modules" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /client-vue/.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /client-vue/component/feedCard/feedCard.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 32 | 33 | 54 | -------------------------------------------------------------------------------- /client-vue/component/footer/footer.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | -------------------------------------------------------------------------------- /client-vue/component/header/header.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /client-vue/component/scrollList/scrollList.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 85 | 86 | 89 | -------------------------------------------------------------------------------- /client-vue/container/frame/frame.less: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | .app-container { 6 | height: 100%; 7 | position: relative; 8 | } 9 | 10 | .page-wrap { 11 | position: absolute; 12 | width: 100%; 13 | height: 100%; 14 | padding: 50px 0; 15 | left: 0; 16 | top: 0; 17 | overflow: auto; 18 | } 19 | -------------------------------------------------------------------------------- /client-vue/container/frame/frame.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /client-vue/container/index/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 59 | 60 | 88 | -------------------------------------------------------------------------------- /client-vue/container/mine/mine.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /client-vue/container/rank/rank.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /client-vue/container/today/today.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /client-vue/container/wrap.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /client-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client-vue", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev-web": "cross-env NODE_ENV=develop webpack-dev-server --config ./webpack.config.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "vue": "^2.4.4", 14 | "vue-router": "^2.7.0", 15 | "vuex": "^2.4.1", 16 | "weui": "^1.1.2" 17 | }, 18 | "devDependencies": { 19 | "autoprefixer": "^7.1.4", 20 | "babel-core": "^6.26.0", 21 | "babel-loader": "^7.1.2", 22 | "babel-polyfill": "^6.26.0", 23 | "babel-preset-env": "^1.6.0", 24 | "babel-preset-stage-0": "^6.24.1", 25 | "cross-env": "^5.0.5", 26 | "css-loader": "^0.28.7", 27 | "extract-text-webpack-plugin": "^3.0.0", 28 | "html-webpack-plugin": "^2.30.1", 29 | "less": "^2.7.2", 30 | "less-loader": "^4.0.5", 31 | "open-browser-webpack-plugin": "0.0.5", 32 | "postcss-loader": "^2.0.6", 33 | "style-loader": "^0.18.2", 34 | "vue-loader": "^13.0.5", 35 | "vue-template-compiler": "^2.4.4", 36 | "webpack": "^3.6.0", 37 | "webpack-dashboard": "^1.0.0-5", 38 | "webpack-dev-server": "^2.9.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client-vue/page/main/main.js: -------------------------------------------------------------------------------- 1 | import 'weui'; 2 | import Vue from 'vue'; 3 | import VueRouter from 'vue-router'; 4 | 5 | import Wrap from '../../container/wrap.vue'; 6 | import Frame from '../../container/frame/frame.vue'; 7 | import Index from '../../container/index/index.vue'; 8 | import Today from '../../container/today/today.vue'; 9 | import Rank from '../../container/rank/rank.vue'; 10 | import Mine from '../../container/mine/mine.vue'; 11 | 12 | // vuex的store 13 | import store from '../../vuex'; 14 | 15 | // 路由配置 16 | Vue.use(VueRouter); 17 | let router = new VueRouter({ 18 | routes: [ 19 | { 20 | path: '/', 21 | component: Frame, 22 | redirect: '/index', 23 | children: [ 24 | {path: 'index', component: Index}, 25 | {path: 'today', component: Today}, 26 | {path: 'rank', component: Rank}, 27 | {path: 'mine', component: Mine}, 28 | ] 29 | } 30 | ] 31 | }); 32 | 33 | new Vue({ 34 | el: '.doc', 35 | router, store, 36 | render: h => h(Wrap) 37 | }); 38 | -------------------------------------------------------------------------------- /client-vue/style/common.less: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f8f8f8; 3 | } 4 | 5 | .clearfix:after { 6 | content: '.'; 7 | display: block; 8 | height: 0; 9 | clear: both; 10 | visibility: hidden; 11 | } 12 | -------------------------------------------------------------------------------- /client-vue/style/iconfont.less: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('//at.alicdn.com/t/font_jmy7uvur11dims4i.eot?t=1495043119978'); /* IE9*/ 4 | src: url('//at.alicdn.com/t/font_jmy7uvur11dims4i.eot?t=1495043119978#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('//at.alicdn.com/t/font_jmy7uvur11dims4i.woff?t=1495043119978') format('woff'), /* chrome, firefox */ 6 | url('//at.alicdn.com/t/font_jmy7uvur11dims4i.ttf?t=1495043119978') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('//at.alicdn.com/t/font_jmy7uvur11dims4i.svg?t=1495043119978#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-tianjia:before { content: "\e62d"; } 19 | 20 | .icon-liebiao:before { content: "\e606"; } 21 | 22 | .icon-paixingbang:before { content: "\e641"; } 23 | 24 | .icon-gerenzhongxin:before { content: "\e6ab"; } 25 | 26 | .icon-zhuye:before { content: "\e648"; } 27 | 28 | .icon-zan:before { content: "\e870"; } 29 | 30 | .icon-zan1:before { content: "\e871"; } 31 | -------------------------------------------------------------------------------- /client-vue/style/index.less: -------------------------------------------------------------------------------- 1 | @import "./reset.less"; 2 | @import "./iconfont.less"; 3 | @import "./common.less"; 4 | -------------------------------------------------------------------------------- /client-vue/style/reset.less: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | ul { 8 | list-style: none; 9 | } 10 | 11 | a, a:link, a:visited, a:hover, a:active { 12 | color: #000; 13 | } 14 | -------------------------------------------------------------------------------- /client-vue/template/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 我是早鸟 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /client-vue/util/util.js: -------------------------------------------------------------------------------- 1 | let ajax = ({method='', url='', async=true, data={}, query={}, headers={}}) => { 2 | return new Promise((resolve, reject) => { 3 | let xhr = new XMLHttpRequest(); 4 | xhr.open(method, url, async); 5 | 6 | for (let key in headers) { 7 | xhr.setRequestHeader(key, headers[key]); 8 | } 9 | 10 | if (method.toUpperCase() === 'POST') { 11 | xhr.setRequestHeader('Content-Type', 'application/json'); 12 | } 13 | 14 | let sendString = typeof data === 'string' ? data : JSON.stringify(data); 15 | xhr.send(sendString); 16 | 17 | xhr.onload = () => { 18 | resolve(JSON.parse(xhr.responseText)) 19 | }; 20 | 21 | xhr.onerror = () => { 22 | reject(xhr.responseText); 23 | }; 24 | }); 25 | }; 26 | 27 | export {ajax} 28 | -------------------------------------------------------------------------------- /client-vue/vuex/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | Vue.use(Vuex); 5 | 6 | import main from './modules/main.js'; 7 | import today from './modules/today.js'; 8 | import rank from './modules/rank.js'; 9 | 10 | 11 | let store = new Vuex.Store({ 12 | modules: { 13 | main, today, rank 14 | } 15 | }); 16 | if (window) { 17 | window._store = store; // 这样做不好 但是嘿嘿嘿😝 18 | } 19 | export default store; 20 | -------------------------------------------------------------------------------- /client-vue/vuex/modules/main.js: -------------------------------------------------------------------------------- 1 | import * as CONSTANTS from '../mutation-types.js'; 2 | import {ajax} from '@/util/util.js'; 3 | let requestPrefix = 'http://localhost:8333'; 4 | 5 | let mainModule = { 6 | state: { 7 | myInfo: { 8 | userName: null, 9 | avatar: null, 10 | getupTime: null, 11 | rank: null, 12 | continued: null, 13 | uid: null, 14 | loading: true 15 | }, 16 | myList: { 17 | list: [], 18 | status: 'loading', // init loading empty loaded 19 | hasMore: true, 20 | offset: 0, 21 | limit: 10, 22 | } 23 | }, 24 | getters: { 25 | 26 | }, 27 | actions: { 28 | getMyInfo ({commit, state}) { 29 | ajax({ 30 | url: `${requestPrefix}/api/myinfo`, 31 | method: 'get' 32 | }).then(res => { 33 | commit({ 34 | type: CONSTANTS.GET_MYINFO, 35 | myInfo: res 36 | }); 37 | }).catch(err => { 38 | console.log(err); 39 | }) 40 | }, 41 | getMyList ({commit, state}, action) { 42 | let {offset, limit} = state.myList; 43 | if (action === 'init') { 44 | offset = 0; 45 | limit = 10; 46 | } 47 | 48 | commit({type: CONSTANTS.MYLIST_STATUS, status: 'loading'}); 49 | ajax({ 50 | url: `${requestPrefix}/api/mylist`, 51 | method: 'post', 52 | data: { offset, limit }, 53 | }).then((res) => { 54 | commit({ 55 | type: CONSTANTS.GET_MYLIST, 56 | myList: res, 57 | action: action 58 | }) 59 | }).catch((err) => { 60 | commit({type: CONSTANTS.MYLIST_STATUS, status: 'empty'}); 61 | console.error(err); 62 | }); 63 | } 64 | }, 65 | mutations: { 66 | [CONSTANTS.GET_MYINFO] (state, payload) { 67 | state.myInfo = { 68 | ...payload.myInfo, loading: false 69 | }; 70 | }, 71 | [CONSTANTS.GET_MYLIST] (state, payload) { 72 | let {list, hasMore} = payload.myList; 73 | let newList = [...state.myList.list, ...list]; 74 | 75 | state.myList.list = newList; 76 | state.myList.offset = newList.length; 77 | state.myList.hasMore = hasMore; 78 | 79 | if (list.length === 0 && hasMore === false) { 80 | debugger 81 | state.myList.status = 'empty'; 82 | } else { 83 | state.myList.status = 'loaded'; 84 | } 85 | }, 86 | [CONSTANTS.MYLIST_STATUS] (state, payload) { 87 | state.myList.status = payload.status; 88 | } 89 | }, 90 | }; 91 | 92 | export default mainModule; 93 | -------------------------------------------------------------------------------- /client-vue/vuex/modules/rank.js: -------------------------------------------------------------------------------- 1 | let rankModule = { 2 | state: { 3 | 4 | }, 5 | getters: { 6 | 7 | }, 8 | actions: { 9 | 10 | }, 11 | mutations: { 12 | 13 | }, 14 | }; 15 | 16 | export default rankModule; 17 | -------------------------------------------------------------------------------- /client-vue/vuex/modules/today.js: -------------------------------------------------------------------------------- 1 | let todayModule = { 2 | state: { 3 | 4 | }, 5 | getters: { 6 | 7 | }, 8 | actions: { 9 | 10 | }, 11 | mutations: { 12 | 13 | }, 14 | }; 15 | 16 | export default todayModule; 17 | -------------------------------------------------------------------------------- /client-vue/vuex/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const GET_MYINFO = 'get_myinfo'; // 获取我的信息 2 | 3 | export const GET_MYLIST = 'get_mylist'; // 获取我的个人列表 4 | export const MYLIST_STATUS = 'mylist_status'; // 我的列表的loading状态 5 | -------------------------------------------------------------------------------- /client-vue/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | 7 | const port = 8602; 8 | 9 | module.exports = { 10 | entry: { 11 | vendor: ['babel-polyfill', 'vue', 'vue-router', 'vuex'], 12 | main: './page/main/main.js' 13 | }, 14 | 15 | output: { 16 | path: path.join(__dirname, '../dist'), 17 | filename: 'js/[name].js', 18 | publicPath: '/', 19 | }, 20 | 21 | resolve: { 22 | extensions: ['.js', '.vue'], 23 | alias: { 24 | '@': path.join(__dirname, './') 25 | } 26 | }, 27 | 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.vue$/, 32 | loaders: 'vue-loader', 33 | options: { 34 | loaders: { 35 | less: 'vue-style-loader!css-loader!postcss-loader!less-loader', 36 | // less: ExtractTextPlugin.extract({ 37 | // fallback: 'vue-style-loader', 38 | // use: [ 39 | // {loader: 'css-loader', options: {minimize: true, sourceMap: true}}, 40 | // {loader: 'less-loader', options: {sourceMap: true}} 41 | // ], 42 | // disable: false, 43 | // }) 44 | } 45 | } 46 | }, 47 | { 48 | test: /\.js$/, 49 | loader: 'babel-loader', 50 | exclude: /node_modules/ 51 | }, 52 | { 53 | test: /\.css|less$/, 54 | loader: 'style-loader!css-loader!postcss-loader!less-loader' 55 | } 56 | ] 57 | }, 58 | 59 | devServer: { 60 | hot: true, 61 | port: port, 62 | headers: { 63 | 'Access-Control-Allow-Origin': '*' 64 | }, 65 | }, 66 | 67 | devtool: 'cheap-module-eval-source-map', 68 | 69 | plugins: [ 70 | new webpack.HotModuleReplacementPlugin(), 71 | new HtmlWebpackPlugin({ 72 | filename: 'index.html', 73 | template: './template/main.html', 74 | chunks: ['vendor', 'main'], 75 | inject: true 76 | }), 77 | 78 | new webpack.optimize.CommonsChunkPlugin({ 79 | name: 'vendor', 80 | filename: 'js/[name].bundle.js', 81 | minChunks: Infinity 82 | }), 83 | ], 84 | }; 85 | -------------------------------------------------------------------------------- /middleware/auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/6/3. 3 | */ 4 | module.exports = async (ctx, next) => { 5 | let accepts = ctx.accepts(); // 请求头的Accept数组 6 | let whiteList = ['/api/login', '/login.html']; // 不需要鉴权的白名单 7 | let earlyToken = ctx.cookies.get('earlytoken'); // 用户的token 8 | 9 | // 直接放行的情况:1、在白名单列表 2、有鉴权cookie 10 | if ( whiteList.includes(ctx.path) || earlyToken ) { 11 | await next(); 12 | } else { 13 | // 鉴权失败的情况:1、网页要重定向 2、API请求要返回失败 3、静态资源暂时不处理 14 | if ( accepts.includes('text/html') ) { 15 | ctx.redirect('/login.html'); 16 | } else if ( ctx.path.includes('/api/') ) { 17 | ctx.body = { 18 | code: 401, 19 | message: '用户还没有登陆' 20 | } 21 | } else { 22 | await next(); 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /middleware/sleep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/6/24. 3 | */ 4 | module.exports = async (ctx, next) => { 5 | if (ctx.path.includes('/api/')) { 6 | await new Promise(resolve => setTimeout(resolve, 1500)); 7 | } 8 | await next(); 9 | }; 10 | -------------------------------------------------------------------------------- /mock/api/detail.json: -------------------------------------------------------------------------------- 1 | { 2 | "img": "http://localhost:8333/static/img/avatar/benqijiemu.jpg", 3 | "userName": "小朋友1", 4 | "userId": "222", 5 | "rank": 1, 6 | "continued": 234, 7 | "time": "2017.06.01", 8 | "like": 12, 9 | "liked": false 10 | } 11 | -------------------------------------------------------------------------------- /mock/api/myInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "userName": "漂流瓶", 3 | "avatar": "http://localhost:8333/static/img/avatar/benqijiemu.jpg", 4 | "getupTime": "9:30", 5 | "rank": 1, 6 | "continued": 234, 7 | "uid": 333 8 | } 9 | -------------------------------------------------------------------------------- /mock/api/myList.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 4 | "userName":"漂流瓶", 5 | "status":"ontime", 6 | "time":"2017.05.22", 7 | "text":"又是新的一天0", 8 | "isLike":true, 9 | "like":12, 10 | "uid":333 11 | }, 12 | { 13 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 14 | "userName":"漂流瓶", 15 | "status":"ontime", 16 | "time":"2017.05.22", 17 | "text":"又是新的一天1", 18 | "isLike":true, 19 | "like":12, 20 | "uid":333 21 | }, 22 | { 23 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 24 | "userName":"漂流瓶", 25 | "status":"ontime", 26 | "time":"2017.05.22", 27 | "text":"又是新的一天2", 28 | "isLike":true, 29 | "like":12, 30 | "uid":333 31 | }, 32 | { 33 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 34 | "userName":"漂流瓶", 35 | "status":"ontime", 36 | "time":"2017.05.22", 37 | "text":"又是新的一天3", 38 | "isLike":true, 39 | "like":12, 40 | "uid":333 41 | }, 42 | { 43 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 44 | "userName":"漂流瓶", 45 | "status":"ontime", 46 | "time":"2017.05.22", 47 | "text":"又是新的一天4", 48 | "isLike":true, 49 | "like":12, 50 | "uid":333 51 | }, 52 | { 53 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 54 | "userName":"漂流瓶", 55 | "status":"ontime", 56 | "time":"2017.05.22", 57 | "text":"又是新的一天5", 58 | "isLike":true, 59 | "like":12, 60 | "uid":333 61 | }, 62 | { 63 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 64 | "userName":"漂流瓶", 65 | "status":"ontime", 66 | "time":"2017.05.22", 67 | "text":"又是新的一天6", 68 | "isLike":true, 69 | "like":12, 70 | "uid":333 71 | }, 72 | { 73 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 74 | "userName":"漂流瓶", 75 | "status":"ontime", 76 | "time":"2017.05.22", 77 | "text":"又是新的一天7", 78 | "isLike":true, 79 | "like":12, 80 | "uid":333 81 | }, 82 | { 83 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 84 | "userName":"漂流瓶", 85 | "status":"ontime", 86 | "time":"2017.05.22", 87 | "text":"又是新的一天8", 88 | "isLike":true, 89 | "like":12, 90 | "uid":333 91 | }, 92 | { 93 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 94 | "userName":"漂流瓶", 95 | "status":"ontime", 96 | "time":"2017.05.22", 97 | "text":"又是新的一天9", 98 | "isLike":true, 99 | "like":12, 100 | "uid":333 101 | }, 102 | { 103 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 104 | "userName":"漂流瓶", 105 | "status":"ontime", 106 | "time":"2017.05.22", 107 | "text":"又是新的一天10", 108 | "isLike":true, 109 | "like":12, 110 | "uid":333 111 | }, 112 | { 113 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 114 | "userName":"漂流瓶", 115 | "status":"ontime", 116 | "time":"2017.05.22", 117 | "text":"又是新的一天11", 118 | "isLike":true, 119 | "like":12, 120 | "uid":333 121 | }, 122 | { 123 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 124 | "userName":"漂流瓶", 125 | "status":"ontime", 126 | "time":"2017.05.22", 127 | "text":"又是新的一天12", 128 | "isLike":true, 129 | "like":12, 130 | "uid":333 131 | }, 132 | { 133 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 134 | "userName":"漂流瓶", 135 | "status":"ontime", 136 | "time":"2017.05.22", 137 | "text":"又是新的一天13", 138 | "isLike":true, 139 | "like":12, 140 | "uid":333 141 | }, 142 | { 143 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 144 | "userName":"漂流瓶", 145 | "status":"ontime", 146 | "time":"2017.05.22", 147 | "text":"又是新的一天14", 148 | "isLike":true, 149 | "like":12, 150 | "uid":333 151 | }, 152 | { 153 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 154 | "userName":"漂流瓶", 155 | "status":"ontime", 156 | "time":"2017.05.22", 157 | "text":"又是新的一天15", 158 | "isLike":true, 159 | "like":12, 160 | "uid":333 161 | }, 162 | { 163 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 164 | "userName":"漂流瓶", 165 | "status":"ontime", 166 | "time":"2017.05.22", 167 | "text":"又是新的一天16", 168 | "isLike":true, 169 | "like":12, 170 | "uid":333 171 | }, 172 | { 173 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 174 | "userName":"漂流瓶", 175 | "status":"ontime", 176 | "time":"2017.05.22", 177 | "text":"又是新的一天17", 178 | "isLike":true, 179 | "like":12, 180 | "uid":333 181 | }, 182 | { 183 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 184 | "userName":"漂流瓶", 185 | "status":"ontime", 186 | "time":"2017.05.22", 187 | "text":"又是新的一天18", 188 | "isLike":true, 189 | "like":12, 190 | "uid":333 191 | }, 192 | { 193 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 194 | "userName":"漂流瓶", 195 | "status":"ontime", 196 | "time":"2017.05.22", 197 | "text":"又是新的一天19", 198 | "isLike":true, 199 | "like":12, 200 | "uid":333 201 | }, 202 | { 203 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 204 | "userName":"漂流瓶", 205 | "status":"ontime", 206 | "time":"2017.05.22", 207 | "text":"又是新的一天20", 208 | "isLike":true, 209 | "like":12, 210 | "uid":333 211 | }, 212 | { 213 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 214 | "userName":"漂流瓶", 215 | "status":"ontime", 216 | "time":"2017.05.22", 217 | "text":"又是新的一天21", 218 | "isLike":true, 219 | "like":12, 220 | "uid":333 221 | }, 222 | { 223 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 224 | "userName":"漂流瓶", 225 | "status":"ontime", 226 | "time":"2017.05.22", 227 | "text":"又是新的一天22", 228 | "isLike":true, 229 | "like":12, 230 | "uid":333 231 | }, 232 | { 233 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 234 | "userName":"漂流瓶", 235 | "status":"ontime", 236 | "time":"2017.05.22", 237 | "text":"又是新的一天23", 238 | "isLike":true, 239 | "like":12, 240 | "uid":333 241 | } 242 | ] 243 | -------------------------------------------------------------------------------- /mock/api/otherList.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 4 | "userName":"漂流瓶", 5 | "status":"ontime", 6 | "time":"2017.05.22", 7 | "text":"又是新的一天0", 8 | "isLike":true, 9 | "like":12, 10 | "uid":333 11 | }, 12 | { 13 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 14 | "userName":"漂流瓶", 15 | "status":"ontime", 16 | "time":"2017.05.22", 17 | "text":"又是新的一天1", 18 | "isLike":true, 19 | "like":12, 20 | "uid":333 21 | }, 22 | { 23 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 24 | "userName":"漂流瓶", 25 | "status":"ontime", 26 | "time":"2017.05.22", 27 | "text":"又是新的一天2", 28 | "isLike":true, 29 | "like":12, 30 | "uid":333 31 | }, 32 | { 33 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 34 | "userName":"漂流瓶", 35 | "status":"ontime", 36 | "time":"2017.05.22", 37 | "text":"又是新的一天3", 38 | "isLike":true, 39 | "like":12, 40 | "uid":333 41 | }, 42 | { 43 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 44 | "userName":"漂流瓶", 45 | "status":"ontime", 46 | "time":"2017.05.22", 47 | "text":"又是新的一天4", 48 | "isLike":true, 49 | "like":12, 50 | "uid":333 51 | }, 52 | { 53 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 54 | "userName":"漂流瓶", 55 | "status":"ontime", 56 | "time":"2017.05.22", 57 | "text":"又是新的一天5", 58 | "isLike":true, 59 | "like":12, 60 | "uid":333 61 | }, 62 | { 63 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 64 | "userName":"漂流瓶", 65 | "status":"ontime", 66 | "time":"2017.05.22", 67 | "text":"又是新的一天6", 68 | "isLike":true, 69 | "like":12, 70 | "uid":333 71 | }, 72 | { 73 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 74 | "userName":"漂流瓶", 75 | "status":"ontime", 76 | "time":"2017.05.22", 77 | "text":"又是新的一天7", 78 | "isLike":true, 79 | "like":12, 80 | "uid":333 81 | }, 82 | { 83 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 84 | "userName":"漂流瓶", 85 | "status":"ontime", 86 | "time":"2017.05.22", 87 | "text":"又是新的一天8", 88 | "isLike":true, 89 | "like":12, 90 | "uid":333 91 | }, 92 | { 93 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 94 | "userName":"漂流瓶", 95 | "status":"ontime", 96 | "time":"2017.05.22", 97 | "text":"又是新的一天9", 98 | "isLike":true, 99 | "like":12, 100 | "uid":333 101 | }, 102 | { 103 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 104 | "userName":"漂流瓶", 105 | "status":"ontime", 106 | "time":"2017.05.22", 107 | "text":"又是新的一天10", 108 | "isLike":true, 109 | "like":12, 110 | "uid":333 111 | }, 112 | { 113 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 114 | "userName":"漂流瓶", 115 | "status":"ontime", 116 | "time":"2017.05.22", 117 | "text":"又是新的一天11", 118 | "isLike":true, 119 | "like":12, 120 | "uid":333 121 | }, 122 | { 123 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 124 | "userName":"漂流瓶", 125 | "status":"ontime", 126 | "time":"2017.05.22", 127 | "text":"又是新的一天12", 128 | "isLike":true, 129 | "like":12, 130 | "uid":333 131 | }, 132 | { 133 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 134 | "userName":"漂流瓶", 135 | "status":"ontime", 136 | "time":"2017.05.22", 137 | "text":"又是新的一天13", 138 | "isLike":true, 139 | "like":12, 140 | "uid":333 141 | }, 142 | { 143 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 144 | "userName":"漂流瓶", 145 | "status":"ontime", 146 | "time":"2017.05.22", 147 | "text":"又是新的一天14", 148 | "isLike":true, 149 | "like":12, 150 | "uid":333 151 | }, 152 | { 153 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 154 | "userName":"漂流瓶", 155 | "status":"ontime", 156 | "time":"2017.05.22", 157 | "text":"又是新的一天15", 158 | "isLike":true, 159 | "like":12, 160 | "uid":333 161 | }, 162 | { 163 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 164 | "userName":"漂流瓶", 165 | "status":"ontime", 166 | "time":"2017.05.22", 167 | "text":"又是新的一天16", 168 | "isLike":true, 169 | "like":12, 170 | "uid":333 171 | }, 172 | { 173 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 174 | "userName":"漂流瓶", 175 | "status":"ontime", 176 | "time":"2017.05.22", 177 | "text":"又是新的一天17", 178 | "isLike":true, 179 | "like":12, 180 | "uid":333 181 | }, 182 | { 183 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 184 | "userName":"漂流瓶", 185 | "status":"ontime", 186 | "time":"2017.05.22", 187 | "text":"又是新的一天18", 188 | "isLike":true, 189 | "like":12, 190 | "uid":333 191 | }, 192 | { 193 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 194 | "userName":"漂流瓶", 195 | "status":"ontime", 196 | "time":"2017.05.22", 197 | "text":"又是新的一天19", 198 | "isLike":true, 199 | "like":12, 200 | "uid":333 201 | }, 202 | { 203 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 204 | "userName":"漂流瓶", 205 | "status":"ontime", 206 | "time":"2017.05.22", 207 | "text":"又是新的一天20", 208 | "isLike":true, 209 | "like":12, 210 | "uid":333 211 | }, 212 | { 213 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 214 | "userName":"漂流瓶", 215 | "status":"ontime", 216 | "time":"2017.05.22", 217 | "text":"又是新的一天21", 218 | "isLike":true, 219 | "like":12, 220 | "uid":333 221 | }, 222 | { 223 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 224 | "userName":"漂流瓶", 225 | "status":"ontime", 226 | "time":"2017.05.22", 227 | "text":"又是新的一天22", 228 | "isLike":true, 229 | "like":12, 230 | "uid":333 231 | }, 232 | { 233 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 234 | "userName":"漂流瓶", 235 | "status":"ontime", 236 | "time":"2017.05.22", 237 | "text":"又是新的一天23", 238 | "isLike":true, 239 | "like":12, 240 | "uid":333 241 | } 242 | ] 243 | -------------------------------------------------------------------------------- /mock/api/otherUser.json: -------------------------------------------------------------------------------- 1 | { 2 | "userName": "小朋友", 3 | "avatar": "http://localhost:8333/static/img/avatar/benqijiemu.jpg", 4 | "getupTime": "9:30", 5 | "rank": 1, 6 | "continued": 234, 7 | "uid": 333 8 | } 9 | -------------------------------------------------------------------------------- /mock/api/rankList.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userName":"漂流瓶33号", 4 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 5 | "getupTime":"9:30", 6 | "rank":1, 7 | "continued":323, 8 | "uid":333 9 | }, 10 | { 11 | "userName":"漂流瓶59号", 12 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 13 | "getupTime":"9:30", 14 | "rank":2, 15 | "continued":284, 16 | "uid":333 17 | }, 18 | { 19 | "userName":"漂流瓶14号", 20 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 21 | "getupTime":"9:30", 22 | "rank":3, 23 | "continued":260, 24 | "uid":333 25 | }, 26 | { 27 | "userName":"漂流瓶34号", 28 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 29 | "getupTime":"9:30", 30 | "rank":4, 31 | "continued":213, 32 | "uid":333 33 | }, 34 | { 35 | "userName":"漂流瓶18号", 36 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 37 | "getupTime":"9:30", 38 | "rank":5, 39 | "continued":200, 40 | "uid":333 41 | }, 42 | { 43 | "userName":"漂流瓶57号", 44 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 45 | "getupTime":"9:30", 46 | "rank":6, 47 | "continued":195, 48 | "uid":333 49 | }, 50 | { 51 | "userName":"漂流瓶93号", 52 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 53 | "getupTime":"9:30", 54 | "rank":7, 55 | "continued":189, 56 | "uid":333 57 | }, 58 | { 59 | "userName":"漂流瓶37号", 60 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 61 | "getupTime":"9:30", 62 | "rank":8, 63 | "continued":183, 64 | "uid":333 65 | }, 66 | { 67 | "userName":"漂流瓶96号", 68 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 69 | "getupTime":"9:30", 70 | "rank":9, 71 | "continued":176, 72 | "uid":333 73 | }, 74 | { 75 | "userName":"漂流瓶89号", 76 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 77 | "getupTime":"9:30", 78 | "rank":10, 79 | "continued":154, 80 | "uid":333 81 | }, 82 | { 83 | "userName":"漂流瓶89号", 84 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 85 | "getupTime":"9:30", 86 | "rank":11, 87 | "continued":154, 88 | "uid":333 89 | }, 90 | { 91 | "userName":"漂流瓶89号", 92 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 93 | "getupTime":"9:30", 94 | "rank":12, 95 | "continued":149, 96 | "uid":333 97 | }, 98 | { 99 | "userName":"漂流瓶48号", 100 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 101 | "getupTime":"9:30", 102 | "rank":13, 103 | "continued":139, 104 | "uid":333 105 | }, 106 | { 107 | "userName":"漂流瓶28号", 108 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 109 | "getupTime":"9:30", 110 | "rank":14, 111 | "continued":129, 112 | "uid":333 113 | }, 114 | { 115 | "userName":"漂流瓶87号", 116 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 117 | "getupTime":"9:30", 118 | "rank":15, 119 | "continued":127, 120 | "uid":333 121 | }, 122 | { 123 | "userName":"漂流瓶80号", 124 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 125 | "getupTime":"9:30", 126 | "rank":16, 127 | "continued":125, 128 | "uid":333 129 | }, 130 | { 131 | "userName":"漂流瓶11号", 132 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 133 | "getupTime":"9:30", 134 | "rank":17, 135 | "continued":84, 136 | "uid":333 137 | }, 138 | { 139 | "userName":"漂流瓶35号", 140 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 141 | "getupTime":"9:30", 142 | "rank":18, 143 | "continued":84, 144 | "uid":333 145 | }, 146 | { 147 | "userName":"漂流瓶52号", 148 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 149 | "getupTime":"9:30", 150 | "rank":19, 151 | "continued":55, 152 | "uid":333 153 | }, 154 | { 155 | "userName":"漂流瓶53号", 156 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 157 | "getupTime":"9:30", 158 | "rank":20, 159 | "continued":44, 160 | "uid":333 161 | }, 162 | { 163 | "userName":"漂流瓶92号", 164 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 165 | "getupTime":"9:30", 166 | "rank":21, 167 | "continued":40, 168 | "uid":333 169 | }, 170 | { 171 | "userName":"漂流瓶19号", 172 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 173 | "getupTime":"9:30", 174 | "rank":22, 175 | "continued":34, 176 | "uid":333 177 | }, 178 | { 179 | "userName":"漂流瓶77号", 180 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 181 | "getupTime":"9:30", 182 | "rank":23, 183 | "continued":29, 184 | "uid":333 185 | }, 186 | { 187 | "userName":"漂流瓶76号", 188 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 189 | "getupTime":"9:30", 190 | "rank":24, 191 | "continued":12, 192 | "uid":333 193 | } 194 | ] 195 | -------------------------------------------------------------------------------- /mock/api/todayList.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 4 | "userName":"漂流瓶", 5 | "status":"ontime", 6 | "time":"2017.05.22", 7 | "text":"又是新的一天0", 8 | "isLike":true, 9 | "like":12, 10 | "uid":333 11 | }, 12 | { 13 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 14 | "userName":"漂流瓶", 15 | "status":"ontime", 16 | "time":"2017.05.22", 17 | "text":"又是新的一天1", 18 | "isLike":true, 19 | "like":12, 20 | "uid":333 21 | }, 22 | { 23 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 24 | "userName":"漂流瓶", 25 | "status":"ontime", 26 | "time":"2017.05.22", 27 | "text":"又是新的一天2", 28 | "isLike":true, 29 | "like":12, 30 | "uid":333 31 | }, 32 | { 33 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 34 | "userName":"漂流瓶", 35 | "status":"ontime", 36 | "time":"2017.05.22", 37 | "text":"又是新的一天3", 38 | "isLike":true, 39 | "like":12, 40 | "uid":333 41 | }, 42 | { 43 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 44 | "userName":"漂流瓶", 45 | "status":"ontime", 46 | "time":"2017.05.22", 47 | "text":"又是新的一天4", 48 | "isLike":true, 49 | "like":12, 50 | "uid":333 51 | }, 52 | { 53 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 54 | "userName":"漂流瓶", 55 | "status":"ontime", 56 | "time":"2017.05.22", 57 | "text":"又是新的一天5", 58 | "isLike":true, 59 | "like":12, 60 | "uid":333 61 | }, 62 | { 63 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 64 | "userName":"漂流瓶", 65 | "status":"ontime", 66 | "time":"2017.05.22", 67 | "text":"又是新的一天6", 68 | "isLike":true, 69 | "like":12, 70 | "uid":333 71 | }, 72 | { 73 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 74 | "userName":"漂流瓶", 75 | "status":"ontime", 76 | "time":"2017.05.22", 77 | "text":"又是新的一天7", 78 | "isLike":true, 79 | "like":12, 80 | "uid":333 81 | }, 82 | { 83 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 84 | "userName":"漂流瓶", 85 | "status":"ontime", 86 | "time":"2017.05.22", 87 | "text":"又是新的一天8", 88 | "isLike":true, 89 | "like":12, 90 | "uid":333 91 | }, 92 | { 93 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 94 | "userName":"漂流瓶", 95 | "status":"ontime", 96 | "time":"2017.05.22", 97 | "text":"又是新的一天9", 98 | "isLike":true, 99 | "like":12, 100 | "uid":333 101 | }, 102 | { 103 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 104 | "userName":"漂流瓶", 105 | "status":"ontime", 106 | "time":"2017.05.22", 107 | "text":"又是新的一天10", 108 | "isLike":true, 109 | "like":12, 110 | "uid":333 111 | }, 112 | { 113 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 114 | "userName":"漂流瓶", 115 | "status":"ontime", 116 | "time":"2017.05.22", 117 | "text":"又是新的一天11", 118 | "isLike":true, 119 | "like":12, 120 | "uid":333 121 | }, 122 | { 123 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 124 | "userName":"漂流瓶", 125 | "status":"ontime", 126 | "time":"2017.05.22", 127 | "text":"又是新的一天12", 128 | "isLike":true, 129 | "like":12, 130 | "uid":333 131 | }, 132 | { 133 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 134 | "userName":"漂流瓶", 135 | "status":"ontime", 136 | "time":"2017.05.22", 137 | "text":"又是新的一天13", 138 | "isLike":true, 139 | "like":12, 140 | "uid":333 141 | }, 142 | { 143 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 144 | "userName":"漂流瓶", 145 | "status":"ontime", 146 | "time":"2017.05.22", 147 | "text":"又是新的一天14", 148 | "isLike":true, 149 | "like":12, 150 | "uid":333 151 | }, 152 | { 153 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 154 | "userName":"漂流瓶", 155 | "status":"ontime", 156 | "time":"2017.05.22", 157 | "text":"又是新的一天15", 158 | "isLike":true, 159 | "like":12, 160 | "uid":333 161 | }, 162 | { 163 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 164 | "userName":"漂流瓶", 165 | "status":"ontime", 166 | "time":"2017.05.22", 167 | "text":"又是新的一天16", 168 | "isLike":true, 169 | "like":12, 170 | "uid":333 171 | }, 172 | { 173 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 174 | "userName":"漂流瓶", 175 | "status":"ontime", 176 | "time":"2017.05.22", 177 | "text":"又是新的一天17", 178 | "isLike":true, 179 | "like":12, 180 | "uid":333 181 | }, 182 | { 183 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 184 | "userName":"漂流瓶", 185 | "status":"ontime", 186 | "time":"2017.05.22", 187 | "text":"又是新的一天18", 188 | "isLike":true, 189 | "like":12, 190 | "uid":333 191 | }, 192 | { 193 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 194 | "userName":"漂流瓶", 195 | "status":"ontime", 196 | "time":"2017.05.22", 197 | "text":"又是新的一天19", 198 | "isLike":true, 199 | "like":12, 200 | "uid":333 201 | }, 202 | { 203 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 204 | "userName":"漂流瓶", 205 | "status":"ontime", 206 | "time":"2017.05.22", 207 | "text":"又是新的一天20", 208 | "isLike":true, 209 | "like":12, 210 | "uid":333 211 | }, 212 | { 213 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 214 | "userName":"漂流瓶", 215 | "status":"ontime", 216 | "time":"2017.05.22", 217 | "text":"又是新的一天21", 218 | "isLike":true, 219 | "like":12, 220 | "uid":333 221 | }, 222 | { 223 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 224 | "userName":"漂流瓶", 225 | "status":"ontime", 226 | "time":"2017.05.22", 227 | "text":"又是新的一天22", 228 | "isLike":true, 229 | "like":12, 230 | "uid":333 231 | }, 232 | { 233 | "img":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 234 | "userName":"漂流瓶", 235 | "status":"ontime", 236 | "time":"2017.05.22", 237 | "text":"又是新的一天23", 238 | "isLike":true, 239 | "like":12, 240 | "uid":333 241 | } 242 | ] 243 | -------------------------------------------------------------------------------- /mock/api/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "userName":"漂流瓶", 3 | "avatar":"http://localhost:8333/static/img/avatar/benqijiemu.jpg", 4 | "getupTime":"9:30", 5 | "rank":1, 6 | "continued":234, 7 | "uid":333 8 | } 9 | -------------------------------------------------------------------------------- /mock/generateMockData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/7/9. 3 | */ 4 | function createArray(length) { 5 | // new Array(24); // 在map方法有问题 6 | let arr = []; 7 | for ( let i = 0; i < length; i++ ) { 8 | arr.push(null); 9 | } 10 | return arr; 11 | } 12 | 13 | function log(obj) { 14 | let jsonStr = JSON.stringify(obj); 15 | console.log(jsonStr); 16 | return jsonStr; 17 | } 18 | 19 | function createRandomNum(min, max) { 20 | let range = max - min; 21 | let randNum = Math.random(); 22 | let num = min + Math.round(range * randNum); 23 | return num; 24 | } 25 | 26 | function createRandomArray(length, min, max, type) { 27 | let arr = []; 28 | for ( let i = 0; i < length; i++ ) { 29 | arr.push(createRandomNum(min, max)); 30 | } 31 | 32 | if (type === 'sort') { 33 | arr.sort((a, b) => { 34 | return b-a; 35 | }); 36 | } 37 | 38 | return arr; 39 | } 40 | 41 | let myList = createArray(24); 42 | myList = myList.map((item, index) => { 43 | return { 44 | "img": "http://localhost:8333/static/img/avatar/benqijiemu.jpg", 45 | "userName": "漂流瓶", 46 | "status": "ontime", 47 | "time": "2017.05.22", 48 | "text": `又是新的一天${index}`, 49 | "isLike": true, 50 | "like": 12, 51 | "uid": 333 52 | } 53 | }); 54 | 55 | let rankList = createArray(24); 56 | let continuedArr = createRandomArray(24, 1, 365, 'sort'); 57 | let userNameArr = createRandomArray(24, 1, 100); 58 | rankList = rankList.map((item, index) => { 59 | return { 60 | "userName": `漂流瓶${userNameArr[index]}号`, 61 | "avatar": "http://localhost:8333/static/img/avatar/benqijiemu.jpg", 62 | "getupTime": "9:30", 63 | "rank": index+1, 64 | "continued": continuedArr[index], 65 | "uid": 333 66 | } 67 | }); 68 | log(rankList); 69 | -------------------------------------------------------------------------------- /mock/mock.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | node generateMockData.js > tmp.json 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "earlyjoy", 3 | "version": "1.0.0", 4 | "description": "😳The course for react stack. earlyjoy,enjoy it!", 5 | "main": "app.js", 6 | "scripts": { 7 | "dev:node": "cross-env NODE_ENV=develop nodemon app.js", 8 | "dev:node-base": "cross-env NODE_ENV=develop node app.js", 9 | "start:node": "cross-env NODE_ENV=production node app.js", 10 | "pm2:start": "pm2 startOrRestart pm2.prod.config.js" 11 | }, 12 | "dependencies": { 13 | "kcors": "^2.2.1", 14 | "koa": "^2.3.0", 15 | "koa-bodyparser": "^4.2.0", 16 | "koa-cors": "0.0.16", 17 | "koa-router": "^7.2.1", 18 | "koa-static-server": "^1.3.2", 19 | "node-fetch": "^1.7.3" 20 | }, 21 | "devDependencies": { 22 | "cross-env": "^5.0.5", 23 | "nodemon": "^1.12.1" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/geeknull/earlyjoy.git" 28 | }, 29 | "keywords": [], 30 | "author": "", 31 | "license": "ISC", 32 | "bugs": { 33 | "url": "https://github.com/geeknull/earlyjoy/issues" 34 | }, 35 | "homepage": "https://github.com/geeknull/earlyjoy#readme" 36 | } 37 | -------------------------------------------------------------------------------- /pm2.prod.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * Application configuration section 4 | * http://pm2.keymetrics.io/docs/usage/application-declaration/ 5 | */ 6 | apps : [ 7 | 8 | // First application 9 | { 10 | name : 'earlyJoy', 11 | script : 'app.js', 12 | env: { 13 | COMMON_VARIABLE: 'true' 14 | }, 15 | env_production : { 16 | NODE_ENV: 'production' 17 | } 18 | } 19 | 20 | // Second application 21 | ] 22 | 23 | /** 24 | * Deployment section 25 | * http://pm2.keymetrics.io/docs/usage/deployment/ 26 | */ 27 | // deploy : { 28 | // production : { 29 | // user : 'node', 30 | // host : '212.83.163.1', 31 | // ref : 'origin/master', 32 | // repo : 'git@github.com:repo.git', 33 | // path : '/var/www/production', 34 | // 'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production' 35 | // }, 36 | // dev : { 37 | // user : 'node', 38 | // host : '212.83.163.1', 39 | // ref : 'origin/master', 40 | // repo : 'git@github.com:repo.git', 41 | // path : '/var/www/development', 42 | // 'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env dev', 43 | // env : { 44 | // NODE_ENV: 'dev' 45 | // } 46 | // } 47 | // } 48 | }; 49 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/5/16. 3 | */ 4 | module.exports = { 5 | plugins: { 6 | 'autoprefixer': { 7 | browsers: ['> 0%', '> 1%', 'IE > 8', 'Android >= 1.6', 'iOS >= 1.0'] 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /router/api.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const Util = require('../util/util.js'); 4 | 5 | module.exports = (router) => { 6 | // 【用户】获取我的信息 7 | router.get('/api/myinfo', (ctx, next) => { 8 | ctx.body = Util.readJson(path.resolve(__dirname, '../mock/api/myInfo.json')); 9 | }); 10 | 11 | // 【列表】获取我的起床时间列表 12 | router.post('/api/mylist', async (ctx, next) => { 13 | let allList = Util.readJson(path.resolve(__dirname, '../mock/api/myList.json')); 14 | let { offset, limit } = ctx.request.body; 15 | 16 | ctx.body = { 17 | list: allList.slice(offset, offset+limit), 18 | hasMore: offset+limit <= allList.length 19 | }; 20 | }); 21 | 22 | // 【存储】保存我的信息 23 | router.post('/api/setmyinfo', async (ctx, next) => { 24 | ctx.body = { 25 | code: 200, 26 | message: '消息保存成功' 27 | } 28 | }); 29 | 30 | // 【存储】创建今日状态 31 | router.post('/api/markToday', async (ctx, next) => { 32 | ctx.body = { 33 | code: 200, 34 | message: '创建今日起床记录成功' 35 | } 36 | }); 37 | 38 | // 【列表】获取今日起床列表 39 | router.post('/api/todaylist', async (ctx, next) => { 40 | let allList = Util.readJson(path.resolve(__dirname, '../mock/api/todaylist.json')); 41 | let { offset, limit } = ctx.request.body; 42 | 43 | ctx.body = { 44 | list: allList.slice(offset, offset + limit), 45 | hasMore: offset+limit <= allList.length 46 | }; 47 | }); 48 | 49 | // 【列表】获取排行列表 50 | router.post('/api/ranklist', (ctx, next) => { 51 | let allList = Util.readJson(path.resolve(__dirname, '../mock/api/rankList.json')); 52 | let { offset, limit } = ctx.request.body; 53 | 54 | ctx.body = { 55 | list: allList.slice(offset, offset + limit), 56 | hasMore: offset+limit <= allList.length 57 | }; 58 | }); 59 | 60 | // 【详情】获取起床记录详情信息 61 | router.get('/api/detail/:id', (ctx, next) => { 62 | let requestDetailId = ctx.params.id; 63 | ctx.body = Util.readJson(path.resolve(__dirname, '../mock/api/detail.json')); 64 | }); 65 | 66 | // 【用户】获取他人的信息 67 | router.get('/api/otheruser/:id', (ctx, next) => { 68 | let requestUid = ctx.params.id; 69 | ctx.body = Util.readJson(path.resolve(__dirname, '../mock/api/otherUser.json')); 70 | }); 71 | 72 | // 【列表】获取他人的列表 73 | router.post('/api/otherlist/:id', (ctx, next) => { 74 | let allList = Util.readJson(path.resolve(__dirname, '../mock/api/otherList.json')); 75 | let { offset, limit } = ctx.request.body; 76 | 77 | ctx.body = { 78 | list: allList.slice(offset, offset + limit), 79 | hasMore: offset+limit <= allList.length 80 | }; 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /router/index.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | const router = new Router(); 3 | const api = require('./api.js'); 4 | const page = require('./page.js'); 5 | const login = require('./login.js'); 6 | 7 | page(router); 8 | api(router); 9 | login(router); 10 | 11 | module.exports = (app) => { 12 | app.use(router.routes()) 13 | }; 14 | -------------------------------------------------------------------------------- /router/login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/6/3. 3 | */ 4 | module.exports = (router) => { 5 | router.all('/api/login', (ctx, next) => { 6 | console.log('xxxx'); 7 | ctx.cookies.set('earlytoken', 'imnolyaearlyjoytoken', { 8 | maxAge: 1000*60*60*2 9 | }); 10 | ctx.body = { 11 | status: true 12 | }; 13 | }) 14 | }; 15 | -------------------------------------------------------------------------------- /router/page.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const fetch = require('node-fetch'); 4 | let isDev = process.env.NODE_ENV === 'develop'; 5 | 6 | module.exports = (router) => { 7 | router.get('/', async (ctx, next) => { 8 | if ( isDev ) { 9 | let res = await fetch('http://localhost:9333'); 10 | ctx.body = await res.text(); 11 | } else { 12 | ctx.body = fs.readFileSync(path.join(__dirname, '../dist/index.html'), 'utf-8'); 13 | } 14 | }); 15 | 16 | router.get('/login.html', async (ctx, next) => { 17 | if ( isDev ) { 18 | let res = await fetch('http://localhost:9333/login.html'); 19 | ctx.body = await res.text(); 20 | } else { 21 | ctx.body = fs.readFileSync(path.join(__dirname, '../dist/login.html'), 'utf-8'); 22 | } 23 | }); 24 | }; 25 | 26 | 27 | // console.log(process.env.NODE_ENV); 28 | -------------------------------------------------------------------------------- /static/img/avatar/benqijiemu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geeknull/earlyjoy/048cb592b1635344eafd52413122137d6e470327/static/img/avatar/benqijiemu.jpg -------------------------------------------------------------------------------- /util/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Weil on 2017/7/7. 3 | */ 4 | const fs = require('fs'); 5 | 6 | module.exports = { 7 | readJson: (jsonPath) => { 8 | let jsonStr = fs.readFileSync(jsonPath, 'utf-8'); 9 | return JSON.parse(jsonStr); 10 | } 11 | }; 12 | --------------------------------------------------------------------------------