├── .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 |
13 | -
14 |
15 |
16 | 主页
17 |
18 |
19 | -
20 |
21 |
22 | 今日列表
23 |
24 |
25 | -
26 |
27 |
28 | 排行榜
29 |
30 |
31 | -
32 |
33 |
34 | 个人中心
35 |
36 |
37 |
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 |
29 |
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 |
13 | -
14 |
15 |
16 | 主页
17 |
18 |
19 | -
20 |
21 |
22 | 今日列表
23 |
24 |
25 | -
26 |
27 |
28 | 排行榜
29 |
30 |
31 | -
32 |
33 |
34 | 个人中心
35 |
36 |
37 |
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 |
43 |
44 | {}} disabled={true} value={formatDate(Date.now())}/>
45 |
46 |
47 |
48 |
51 |
52 | {}} disabled={true} value={'8:30'}/>
53 |
54 |
55 |
56 |
59 |
60 | {}} disabled={true} value={'准时起床'}/>
61 |
62 |
63 |
64 |
65 |
说点什么
66 |
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 |
60 |
61 |
密码
62 |
72 |
73 |
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 |
2 |
3 |
4 |
5 | {{text}}
6 | {{time}}
7 |
8 |
9 |
10 |
11 |
32 |
33 |
54 |
--------------------------------------------------------------------------------
/client-vue/component/footer/footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
51 |
--------------------------------------------------------------------------------
/client-vue/component/header/header.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
23 |
--------------------------------------------------------------------------------
/client-vue/component/scrollList/scrollList.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
21 |
--------------------------------------------------------------------------------
/client-vue/container/index/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
![]()
5 |
{{myInfo.userName}}
6 |
7 |
8 | +
9 | 添加今日状态
10 |
11 |
最近状态
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
59 |
60 |
88 |
--------------------------------------------------------------------------------
/client-vue/container/mine/mine.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | mine
4 |
5 |
6 |
--------------------------------------------------------------------------------
/client-vue/container/rank/rank.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | rank
4 |
5 |
6 |
--------------------------------------------------------------------------------
/client-vue/container/today/today.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | today
4 |
5 |
6 |
--------------------------------------------------------------------------------
/client-vue/container/wrap.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
--------------------------------------------------------------------------------