├── .eslintignore
├── .gitignore
├── screenshots
├── home.gif
├── home.png
├── label.png
├── setter.png
├── user.gif
└── user.png
├── src
├── assets
│ ├── img
│ │ ├── no.png
│ │ ├── ok.png
│ │ ├── close.png
│ │ ├── email.png
│ │ ├── home.png
│ │ ├── logo.png
│ │ ├── weibo.png
│ │ ├── avatar.png
│ │ ├── contact.png
│ │ ├── dafault.png
│ │ ├── message.png
│ │ ├── password.png
│ │ ├── popular.png
│ │ ├── weixin.png
│ │ └── login-background.png
│ └── font
│ │ ├── iconfont.eot
│ │ ├── iconfont.ttf
│ │ └── iconfont.woff
├── template
│ └── index.html
├── store
│ ├── index.js
│ ├── Setterstore.js
│ ├── Peoplestore.js
│ └── Editorstore.js
├── components
│ ├── Comment
│ │ ├── Loading.js
│ │ ├── Text.js
│ │ ├── Header.js
│ │ ├── Action.js
│ │ ├── List.js
│ │ ├── Author.js
│ │ ├── Textarea.js
│ │ └── index.js
│ ├── Modal
│ │ ├── click
│ │ │ ├── Render.js
│ │ │ └── index.js
│ │ ├── remove
│ │ │ ├── Portal.js
│ │ │ └── index.js
│ │ ├── mouseover
│ │ │ ├── Render.js
│ │ │ └── index.js
│ │ └── Message.js
│ ├── Header
│ │ ├── User
│ │ │ ├── LoginRegistered.js
│ │ │ ├── index.js
│ │ │ ├── Avatar.js
│ │ │ ├── Menu.js
│ │ │ ├── Message.js
│ │ │ └── MessageList.js
│ │ ├── index.js
│ │ ├── Write.js
│ │ └── Navigation.js
│ ├── Collect
│ │ ├── Title.js
│ │ ├── Switch.js
│ │ ├── List.js
│ │ ├── Create.js
│ │ └── index.js
│ ├── Author
│ │ ├── List.js
│ │ ├── Avatar.js
│ │ ├── Follow.js
│ │ └── index.js
│ ├── Forward
│ │ └── index.js
│ ├── Label
│ │ └── index.js
│ ├── Events
│ │ └── index.js
│ └── Page
│ │ └── index.js
├── pages
│ ├── 404
│ │ └── index.js
│ ├── Posts
│ │ ├── Sidebar
│ │ │ ├── index.js
│ │ │ ├── Order.js
│ │ │ └── Search.js
│ │ ├── Type.js
│ │ ├── List.js
│ │ ├── Author.js
│ │ ├── Action.js
│ │ ├── Article.js
│ │ ├── Html.js
│ │ └── index.js
│ ├── Article
│ │ ├── Html.js
│ │ ├── Type.js
│ │ ├── Header.js
│ │ └── index.js
│ ├── router.js
│ ├── Setter
│ │ ├── Links.js
│ │ ├── Password.js
│ │ ├── index.js
│ │ ├── Account.js
│ │ ├── PasswordView.js
│ │ └── AccountView.js
│ ├── People
│ │ ├── Sidebar
│ │ │ ├── index.js
│ │ │ ├── Achieve.js
│ │ │ └── Follow.js
│ │ ├── Router.js
│ │ ├── Links.js
│ │ ├── Content
│ │ │ ├── Dynamic.js
│ │ │ ├── Article.js
│ │ │ ├── Collect.js
│ │ │ └── Follow.js
│ │ ├── Header.js
│ │ └── index.js
│ ├── Editor
│ │ ├── Title.js
│ │ ├── Center.js
│ │ ├── index.js
│ │ ├── Image.js
│ │ ├── Write.js
│ │ └── Label.js
│ ├── Login
│ │ ├── index.js
│ │ ├── Login.js
│ │ ├── Register.js
│ │ ├── Submit.js
│ │ └── Controller.js
│ ├── Collect
│ │ ├── Sidebar.js
│ │ └── index.js
│ ├── Subscr
│ │ ├── Search.js
│ │ ├── List.js
│ │ └── index.js
│ └── App.js
├── index.js
├── utils
│ └── index.js
└── request
│ └── index.js
├── .babelrc
├── dist
└── index.html
├── .eslintrc.json
├── config
└── index.js
├── LICENSE
├── package.json
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | package-lock.json
3 | .vscode/
--------------------------------------------------------------------------------
/screenshots/home.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/screenshots/home.gif
--------------------------------------------------------------------------------
/screenshots/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/screenshots/home.png
--------------------------------------------------------------------------------
/screenshots/label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/screenshots/label.png
--------------------------------------------------------------------------------
/screenshots/setter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/screenshots/setter.png
--------------------------------------------------------------------------------
/screenshots/user.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/screenshots/user.gif
--------------------------------------------------------------------------------
/screenshots/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/screenshots/user.png
--------------------------------------------------------------------------------
/src/assets/img/no.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/no.png
--------------------------------------------------------------------------------
/src/assets/img/ok.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/ok.png
--------------------------------------------------------------------------------
/src/assets/img/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/close.png
--------------------------------------------------------------------------------
/src/assets/img/email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/email.png
--------------------------------------------------------------------------------
/src/assets/img/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/home.png
--------------------------------------------------------------------------------
/src/assets/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/logo.png
--------------------------------------------------------------------------------
/src/assets/img/weibo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/weibo.png
--------------------------------------------------------------------------------
/src/assets/img/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/avatar.png
--------------------------------------------------------------------------------
/src/assets/img/contact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/contact.png
--------------------------------------------------------------------------------
/src/assets/img/dafault.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/dafault.png
--------------------------------------------------------------------------------
/src/assets/img/message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/message.png
--------------------------------------------------------------------------------
/src/assets/img/password.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/password.png
--------------------------------------------------------------------------------
/src/assets/img/popular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/popular.png
--------------------------------------------------------------------------------
/src/assets/img/weixin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/weixin.png
--------------------------------------------------------------------------------
/src/assets/font/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/font/iconfont.eot
--------------------------------------------------------------------------------
/src/assets/font/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/font/iconfont.ttf
--------------------------------------------------------------------------------
/src/assets/font/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/font/iconfont.woff
--------------------------------------------------------------------------------
/src/assets/img/login-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manyuewuxin/mengya/HEAD/src/assets/img/login-background.png
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env","react","es2015","stage-0"],
3 |
4 | "plugins": [
5 | "syntax-dynamic-import",
6 | "transform-decorators-legacy",
7 | "transform-class-properties",
8 | "transform-regenerator",
9 | "react-hot-loader/babel",
10 | "transform-runtime"
11 | ]
12 | }
--------------------------------------------------------------------------------
/src/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Appstore from "./Appstore";
2 | import Editorstore from "./Editorstore";
3 | import Peoplestore from "./Peoplestore";
4 | import Setterstore from "./Setterstore";
5 |
6 | export default {
7 | Appstore,
8 | Editorstore,
9 | Peoplestore,
10 | Setterstore
11 | };
12 |
--------------------------------------------------------------------------------
/src/components/Comment/Loading.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | export default function Loading() {
3 | return (
4 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Comment/Text.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | const CommentText = ({ text }) => {
5 | return {text}
;
6 | };
7 |
8 | CommentText.propTypes = {
9 | text: PropTypes.string
10 | };
11 |
12 | export default CommentText;
13 |
--------------------------------------------------------------------------------
/src/pages/Posts/Sidebar/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import Search from "./Search";
3 | import Order from "./Order";
4 |
5 | export default class SidebarList extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
12 | );
13 | }
14 | }
--------------------------------------------------------------------------------
/src/components/Modal/click/Render.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @observer
6 | export default class Index extends Component {
7 | static propTypes = {
8 | modal: PropTypes.object,
9 | render: PropTypes.func
10 | };
11 | render() {
12 | return this.props.render(this.props.modal);
13 | }
14 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from "mobx-react";
4 | import { configure } from "mobx";
5 | import Main from "./pages/router";
6 | import store from "./store";
7 | import "./assets/css/app.css";
8 |
9 | configure({ enforceActions: true });
10 |
11 | ReactDOM.render(
12 |
13 |
14 | ,
15 | document.getElementById("root")
16 | );
17 |
--------------------------------------------------------------------------------
/src/components/Header/User/LoginRegistered.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | export default function Loginbutton() {
4 | return (
5 |
6 |
7 |
8 | 登录
9 |
10 |
11 | 注册
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/pages/404/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class NotFound extends Component {
5 | static propTypes = {
6 | location: PropTypes.object
7 | }
8 | render() {
9 | const { state } = this.props.location;
10 | return(
11 |
12 |
Not Found
13 |
{`没有找到${state?this.props.location.state.error:'该路径'}资源`}
14 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/Header/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import Navigation from "./Navigation";
3 | import Write from "./Write";
4 | import User from "./User";
5 |
6 | export default class Header extends Component {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Collect/Title.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @observer
6 | export default class CollectTitle extends Component {
7 | static propTypes = {
8 | is_create: PropTypes.bool,
9 | continues: PropTypes.func
10 | };
11 | render() {
12 | return (
13 |
14 |
{this.props.is_create ? "创建收藏夹" : "添加到收藏夹"}
15 |
X
16 |
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Collect/Switch.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @observer
6 | export default class CollectCreate extends Component {
7 | static propTypes = {
8 | switchs: PropTypes.func,
9 | is_create: PropTypes.bool
10 | };
11 | render() {
12 | return (
13 |
14 |
15 | {this.props.is_create ? "确认创建" : "创建收藏夹"}
16 |
17 |
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser":"babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | },
7 | "extends": ["eslint:recommended", "plugin:react/recommended"],
8 | "parserOptions": {
9 | "ecmaVersion": 2018,
10 | "sourceType": "module",
11 | "ecmaFeatures": {
12 | "experimentalObjectRestSpread": true,
13 | "jsx": true
14 | }
15 | },
16 | "plugins": ["react"],
17 | "globals": {
18 | "Promise":true
19 | },
20 | "rules": {
21 | "semi": 2,
22 | "no-console":0,
23 | "no-mixed-spaces-and-tabs":0
24 | }
25 | }
--------------------------------------------------------------------------------
/src/pages/Article/Html.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import utils from "@utils";
5 |
6 | @observer
7 | export default class Html extends Component {
8 | static propTypes = {
9 | html: PropTypes.string,
10 | date: PropTypes.string
11 | };
12 | render() {
13 | const { html, date } = this.props;
14 | const dt = utils.format(date);
15 | return (
16 |
17 |
18 |
{`发布于 ${dt.date} ${dt.time}`}
19 |
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | const format = (timestamp) => {
2 | const dt = new Date(timestamp);
3 |
4 | const date=`${dt.getFullYear()}-${dt.getMonth()+1}-${dt.getDate()}`; //日期
5 |
6 | const h=dt.getHours();
7 | const m=dt.getMinutes()<10 ? `0${dt.getMinutes()}` : dt.getMinutes();
8 | const s=dt.getSeconds()<10 ? `0${dt.getSeconds()}` : dt.getSeconds();
9 |
10 | const time=`${h}:${m}:${s}`; //时间
11 |
12 | return {date,time};
13 | };
14 |
15 | const search = (search) => {
16 | var regExp = /([^&=]+)=([\w\W]*?)(&|$)/g;
17 | var obj = {};
18 | var result;
19 | while ((result = regExp.exec(search)) !== null) {
20 | obj[result[1]] = result[2];
21 | }
22 | return obj;
23 | };
24 |
25 | export default {
26 | format,
27 | search
28 | };
--------------------------------------------------------------------------------
/src/pages/router.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
3 |
4 | import App from "./App";
5 | import NotFound from "./404";
6 | import Login from "./Login";
7 |
8 | export default class Routers extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | module.exports = {
4 | build: {
5 | env: "production",
6 | index: path.resolve(__dirname, "../src/dist/index.html"),
7 | assetsRoot: path.resolve(__dirname, "../dist"),
8 | assetsPublicPath: "/",
9 | productionSourceMap: true,
10 | productionGzip: false,
11 | productionGzipExtensions: ["js", "css"]
12 | },
13 | dev: {
14 | env: "development",
15 | port: 3000,
16 | assetsPublicPath: "/",
17 | context: [
18 | "/user",
19 | "/posts",
20 | "/file",
21 | "/editor",
22 | "/logo",
23 | "/collect",
24 | "/avatar"
25 | ],
26 | proxypath: "http://localhost:8000"
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/src/components/Header/User/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer, inject } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import Avatar from "./Avatar";
6 | import Message from "./Message";
7 | import LoginRegistered from "./LoginRegistered";
8 |
9 | @inject("Appstore")
10 | @observer
11 | export default class User extends Component {
12 | static propTypes = {
13 | Appstore: PropTypes.object
14 | };
15 | render() {
16 | if (this.props.Appstore.id !== null) {
17 | return (
18 |
19 |
20 |
21 |
22 | );
23 | } else {
24 | return ;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/pages/Article/Type.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import { Link } from "react-router-dom";
5 |
6 | @observer
7 | export default class ArticleType extends Component {
8 | static propTypes = {
9 | type: PropTypes.object
10 | };
11 | render() {
12 | const { type } = this.props;
13 | const list = type.map((types) => {
14 | return (
15 |
19 | {types}
20 |
21 | );
22 | });
23 | return ;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Author/List.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import { Link } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 |
6 | @observer
7 | export default class List extends Component {
8 | static propTypes = {
9 | posts: PropTypes.array
10 | };
11 | render() {
12 | const list = this.props.posts.map((p, index) => {
13 | return (
14 |
15 | {`${index + 1}、${p.title}`}
16 |
17 | );
18 | });
19 | return (
20 |
21 | 最近发表的文章:
22 |
23 |
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Comment/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class CommentCount extends Component {
5 | static propTypes = {
6 | count: PropTypes.number,
7 | setReversed: PropTypes.func,
8 | sort: PropTypes.number
9 | };
10 | render() {
11 | const { count, sort } = this.props;
12 | return (
13 |
14 |
15 |
{count > 0 ? `${count} 条评论` : "还没有评论"}
16 | {count > 0 ? (
17 |
18 | {sort === -1 ? "正序显示" : "倒序显示"}
19 |
20 | ) : null}
21 |
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/pages/Posts/Type.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import { Link } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 | import { toJS } from "mobx";
6 |
7 | @observer
8 | export default class ArticleType extends Component {
9 | static propTypes = {
10 | type: PropTypes.oneOfType([PropTypes.object, PropTypes.string])
11 | };
12 | render() {
13 | const t = toJS(this.props.type);
14 | const type = Array.isArray(t) ? t[0] : t;
15 | return (
16 |
17 |
18 | 来自分类:
19 |
20 | {type}
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Author/Avatar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import { Link } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 |
6 | @observer
7 | export default class Header extends Component {
8 | static propTypes = {
9 | author: PropTypes.object
10 | };
11 | render() {
12 | const { author } = this.props;
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/pages/Setter/Links.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 |
5 | export default class SwitchLike extends Component {
6 | static propTypes = {
7 | location: PropTypes.object
8 | };
9 | render() {
10 | const link = [
11 | { path: "/setter/account", type: "个人资料" },
12 | { path: "/setter/password", type: "修改密码" }
13 | ];
14 | const list = link.map((elem) => {
15 | return (
16 |
20 | {elem.type}
21 |
22 | );
23 | });
24 | return {list}
;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Modal/remove/Portal.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import ReactDOM from "react-dom";
4 |
5 | export default class RemoveModal extends Component {
6 | static propTypes = {
7 | children: PropTypes.node
8 | };
9 | constructor(props) {
10 | super(props);
11 | this.node = document.createElement("div");
12 | this.child = document.createElement("div");
13 |
14 | this.node.className = "animation_opacity";
15 | this.child.className = "animation_backdrop";
16 |
17 | this.node.appendChild(this.child);
18 | }
19 | render() {
20 | return ReactDOM.createPortal(this.props.children, this.node);
21 | }
22 |
23 | componentDidMount() {
24 | //初始化后
25 | document.body.appendChild(this.node); //插入
26 | }
27 |
28 | componentWillUnmount() {
29 | //卸载
30 | this.node.removeChild(this.child);
31 | document.body.removeChild(this.node); //删除
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/pages/People/Sidebar/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import Achieve from "./Achieve";
6 | import Follow from "./Follow";
7 |
8 | @observer
9 | export default class Sidebar extends Component {
10 | static propTypes = {
11 | author: PropTypes.object,
12 | is_myhome: PropTypes.bool
13 | };
14 | render() {
15 | const { author } = this.props;
16 | return (
17 |
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/pages/People/Sidebar/Achieve.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @observer
6 | export default class Create extends Component {
7 | static propTypes = {
8 | create_p_count: PropTypes.number,
9 | collect: PropTypes.number,
10 | like: PropTypes.number
11 | };
12 | render() {
13 | const { create_p_count, collect, like } = this.props;
14 | return (
15 |
16 |
个人成就
17 |
18 |
19 |
20 | {`创建${create_p_count}篇文章`}
21 |
22 |
23 |
24 | {`获得${like}喜欢,${collect}个收藏`}
25 |
26 |
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/pages/Editor/Title.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer, inject } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @inject("Editorstore")
6 | @observer
7 | export default class Title extends Component {
8 | static propTypes = {
9 | Editorstore: PropTypes.object
10 | };
11 | constructor(props) {
12 | super(props);
13 | this.change = this.change.bind(this);
14 | }
15 | change(e) {
16 | e.stopPropagation();
17 | this.props.Editorstore.changeTitle(e.target.value);
18 | }
19 | render() {
20 | const { title } = this.props.Editorstore;
21 | return (
22 |
23 |
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/Author/Follow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import { Link } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 |
6 | @observer
7 | export default class Follow extends Component {
8 | static propTypes = {
9 | author: PropTypes.object,
10 | is_follow: PropTypes.bool,
11 | follow: PropTypes.func
12 | };
13 | render() {
14 | const { author, is_follow } = this.props;
15 | return (
16 |
17 |
18 | 关注者:
19 |
20 | {author.followers_count}
21 |
22 |
23 |
26 | {is_follow ? "已关注" : "关注他"}
27 |
28 |
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/pages/People/Sidebar/Follow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @observer
6 | export default class FollowCount extends Component {
7 | static propTypes = {
8 | is_myhome: PropTypes.bool,
9 | followers_count: PropTypes.number,
10 | following_count: PropTypes.number
11 | };
12 | render() {
13 | const { is_myhome, followers_count, following_count } = this.props;
14 | return (
15 |
16 |
关注用户
17 |
18 |
19 |
{`${is_myhome ? "我" : "他"}关注的用户`}
20 |
{following_count}
21 |
22 |
23 |
{`关注${is_myhome ? "我" : "他"}的用户`}
24 |
{followers_count}
25 |
26 |
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/pages/Posts/List.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import Article from "./Article";
5 |
6 | @observer
7 | export default class List extends Component {
8 | //防更新抖动,利用mobx监听render引用精确更新
9 | static propTypes = {
10 | posts: PropTypes.object.isRequired,
11 | is_my_people: PropTypes.bool.isRequired
12 | };
13 | render() {
14 | const { posts, is_my_people } = this.props;
15 | const list = posts.map((article, index) => {
16 | return (
17 |
25 | );
26 | });
27 | return list.length === 0 ? (
28 | 列表为空
29 | ) : (
30 | {list}
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 mengya
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/pages/Posts/Sidebar/Order.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link } from "react-router-dom";
3 | import { inject, observer } from "mobx-react";
4 | import PropTypes from "prop-types";
5 |
6 | @inject("Appstore")
7 | @observer
8 | export default class Order extends Component {
9 | static propTypes = {
10 | Appstore: PropTypes.object
11 | };
12 | render() {
13 | const list = this.props.Appstore.app.order.map((p, index) => {
14 | return (
15 |
19 |
20 | {index + 1}
21 | {p.title}
22 |
23 |
24 | );
25 | });
26 | return (
27 |
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/pages/Login/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import { withRouter } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 | import Controller from "./Controller";
6 |
7 | @inject("Appstore")
8 | @withRouter
9 | @observer
10 | export default class Index extends Component {
11 | static propTypes = {
12 | history: PropTypes.object,
13 | location: PropTypes.object,
14 | Appstore: PropTypes.object
15 | };
16 | render() {
17 | const { Appstore, location, history } = this.props;
18 |
19 | if (Appstore.app.loginPath !== null) {
20 | return (
21 |
26 | );
27 | } else if (location.pathname === "/signin" || location.pathname === "/signup") {
28 | return (
29 |
30 | );
31 | }
32 | return null;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/pages/Posts/Author.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import { Link } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 |
6 | @observer
7 | export default class ArticleAuthor extends Component {
8 | static propTypes = {
9 | author: PropTypes.object,
10 | index: PropTypes.number
11 | };
12 | render() {
13 | const author = this.props.author[0];
14 | const index = this.props.index;
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
25 | {author.name}
26 |
27 | {`,${author.information}`}
28 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/Modal/mouseover/Render.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @observer
6 | export default class Hove extends Component {
7 | static propTypes = {
8 | Appstore: PropTypes.object,
9 | render: PropTypes.func
10 | };
11 | constructor(props) {
12 | super(props);
13 | this.transition = this.transition.bind(this);
14 | }
15 | transition(e) {
16 | e.stopPropagation();
17 | if (this.props.Appstore.hove.opacity === 0) this.props.Appstore.setState("hove", { hove_type: null });
18 | }
19 | render() {
20 | const { position, opacity } = this.props.Appstore.hove;
21 | const style = {
22 | position: "absolute",
23 | top: position.top,
24 | left: position.left,
25 | opacity: opacity,
26 | transition: "opacity 0.3s"
27 | };
28 | return (
29 |
30 | {this.props.render(this.props.Appstore.hove)}
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/Modal/mouseover/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import Render from "./Render";
5 | import Author from "../../Author";
6 | import Label from "../../Label";
7 |
8 | @inject("Appstore")
9 | @observer
10 | export default class Index extends Component {
11 | static propTypes = {
12 | Appstore:PropTypes.object
13 | };
14 | render() {
15 | if (this.props.Appstore.hove.hove_type !== null){
16 | return (
17 | {
18 | switch(hove_type){
19 | case "author":
20 | return ;
21 |
22 | case "label":
23 | return ;
24 |
25 | default:
26 | console.log('没有找到hove');
27 | return null;
28 | }
29 | }}/>
30 |
31 | );
32 | }
33 | return null;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/Header/Write.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import { Link } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 |
6 | @inject("Appstore")
7 | @observer
8 | export default class Write extends Component {
9 | static propTypes = {
10 | Appstore: PropTypes.object
11 | };
12 | constructor(props) {
13 | super(props);
14 | this.switch = this.switch.bind(this);
15 | }
16 | switch(e) {
17 | e.stopPropagation();
18 | this.props.Appstore.login("/signin", { text: "请登录后再发表文章", is: false });
19 | }
20 | render() {
21 | return (
22 |
23 | {this.props.Appstore.id == null ? (
24 |
25 |
26 | 写文章
27 |
28 | ) : (
29 |
30 |
31 | 写文章
32 |
33 | )}
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/Modal/click/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import Render from "./Render";
6 | import Collect from "../../Collect";
7 | import Forward from "../../Forward";
8 |
9 | @inject("Appstore")
10 | @observer
11 | export default class Index extends Component {
12 | static propTypes = {
13 | Appstore: PropTypes.object
14 | };
15 | render() {
16 | if(this.props.Appstore.modal.open!==null) {
17 | return(
18 | {
19 | switch(open){
20 | case "collect":
21 | return ;
22 |
23 | case "forward":
24 | return ;
25 |
26 | default:
27 | return null;
28 | }
29 | }}/>
30 | );
31 | }
32 | return null;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/pages/Setter/Password.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import PasswordView from "./PasswordView";
6 |
7 | @inject("Appstore", "Setterstore")
8 | @observer
9 | export default class Password extends Component {
10 | static propTypes = {
11 | Setterstore: PropTypes.object,
12 | Appstore: PropTypes.object
13 | };
14 | constructor(props) {
15 | super(props);
16 | this.send = this.send.bind(this);
17 | this.change = this.change.bind(this);
18 | }
19 | send(e) {
20 | e.stopPropagation();
21 | this.props.Setterstore.setPassword();
22 | }
23 |
24 | change(e) {
25 | e.stopPropagation();
26 | const name = e.target.name;
27 | this.props.Setterstore.change(name, e.target.value);
28 | }
29 |
30 | render() {
31 | return (
32 |
33 |
34 |
35 | 保存
36 |
37 |
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/pages/Setter/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Route, Switch, Redirect } from "react-router-dom";
3 | import { observer } from "mobx-react";
4 | import PropTypes from "prop-types";
5 |
6 | import Links from "./Links";
7 | import Account from "./Account";
8 | import Password from "./Password";
9 |
10 |
11 | @observer
12 | export default class Setter extends Component {
13 | static propTypes = {
14 | location: PropTypes.object
15 | };
16 | render() {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {
26 | return ;
27 | }} />
28 |
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/Comment/Action.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class CommentAction extends Component {
5 | static propTypes = {
6 | comment: PropTypes.object,
7 | author: PropTypes.array,
8 | user_id: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
9 | index: PropTypes.number
10 | };
11 | render() {
12 | const { comment, author, user_id, index } = this.props;
13 | const is_good = comment.good.includes(user_id);
14 | return (
15 |
16 |
20 | {comment.good.length}
21 |
22 |
23 |
24 | 回复
25 |
26 |
27 | {user_id === author[0]._id ? (
28 |
29 | 删除
30 |
31 | ) : null}
32 |
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/Header/User/Avatar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer, inject } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import Menu from "./Menu";
6 |
7 | @inject("Appstore")
8 | @observer
9 | export default class User extends Component {
10 | static propTypes = {
11 | Appstore: PropTypes.object
12 | };
13 | constructor(props) {
14 | super(props);
15 | this.close = this.close.bind(this);
16 | this.action = this.action.bind(this);
17 | }
18 | action(e) {
19 | e.stopPropagation();
20 | this.props.Appstore.setter_message("setter", e.target.offsetLeft - 60 + 15); //模型一半宽+被点击元素一半宽
21 | }
22 | close(e) {
23 | e.stopPropagation();
24 | this.props.Appstore.setter_message("close");
25 | }
26 | render() {
27 | const { open, left } = this.props.Appstore.header;
28 | return (
29 |
30 |
31 | {open === "setter" ? (
32 |
33 | ) : null}
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/Collect/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { Link } from "react-router-dom";
4 | import utils from "@utils";
5 |
6 | export default class CollectSidebar extends Component {
7 | static propTypes = {
8 | user: PropTypes.object,
9 | date: PropTypes.node
10 | };
11 | render() {
12 | const { user, date } = this.props;
13 | const dt = utils.format(date);
14 | return (
15 |
16 |
17 |
关于创建者
18 |
19 |
20 |
21 |
22 |
{user.name}
23 |
24 |
25 |
26 |
收藏夹状态
27 |
{`最近活动于 ${dt.date} ${
28 | dt.time
29 | }`}
30 |
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/pages/People/Router.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Route, Switch, Redirect } from "react-router-dom";
3 | import Dynamic from "./Content/Dynamic";
4 | import Article from "./Content/Article";
5 | import Collect from "./Content/Collect";
6 | import Follow from "./Content/Follow";
7 |
8 | export default class PeopleRouter extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 | {
19 | return (
20 |
26 | );
27 | }}
28 | />
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/pages/Editor/Center.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer, inject } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import { withRouter } from "react-router-dom";
5 |
6 | @inject("Editorstore")
7 | @withRouter
8 | @observer
9 | export default class Center extends Component {
10 | static propTypes = {
11 | Editorstore: PropTypes.object,
12 | history: PropTypes.object
13 | };
14 | render() {
15 | if (this.props.Editorstore.message === null) return null;
16 | const { message } = this.props.Editorstore;
17 | return (
18 |
19 |
20 |
21 | {message.text}
22 |
23 |
24 | );
25 | }
26 | componentDidUpdate() {
27 | if (
28 | this.props.Editorstore.message !== null &&
29 | this.props.Editorstore.message.is
30 | ) {
31 | this.timeID = window.setTimeout(() => {
32 | this.props.Editorstore.setMessage(null);
33 | this.props.Editorstore.empty();
34 | window.scroll(0, 0);
35 | window.clearTimeout(this.timeID);
36 | }, 1000);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/Header/Navigation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link, withRouter } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 |
5 | @withRouter
6 | export default class Navigation extends Component {
7 | static propTypes = {
8 | location: PropTypes.object
9 | };
10 | render() {
11 | const { pathname } = this.props.location;
12 | return (
13 |
14 |
15 |
16 |
19 |
20 | 首页
21 |
22 |
25 |
26 | 热门
27 |
28 |
31 |
32 | 关注
33 |
34 |
35 |
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/Comment/List.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import CommentAuthor from "./Author";
4 | import CommentText from "./Text";
5 | import CommentAction from "./Action";
6 |
7 | export default class CommentList extends Component {
8 | static propTypes = {
9 | user_id: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
10 | comments: PropTypes.array,
11 | action: PropTypes.func
12 | };
13 | render() {
14 | const list = this.props.comments.map((comments, index) => {
15 | const { comment, author } = comments;
16 | const key = Math.random()
17 | .toString(36)
18 | .substring(2, 6);
19 | return (
20 |
21 |
22 |
23 |
29 |
30 | );
31 | });
32 | return (
33 |
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/pages/People/Links.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { Link } from "react-router-dom";
4 |
5 | export default class PeopleSwitchList extends Component {
6 | static propTypes = {
7 | match: PropTypes.object
8 | };
9 | render() {
10 | const arr = [
11 | { path: "dynamic", match: "dynamic", text: "动态" },
12 | { path: "article", match: "article", text: "文章" },
13 | { path: "collect", match: "collect", text: "收藏" },
14 | { path: "following", match: "following|followers", text: "关注" }
15 | ];
16 | const match = this.props.match;
17 | const list = arr.map((route) => {
18 | const key = Math.random()
19 | .toString(36)
20 | .substring(2, 6);
21 | return (
22 |
23 |
30 | {route.text}
31 |
32 |
33 | );
34 | });
35 | return ;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/Editor/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer, inject } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import Image from "./Image";
6 | import Title from "./Title";
7 | import Write from "./Write";
8 | import Center from "./Center";
9 |
10 | @inject("Appstore", "Editorstore")
11 | @observer
12 | export default class Editor extends Component {
13 | static propTypes = {
14 | Editorstore: PropTypes.object,
15 | history: PropTypes.object,
16 | location: PropTypes.object,
17 | match: PropTypes.object
18 | };
19 | render() {
20 | if (this.props.Editorstore.loading) return
;
21 | return (
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | componentDidMount() {
33 | const posts_id =
34 | this.props.location.hash !== ""
35 | ? this.props.location.hash.split("#")[1]
36 | : null;
37 | this.props.Editorstore.getArticle(posts_id);
38 | }
39 | componentWillUnmount() {
40 | this.props.Editorstore.init();
41 | }
42 | componentDidUpdate() {
43 | if (this.props.Editorstore.loading) {
44 | this.props.Editorstore.update_init();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/Collect/List.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class CollectList extends Component {
5 | static propTypes = {
6 | posts_id: PropTypes.string,
7 | collect: PropTypes.array,
8 | collectArticle: PropTypes.func,
9 | scroll: PropTypes.func
10 | };
11 | render() {
12 |
13 | const { collect, posts_id } = this.props;
14 | const list = collect.map((collects, index) => {
15 | const iscollect = collects.collect_article.includes(posts_id);
16 | return (
17 |
20 |
21 |
22 |
23 |
24 |
{collects.name}
25 |
{`收藏 ${collects.collect_count} 篇文章`}
26 |
27 |
32 |
33 | );
34 | });
35 | return ;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/Login/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class Login extends Component {
5 | static propTypes = {
6 | state: PropTypes.object,
7 | keyup: PropTypes.func,
8 | change: PropTypes.func
9 | };
10 | render() {
11 | return (
12 |
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/Comment/Author.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 | import utils from "@utils";
5 |
6 | export default class CommentAuthor extends Component {
7 | static propTypes = {
8 | author: PropTypes.array,
9 | comment: PropTypes.object
10 | };
11 | render() {
12 | const { author, comment } = this.props;
13 | const { user, date } = comment;
14 | const authors = user.length > 1 && user[0] === user[1] ? author[0] : author[1];
15 | const dt = utils.format(date);
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
{author[0].name}
23 | {user.length > 1 ? (
24 |
25 | 回复
26 |
29 | {authors.name}
30 |
31 |
32 | ) : null}
33 |
34 |
{`${dt.date} ${dt.time}`}
35 |
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/pages/People/Content/Dynamic.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import { Link } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 | import utils from "@utils";
6 |
7 | @inject("Peoplestore")
8 | @observer
9 | export default class Dynamic extends Component {
10 | static propTypes = {
11 | Peoplestore: PropTypes.object
12 | };
13 | constructor(props) {
14 | super(props);
15 | this.type = { like: "喜欢了文章", create: "创建了文章", collect: "收藏了文章" };
16 | }
17 |
18 | render() {
19 | const { people } = this.props.Peoplestore;
20 | const list = people.map((d) => {
21 | const dt = utils.format(d.dynamic.date);
22 | return (
23 |
28 |
29 |
{this.type[d.dynamic.dynamic_type]}
30 |
{`${dt.date} ${dt.time}`}
31 |
32 |
33 |
{d.posts[0].title}
34 | {d.posts[0].image !== null ? (
35 |
36 | ) : null}
37 |
38 |
39 | );
40 | });
41 | return ;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/Comment/Textarea.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class CommentTextarea extends Component {
5 | static propTypes = {
6 | create: PropTypes.func,
7 | refx: PropTypes.object
8 | };
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | value: ""
13 | };
14 | this.change = this.change.bind(this);
15 | this.submit = this.submit.bind(this);
16 | }
17 | shouldComponentUpdate(nextProps, nextState) {
18 | return nextState.value !== this.state.value;
19 | }
20 | change(e) {
21 | e.stopPropagation();
22 | e.target.style.height = "auto";
23 | e.target.style.height = `${e.target.scrollHeight - 20}px`;
24 | e.target.scrollTop = e.target.scrollHeight;
25 |
26 | this.setState({ value: e.target.value });
27 | }
28 | submit(e) {
29 | e.stopPropagation();
30 | const { value } = this.state;
31 |
32 | this.props.create(value);
33 | this.setState({ value: "" });
34 | }
35 | render() {
36 | return (
37 |
38 |
47 | 评论
48 |
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/pages/Subscr/Search.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 |
5 | export default class Search extends Component {
6 | static propTypes = {
7 | search: PropTypes.func,
8 | path: PropTypes.string
9 | };
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | value: ""
14 | };
15 | this.change = this.change.bind(this);
16 | this.keyup = this.keyup.bind(this);
17 | }
18 | change(e) {
19 | e.stopPropagation();
20 | this.setState({ value: e.target.value });
21 | }
22 | keyup(e) {
23 | e.stopPropagation();
24 | if (e.key === "Enter" && this.state.value !== "")
25 | this.props.search(this.state.value);
26 | }
27 | render() {
28 | const { path } = this.props;
29 | return (
30 |
31 |
32 | 全部标签
33 |
34 |
37 | 已关注
38 |
39 |
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/Collect/Create.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class CollectCreateView extends Component {
5 | static propTypes = {
6 | image: PropTypes.string,
7 | openFile: PropTypes.func,
8 | refx: PropTypes.object,
9 | change: PropTypes.func,
10 | changeFile: PropTypes.func,
11 | name: PropTypes.string
12 | };
13 | render() {
14 | return (
15 |
16 |
17 | 收藏夹头像
18 |
19 |
20 |
21 |
22 |
支持jpg、jpeg、png格式
23 |
上传图片
24 |
31 |
32 |
33 |
34 | 收藏夹名
35 |
36 |
45 |
46 |
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/pages/Subscr/List.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @observer
6 | export default class List extends Component {
7 | static propTypes = {
8 | label: PropTypes.array,
9 | followtype: PropTypes.node,
10 | follow_type: PropTypes.func
11 | };
12 | render() {
13 | const { label, followtype } = this.props;
14 | const labelList = label.map((labels, index) => {
15 | const isFollow = followtype.includes(labels.type);
16 | return (
17 |
21 |
22 |
23 |
{labels.type}
24 |
25 | {`${labels.followtype_count} 关注`}
26 | {`${labels.article_count} 文章`}
27 |
28 |
32 | {isFollow ? "已关注" : "关注"}
33 |
34 |
35 |
36 | );
37 | });
38 | return (
39 |
40 | {label.length === 0 ? (
41 |
没有找到该标签
42 | ) : (
43 |
44 | )}
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/pages/Posts/Sidebar/Search.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { withRouter } from "react-router-dom";
4 |
5 | @withRouter
6 | export default class Search extends Component {
7 | static propTypes = {
8 | history: PropTypes.object
9 | };
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | value: ""
14 | };
15 | this.submit = this.submit.bind(this);
16 | this.keyup = this.keyup.bind(this);
17 | this.change = this.change.bind(this);
18 | }
19 | send(value) {
20 | if (/^.{1,30}$/.test(value)) {
21 | this.props.history.push(`/search?query=${value}`);
22 | }
23 | }
24 | change(e) {
25 | e.stopPropagation();
26 | this.setState({ value: e.target.value });
27 | }
28 | keyup(e) {
29 | e.stopPropagation();
30 | if (e.key === "Enter" && this.state.value !== "")
31 | this.send(this.state.value.replace(/\s+/g, ""));
32 | }
33 | submit(e) {
34 | e.stopPropagation();
35 | this.send(this.state.value.replace(/\s+/g, ""));
36 | }
37 | render() {
38 | return (
39 |
40 |
文章搜索
41 |
54 |
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/pages/Setter/Account.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import AccountView from "./AccountView";
6 |
7 | @inject("Appstore", "Setterstore")
8 | @observer
9 | export default class Account extends Component {
10 | static propTypes = {
11 | Appstore: PropTypes.object,
12 | Setterstore: PropTypes.object
13 | };
14 | constructor(props) {
15 | super(props);
16 | this.send = this.send.bind(this);
17 | this.changefile = this.changefile.bind(this);
18 | this.openfile = this.openfile.bind(this);
19 | this.change = this.change.bind(this);
20 | this.refx = React.createRef();
21 | }
22 |
23 | change(e) {
24 | e.stopPropagation();
25 | this.props.Setterstore.change(e.target.name, e.target.value);
26 | }
27 |
28 | openfile(e) {
29 | e.stopPropagation();
30 | this.refx.current.click();
31 | }
32 |
33 | changefile(e) {
34 | e.stopPropagation();
35 | if (e.target.files.length > 0) {
36 | this.props.Setterstore.uploadImage(e.target.files[0]);
37 | }
38 | }
39 | send(e) {
40 | e.stopPropagation();
41 | this.props.Setterstore.setInform();
42 | }
43 | render() {
44 | return (
45 |
46 |
53 |
54 | 保存
55 |
56 |
57 | );
58 | }
59 | componentDidMount() {
60 | this.props.Setterstore.init();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/Forward/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class Forward extends Component {
5 | static propTypes = {
6 | position: PropTypes.object
7 | };
8 | constructor(props) {
9 | super(props);
10 | this.openWeixin = this.openWeixin.bind(this);
11 | this.openWeibo = this.openWeibo.bind(this);
12 | }
13 | openWeibo(e) {
14 | e.stopPropagation(); //没买服务器就别转了
15 | const top = window.screen.height / 2 - 250;
16 | const left = window.screen.width / 2 - 300;
17 | const title = encodeURIComponent("封装axios");
18 | const url = encodeURIComponent(
19 | "http://localhost:3000/p/5ba0d6128e392c0e142fc3ee"
20 | );
21 |
22 | window.open(
23 | `http://service.weibo.com/share/share.php?title=${title}&url=${url}`,
24 | "分享至新浪微博",
25 | `height=500,
26 | width=600,
27 | top=${top},
28 | left=${left},
29 | toolbar=no,
30 | menubar=no,
31 | scrollbars=no,
32 | resizable=no,
33 | location=no, status=no`
34 | );
35 | }
36 | openWeixin(e) {
37 | e.stopPropagation();
38 | //待测试
39 | }
40 | render() {
41 | const position = this.props.position;
42 | return (
43 |
44 |
45 |
46 |
47 | 新浪微博
48 |
49 |
50 |
51 | 腾讯微信
52 |
53 |
54 |
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/Header/User/Menu.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import { Link } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 |
6 | @observer
7 | export default class Actionview extends Component {
8 | static propTypes = {
9 | Appstore: PropTypes.object,
10 | left: PropTypes.number
11 | };
12 | constructor(props) {
13 | super(props);
14 | this.quit = this.quit.bind(this);
15 | }
16 | quit(e) {
17 | e.stopPropagation(); //禁止冒泡,防止父捕捉
18 | this.props.Appstore.quit();
19 | }
20 |
21 | render() {
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 | 个人主页
30 |
31 |
32 |
33 |
34 |
35 | 标签管理
36 |
37 |
38 |
39 |
40 |
41 | 设置
42 |
43 |
44 |
45 |
46 |
47 | 退出
48 |
49 |
50 |
51 |
52 |
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/pages/Posts/Action.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @observer
6 | export default class ArticleAction extends Component {
7 | static propTypes = {
8 | is_like: PropTypes.bool,
9 | is_my_people: PropTypes.bool,
10 | open_comment: PropTypes.bool,
11 | open_article: PropTypes.bool,
12 | index: PropTypes.number,
13 | count: PropTypes.number,
14 | length: PropTypes.number
15 | };
16 | render() {
17 | const {
18 | is_like,
19 | is_my_people,
20 | open_comment,
21 | open_article,
22 | index,
23 | count,
24 | length
25 | } = this.props;
26 | return (
27 |
28 |
32 | {length}
33 |
34 |
35 | {open_comment ? "收起评论" : `${count} 评论`}
36 |
37 |
38 | 收藏
39 |
40 |
41 | 转发
42 |
43 | {is_my_people ? (
44 |
45 | 删除
46 | 编辑
47 |
48 | ) : null}
49 | {open_article ? (
50 |
51 | 收起∧
52 |
53 | ) : null}
54 |
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/Modal/Message.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @inject("Appstore")
6 | @observer
7 | export default class Message extends Component {
8 | static propTypes = {
9 | Appstore: PropTypes.object
10 | };
11 | constructor(props) {
12 | super(props);
13 | this.time = null;
14 | this.style = props.Appstore.styel || "animation";
15 | this.animationiteration = this.animationiteration.bind(this);
16 | this.animationend = this.animationend.bind(this);
17 | }
18 | animationiteration(e) {
19 | e.stopPropagation();
20 | e.persist();
21 | if (this.time !== null) {
22 | window.clearTimeout(this.time);
23 | this.time = null;
24 | }
25 | e.target.style.animationPlayState = "paused";
26 |
27 | this.time = window.setTimeout(() => {
28 | e.target.style.animationPlayState = "running";
29 | window.clearTimeout(this.time);
30 | this.time = null;
31 | }, 1500);
32 | }
33 |
34 | animationend(e) {
35 | e.stopPropagation();
36 | this.props.Appstore.setMessage(null);
37 | }
38 |
39 | render() {
40 | if (this.props.Appstore.message === null) return null;
41 | const { message } = this.props.Appstore;
42 | return (
43 |
47 |
48 |
{message.text}
49 |
50 | );
51 | }
52 |
53 | componentDidUpdate() {
54 | if (this.props.Appstore.queue !== null) {
55 | var queue = this.props.Appstore.queue;
56 | this.props.Appstore.queue = null;
57 | this.props.Appstore.setMessage(queue);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/Author/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import Avatar from "./Avatar";
6 | import List from "./List";
7 | import Follow from "./Follow";
8 | import { app as ajax } from "@request";
9 |
10 | @inject("Appstore")
11 | @observer
12 | export default class Author extends Component {
13 | static propTypes = {
14 | Appstore: PropTypes.object,
15 | user_id: PropTypes.string.isRequired
16 | };
17 | constructor(props) {
18 | super(props);
19 | this.state = {
20 | author: null
21 | };
22 | this.follow = this.follow.bind(this);
23 | this.mouseLeave = this.mouseLeave.bind(this);
24 | this.mouseover = this.mouseover.bind(this);
25 | }
26 |
27 | follow(e) {
28 | e.stopPropagation();
29 | this.props.Appstore.follow(this.state.author._id);
30 | }
31 |
32 | mouseover(e) {
33 | e.stopPropagation();
34 | this.props.Appstore.setState("hove", { opacity: 1 });
35 | }
36 | mouseLeave(e) {
37 | e.stopPropagation();
38 | this.props.Appstore.setState("hove", { opacity: 0 });
39 | }
40 | render() {
41 | if (this.state.author === null) {
42 | return
;
43 | }
44 | const { author } = this.state;
45 | const is_follow = this.props.Appstore.following.includes(this.state.author._id);
46 | return (
47 |
55 | );
56 | }
57 |
58 | componentDidMount() {
59 | ajax.getAuthor(this.props.user_id, false).then(({ author }) =>
60 | this.setState({ author })
61 | );
62 | }
63 | //componentWillUnmount(){}
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/Modal/remove/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import Portal from "./Portal";
4 |
5 | export default class Index extends Component {
6 | static propTypes = {
7 | visibility: PropTypes.bool,
8 | description: PropTypes.object,
9 | onRemove: PropTypes.func,
10 | offRemove: PropTypes.func
11 | };
12 | constructor(props) {
13 | super(props);
14 | this.animationiteration = this.animationiteration.bind(this);
15 | this.animationend = this.animationend.bind(this);
16 |
17 | this.no = this.no.bind(this);
18 | this.ok = this.ok.bind(this);
19 | this.refc = React.createRef(); //控制过渡动画
20 | }
21 |
22 | animationiteration(e) {
23 | //初加载
24 | e.stopPropagation();
25 | this.refc.current.style.animationPlayState = "paused";
26 | }
27 |
28 | animationend(e) {
29 | //动画结束
30 | e.stopPropagation();
31 | this.props.offRemove();
32 | }
33 |
34 | no(e) {
35 | //取消
36 | e.stopPropagation();
37 | this.refc.current.parentElement.style.opacity = 0;
38 | this.refc.current.style.animationPlayState = "running";
39 | }
40 | ok(e) {
41 | //确定
42 | e.stopPropagation();
43 | this.props.onRemove();
44 | }
45 | render() {
46 | if (this.props.visibility === false) return null;
47 | return (
48 |
49 |
54 |
{this.props.description.title}
55 |
56 | 取消
57 |
58 | 确定
59 |
60 |
61 |
62 |
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/pages/Posts/Article.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 |
4 | import PropTypes from "prop-types";
5 |
6 | import ArticleType from "./Type";
7 | import ArticleAuthor from "./Author";
8 | import ArticleHtml from "./Html";
9 | import ArticleAction from "./Action";
10 | import Comment from "@components/Comment";
11 |
12 | @inject("Appstore")
13 | @observer
14 | export default class ArticleContainer extends Component {
15 | static propTypes = {
16 | Appstore: PropTypes.object,
17 | article: PropTypes.object.isRequired,
18 | is_my_people: PropTypes.bool.isRequired,
19 | index: PropTypes.number.isRequired
20 | };
21 | render() {
22 | const { article, is_my_people, index } = this.props;
23 | const { read_article, read_comment } = this.props.Appstore.app;
24 | const user_id = this.props.Appstore.id;
25 | return (
26 |
27 |
28 |
29 |
34 |
43 | {read_comment.includes(index) ? (
44 |
49 | ) : null}
50 |
51 | );
52 | }
53 | //componentDidMount(){console.log("componentDidMount");}
54 | }
55 |
--------------------------------------------------------------------------------
/src/pages/People/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @inject("Appstore")
6 | @observer
7 | export default class PeopleHeader extends Component {
8 | static propTypes = {
9 | author: PropTypes.object,
10 | Appstore: PropTypes.object,
11 | history: PropTypes.object,
12 | match: PropTypes.object
13 | };
14 | constructor(props) {
15 | super(props);
16 | this.follow = this.follow.bind(this);
17 | this.is_myhome = false;
18 | }
19 | follow(e) {
20 | e.stopPropagation();
21 | if (this.is_myhome === false) {
22 | this.props.Appstore.follow(e.target.dataset.author_id);
23 | } else {
24 | this.props.history.push("/setter/account");
25 | }
26 | }
27 | render() {
28 | this.is_myhome =
29 | this.props.Appstore.id === this.props.match.params.id ? true : false;
30 | const is_follow = this.props.Appstore.following.includes(
31 | this.props.match.params.id
32 | )
33 | ? "已关注"
34 | : "关注他";
35 | const style = is_follow === "已关注" ? "is_follow_button" : "no_follow_button";
36 | const author = this.props.author;
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
{author.name}
45 |
{author.information}
46 |
47 |
48 |
52 | {this.is_myhome ? "编辑个人资料" : is_follow}
53 |
54 |
55 |
56 |
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/pages/Editor/Image.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer, inject } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | @inject("Editorstore")
6 | @observer
7 | export default class Image extends Component {
8 | static propTypes = {
9 | Editorstore: PropTypes.object
10 | };
11 | constructor(props) {
12 | super(props);
13 | this.uploadImage = this.uploadImage.bind(this);
14 | this.action = this.action.bind(this);
15 | this.refx = React.createRef();
16 | }
17 |
18 | uploadImage(e) {
19 | e.stopPropagation();
20 | if (e.target.files.length > 0) {
21 | this.props.Editorstore.uploadTitleImage(e.target.files[0]);
22 | e.target.value = "";
23 | }
24 | }
25 | action(e) {
26 | e.stopPropagation();
27 | const { className } = e.target;
28 | if (className == "icon_cameras" || className == "icon_camera") {
29 | this.refx.current.click();
30 | } else if (className == "icon_removes") {
31 | this.props.Editorstore.removeTitleImage();
32 | }
33 | }
34 | render() {
35 | const { image } = this.props.Editorstore;
36 | return (
37 |
38 | {image !== null ? (
39 |
40 |
41 |
42 |
43 | ) : (
44 |
48 | )}
49 | {image !== null ?
: null}
50 |
58 |
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/pages/Posts/Html.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { observer } from "mobx-react";
4 | import { Link } from "react-router-dom";
5 | import utils from "@utils";
6 |
7 | @observer
8 | export default class ArticleHtml extends Component {
9 | static propTypes = {
10 | article: PropTypes.object,
11 | open_article: PropTypes.bool,
12 | index: PropTypes.number
13 | };
14 | render() {
15 | const { article, open_article, index } = this.props;
16 | const text =
17 | article.image === null
18 | ? `
19 |
20 | ${article.text}...
21 | 阅读全文
22 |
23 | `
24 | : `
25 |
26 |
27 | ${article.text}...
28 | 阅读全文
29 |
30 | `;
31 | const html =
32 | article.image === null
33 | ? article.html
34 | : `${article.html}`;
35 | const innerHTML = open_article ? html : text;
36 | const dt = utils.format(article.date);
37 |
38 | return (
39 |
40 |
41 | {article.title}
42 |
43 |
47 | {open_article ? (
48 |
{`发布于 ${dt.date} ${dt.time}`}
49 | ) : null}
50 |
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/Header/User/Message.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer, inject } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import MessageList from "./MessageList";
6 |
7 | @inject("Appstore")
8 | @observer
9 | export default class Message extends Component {
10 | static propTypes = {
11 | Appstore: PropTypes.object
12 | };
13 | constructor(props) {
14 | super(props);
15 | this.close = this.close.bind(this);
16 | this.action = this.action.bind(this);
17 | this.scroll = this.scroll.bind(this);
18 | this.time = null;
19 | }
20 | action(e) {
21 | e.stopPropagation();
22 | this.props.Appstore.setter_message(
23 | "message",
24 | e.target.parentElement.offsetLeft - 165 + 15
25 | );
26 | }
27 | close(e) {
28 | e.stopPropagation();
29 | this.props.Appstore.setter_message("close");
30 | }
31 | scroll(e) {
32 | e.stopPropagation();
33 | if (this.time !== null) {
34 | window.clearTimeout(this.time);
35 | this.time = null;
36 | }
37 | this.time = window.setTimeout(() => {
38 | const scrollTop = Math.round(document.documentElement.scrollTop);
39 | const scrollHeight = scrollTop + window.innerHeight;
40 | if (scrollHeight >= document.documentElement.scrollHeight) {
41 |
42 |
43 | this.props.Appstore.getMessage(this.props.Appstore.header.page);
44 | window.clearTimeout(this.time);
45 | this.time = null;
46 | }
47 | }, 100);
48 | }
49 | render() {
50 | const { message, read_count, open, left } = this.props.Appstore.header;
51 | return (
52 |
53 |
54 | {read_count > 0 ? (
55 |
{read_count < 99 ? read_count : "99+"}
56 | ) : null}
57 | {open === "message" ? (
58 |
59 | ) : null}
60 |
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/pages/Setter/PasswordView.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { observer } from "mobx-react";
4 |
5 | @observer
6 | export default class PasswordView extends Component {
7 | static propTypes = {
8 | change: PropTypes.func,
9 | form: PropTypes.object
10 | };
11 | render() {
12 | const { password, newpassword, newpasswords } = this.props.form;
13 | return (
14 |
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mengya",
3 | "version": "1.0.0",
4 | "description": "",
5 | "author": "mengya <996929178@qq.com>",
6 | "license": "MIT",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "dev": "cross-env NODE_ENV=development node build/dev-server.js",
10 | "build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js",
11 | "lint": "eslint --ext .js src/ server/"
12 | },
13 | "dependencies": {
14 | "axios": "^0.17.1",
15 | "express": "^4.16.2",
16 | "mobx": "^4.2.0",
17 | "mobx-react": "^5.0.0",
18 | "mongodb": "^3.1.0-beta4",
19 | "nprogress": "^0.2.0",
20 | "prop-types": "^15.6.2",
21 | "react": "^16.4.0",
22 | "react-dom": "^16.4.0",
23 | "react-router": "^4.3.1",
24 | "react-router-dom": "^4.3.1",
25 | "wangeditor": "^3.1.1",
26 | "xss": "^0.3.7"
27 | },
28 | "devDependencies": {
29 | "babel-core": "^6.26.3",
30 | "babel-eslint": "^8.2.6",
31 | "babel-loader": "^7.1.4",
32 | "babel-plugin-transform-class-properties": "^6.24.1",
33 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
34 | "babel-plugin-transform-regenerator": "^6.26.0",
35 | "babel-plugin-transform-runtime": "^6.23.0",
36 | "babel-polyfill": "^6.26.0",
37 | "babel-preset-env": "^1.6.1",
38 | "babel-preset-es2015": "^6.24.1",
39 | "babel-preset-react": "^6.24.1",
40 | "babel-preset-stage-0": "^6.24.1",
41 | "compression-webpack-plugin": "^2.0.0",
42 | "connect-history-api-fallback": "^1.5.0",
43 | "cross-env": "^5.2.0",
44 | "css-loader": "^0.28.9",
45 | "eslint": "^5.3.0",
46 | "eslint-plugin-react": "^7.10.0",
47 | "file-loader": "^1.1.6",
48 | "friendly-errors-webpack-plugin": "^1.7.0",
49 | "html-webpack-plugin": "^3.2.0",
50 | "http-proxy-middleware": "^0.18.0",
51 | "mini-css-extract-plugin": "^0.4.1",
52 | "optimize-css-assets-webpack-plugin": "^5.0.1",
53 | "react-hot-loader": "^4.0.0",
54 | "style-loader": "^0.19.1",
55 | "webpack": "^4.16.5",
56 | "webpack-bundle-analyzer": "^3.0.2",
57 | "webpack-cli": "^3.1.0",
58 | "webpack-dev-middleware": "^3.1.3",
59 | "webpack-hot-middleware": "^2.22.3",
60 | "webpack-merge": "^4.1.4"
61 | },
62 | "engines": {
63 | "node": ">= 8.9.0",
64 | "npm": ">= 5.0.0"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/pages/People/Content/Article.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import utils from "@utils";
5 |
6 | import List from "../../Posts/List";
7 | import Events from "@components/Events";
8 | import Remove from "@components/Modal/remove";
9 |
10 | //注:这个组件是唯一的交叉store引用
11 | @inject("Appstore", "Peoplestore")
12 | @Events
13 | @observer
14 | export default class PeopleArticle extends Component {
15 | static propTypes = {
16 | Appstore: PropTypes.object,
17 | Peoplestore: PropTypes.object,
18 | match: PropTypes.object,
19 | history: PropTypes.object,
20 | location: PropTypes.object
21 | };
22 | constructor(props) {
23 | super(props);
24 | this.action = this.action.bind(this);
25 | }
26 | action(e) {
27 | if (e.target.dataset.remove) {
28 | e.stopPropagation(); //如果不是,那就继续向上冒泡
29 | this.props.Peoplestore.remove_article(Number(e.target.dataset.index));
30 | }
31 | else if (e.target.dataset.editor) {
32 | e.stopPropagation();
33 | const index = Number(e.target.dataset.index);
34 | const { posts } = this.props.Appstore;
35 | this.props.history.push(`/write#${posts[index]._id}`);
36 | }
37 | }
38 |
39 | render() {
40 | const is_my_people =
41 | this.props.Appstore.id === this.props.match.params.id ? true : false;
42 | const { visibility, description } = this.props.Peoplestore;
43 | return (
44 |
45 |
46 |
47 |
48 |
54 |
55 | );
56 | }
57 | componentWillUnmount() {
58 | this.props.Appstore.setState("app", {
59 | posts: [],
60 | read_article: [],
61 | read_comment: []
62 | });
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/pages/Article/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link } from "react-router-dom";
3 | import { inject, observer } from "mobx-react";
4 | import PropTypes from "prop-types";
5 |
6 | @inject("Appstore")
7 | @observer
8 | export default class PropsHeader extends Component {
9 | static propTypes = {
10 | Appstore: PropTypes.object,
11 | title: PropTypes.string,
12 | image: PropTypes.string,
13 | count: PropTypes.number,
14 | author: PropTypes.object
15 | };
16 |
17 | constructor(props) {
18 | super(props);
19 | this.follow = this.follow.bind(this);
20 | }
21 |
22 | follow(e) {
23 | e.stopPropagation(); //禁止冒泡
24 | this.props.Appstore.follow(this.props.author._id);
25 | }
26 | render() {
27 | const { title, image, count, author } = this.props;
28 | const is_follow = this.props.Appstore.following.includes(author._id);
29 | return (
30 |
31 |
32 |
33 |
34 |
{title}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {author.name}
45 |
46 |
47 |
{author.information}
48 |
49 |
50 |
55 | {is_follow ? "已关注" : "关注他"}
56 |
57 |
58 |
59 |
{`${count}人喜欢该文章`}
60 |
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/pages/Collect/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import ArticleEvent from "@components/Events";
6 | import List from "../Posts/List";
7 | import Sidebar from "./Sidebar";
8 |
9 | import utils from "@utils";
10 | import Page from "@components/Page";
11 |
12 | @inject("Appstore")
13 | @ArticleEvent
14 | @observer
15 | export default class Index extends Component {
16 | static propTypes = {
17 | Appstore: PropTypes.object,
18 | match: PropTypes.object,
19 | location: PropTypes.object
20 | };
21 |
22 | UNSAFE_componentWillReceiveProps(nextProps) {
23 | if (this.props.location.search !== nextProps.location.search) {
24 | this.props.Appstore.setState("app", { update: true });
25 | }
26 | }
27 |
28 | render() {
29 | if (this.props.Appstore.app.collect_loading) return null;
30 | const {
31 | posts,
32 | page,
33 | collect_user,
34 | collect_update_date
35 | } = this.props.Appstore.app;
36 | return (
37 |
49 | );
50 | }
51 | componentDidMount() {
52 | this.props.Appstore.getCollectPosts(this.props.match.params.id, 1);
53 | }
54 | componentDidUpdate() {
55 | if (this.props.Appstore.app.update) {
56 | const search = utils.search(this.props.location.search.split("?")[1]);
57 | this.props.Appstore.getCollectPosts(
58 | this.props.match.params.id,
59 | Number(search.page)
60 | );
61 | }
62 | }
63 | componentWillUnmount() {
64 | this.props.Appstore.setState("app", {
65 | collect_loading: true,
66 | update: false,
67 | page: 1
68 | });
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/pages/Login/Register.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class Register extends Component {
5 | static propTypes = {
6 | state: PropTypes.object,
7 | keyup: PropTypes.func,
8 | change: PropTypes.func
9 | };
10 | render() {
11 | return (
12 |
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/pages/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Route, Switch, Redirect } from "react-router-dom";
3 | import { hot } from "react-hot-loader";
4 | import PropTypes from "prop-types";
5 | import { inject, observer } from "mobx-react";
6 |
7 | import Posts from "./Posts"; //文章列表
8 | import Article from "./Article"; //单独一篇文章
9 | import Setter from "./Setter"; //设置用户资料
10 | import People from "./People"; //个人主页
11 | import Collect from "./Collect"; //一个收藏夹收藏的所有文章
12 | import Editor from "./Editor"; //富文本编辑器,一般是webpack编译是两个入口分开,为了简单就全部单入口应用了
13 | import Login from "./Login"; //点击登录两个入口,一种是直接路由改变,一种是追踪监听观察值loginPath
14 | import Subscr from "./Subscr"; //设置用户关注标签
15 |
16 | import Header from "@components/Header"; //页面头
17 |
18 | //不是 Portals API,全局共享且Modal和Hove内部需要监听 observable
19 | import Message from "@components/Modal/Message"; //页面全局消息提示
20 | import ClickModal from "@components/Modal/click";
21 | import MouseoverModal from "@components/Modal/mouseover";
22 |
23 | @inject("Appstore")
24 | @observer
25 | class App extends Component {
26 | static propTypes = {
27 | Appstore: PropTypes.object
28 | };
29 | render() {
30 | if(this.props.Appstore.app.error!==null) return ;
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | {
46 | return ;
47 | }} />
48 |
49 |
50 |
51 |
52 |
53 |
54 | );
55 | }
56 | }
57 | export default hot(module)(App);
58 |
--------------------------------------------------------------------------------
/src/components/Label/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import { app as ajax } from "@request";
5 |
6 | @inject("Appstore")
7 | @observer
8 | export default class Index extends Component {
9 | static propTypes = {
10 | Appstore: PropTypes.object,
11 | type: PropTypes.string.isRequired
12 | };
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | label: null
17 | };
18 | this.follow = this.follow.bind(this);
19 | this.mouseLeave = this.mouseLeave.bind(this);
20 | this.mouseover = this.mouseover.bind(this);
21 | }
22 | follow(e) {
23 | e.stopPropagation();
24 | this.props.Appstore.follow_type(this.state.label.type);
25 | }
26 | mouseover(e) {
27 | e.stopPropagation();
28 | this.props.Appstore.setState("hove", { opacity: 1 });
29 | }
30 | mouseLeave(e) {
31 | e.stopPropagation();
32 | this.props.Appstore.setState("hove", { opacity: 0 });
33 | }
34 | render() {
35 | if (this.state.label === null) {
36 | return
;
37 | }
38 | const { label } = this.state;
39 | const isFollow = this.props.Appstore.followtype.includes(label.type);
40 | return (
41 |
45 |
46 |
47 |
{label.type}
48 |
49 |
{label.describe}
50 |
51 |
52 | {`文章 ${label.article_count}`}
53 | {`关注 ${label.followtype_count}`}
54 |
55 |
56 |
59 | {isFollow ? "已关注" : "关注"}
60 |
61 |
62 | );
63 | }
64 | componentDidMount() {
65 | ajax.getLabel(`type=${this.props.type}`, 1).then(({ label }) => {
66 | this.setState({ label: label[0] });
67 | });
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/pages/People/Content/Collect.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import { Link } from "react-router-dom";
5 | import utils from "@utils";
6 | import Remove from "@components/Modal/remove";
7 |
8 | @inject("Appstore", "Peoplestore")
9 | @observer
10 | export default class Collect extends Component {
11 | static propTypes = {
12 | match: PropTypes.object,
13 | Appstore: PropTypes.object,
14 | Peoplestore: PropTypes.object
15 | };
16 | constructor(props) {
17 | super(props);
18 | this.remove = this.remove.bind(this);
19 | this.is_myhome = false;
20 | }
21 |
22 | remove(e) {
23 | e.stopPropagation(); //禁止冒泡
24 | this.props.Peoplestore.remove_collect(Number(e.target.dataset.index));
25 | }
26 |
27 | render() {
28 | this.is_myhome =
29 | this.props.Appstore.id === this.props.match.params.id ? true : false; //追踪
30 | const { people, visibility, description } = this.props.Peoplestore;
31 | const list = people.map((collect, index) => {
32 | const dt = utils.format(collect.date);
33 | return (
34 |
39 |
40 |
41 |
42 |
43 |
{collect.name}
44 |
{`${dt.date} 创建 · 收藏 ${
45 | collect.collect_count
46 | } 篇文章`}
47 |
48 |
49 |
查看
50 | {this.is_myhome ? (
51 |
52 | 删除
53 |
54 | ) : null}
55 |
56 |
57 | );
58 | });
59 | return (
60 |
61 |
62 |
68 |
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/components/Header/User/MessageList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import { Link } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 |
6 | @observer
7 | export default class MessageList extends Component {
8 | static propTypes = {
9 | Appstore: PropTypes.object,
10 | left: PropTypes.number,
11 | message: PropTypes.object,
12 | scroll: PropTypes.func
13 | };
14 | render() {
15 | const { message } = this.props;
16 | const list = [];
17 | for(let i = 0; i < message.length; i++){
18 | if(message[i].posts.length>0){
19 | const key = Math.random().toString(36).substring(2, 6);
20 | const m = message[i];
21 | if (m.message.message_type === "reply_posts") {
22 | list.push(
23 |
24 |
25 | {m.author[0].name}
26 |
27 | 评论了你的文章
28 | {m.posts[0].title}
29 |
30 | );
31 | } else if (m.message.message_type === "reply_comment") {
32 | list.push(
33 |
34 |
35 | {m.author[0].name}
36 |
37 | 在文章
38 | {m.posts[0].title}
39 | 回复了你的评论
40 |
41 | );
42 | } else if (m.message.message_type === "follow") {
43 | list.push(
44 |
45 |
46 | {m.author[0].name}
47 |
48 | 关注了你
49 |
50 | );
51 | }
52 | }
53 | }
54 | return (
55 |
56 |
57 | {list.length === 0 ? (
58 |
还没有任何消息推送
59 | ) : (
60 |
61 | )}
62 |
63 |
64 | );
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/pages/Article/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import ArticleEvent from "@components/Events";
6 | import ArticleHtml from "./Html";
7 | import ArticleAction from "../Posts/Action";
8 | import ArticleType from "./Type";
9 | import ArticleHeader from "./Header"; //文章头
10 | import ArticleComment from "@components/Comment";
11 |
12 | @inject("Appstore")
13 | @ArticleEvent
14 | @observer
15 | export default class Index extends Component {
16 | static propTypes = {
17 | Appstore: PropTypes.object,
18 | match: PropTypes.object
19 | };
20 | constructor(props) {
21 | super(props);
22 | this.getoffsetTop = this.getoffsetTop.bind(this);
23 | }
24 | getoffsetTop(e) {
25 | if (e.target.dataset.click === "read_comment") {
26 | e.stopPropagation();
27 | window.scroll(0, e.target.offsetTop);
28 | }
29 | }
30 | render() {
31 | if (this.props.Appstore.app.article_loading) return null;
32 | const article = this.props.Appstore.posts[0];
33 | return (
34 |
35 |
36 |
42 |
43 |
44 |
55 |
60 |
61 |
62 | );
63 | }
64 | componentDidMount() {
65 | this.props.Appstore.getArticle(this.props.match.params.id);
66 | }
67 | componentWillUnmount() {
68 | this.props.Appstore.setState("app", {
69 | article_loading: true,
70 | posts: [],
71 | read_article: [],
72 | read_comment: []
73 | });
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/pages/People/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import Header from "./Header";
6 | import Links from "./Links";
7 | import Sidebar from "./Sidebar";
8 | import Router from "./Router";
9 | import Page from "@components/Page";
10 | import utils from "@utils";
11 |
12 | @inject("Appstore", "Peoplestore")
13 | @observer
14 | export default class People extends Component {
15 | static propTypes = {
16 | Appstore: PropTypes.object,
17 | Peoplestore: PropTypes.object,
18 | location: PropTypes.object,
19 | match: PropTypes.object,
20 | history: PropTypes.object
21 | };
22 | UNSAFE_componentWillReceiveProps(nextProps) {
23 | const newlocation = nextProps.location;
24 | const location = this.props.location;
25 | if (
26 | location.pathname !== newlocation.pathname ||
27 | location.search !== newlocation.search
28 | ) {
29 | this.props.Peoplestore.setState({ update: true });
30 | }
31 | }
32 |
33 | render() {
34 | const { match, location, history, Appstore, Peoplestore } = this.props;
35 | if (Peoplestore.loading) return
;
36 | return (
37 |
38 |
39 |
40 |
41 |
42 | {Peoplestore.update ? null : }
43 |
44 |
51 |
52 |
56 |
57 | );
58 | }
59 | componentDidMount() {
60 | const { match, Peoplestore } = this.props;
61 | const { path, id } = match.params;
62 | Peoplestore.getPeople(path, id, 1);
63 | }
64 | componentDidUpdate() {
65 | if (this.props.Peoplestore.update) {
66 | const { location, match, Peoplestore } = this.props;
67 |
68 | const { path, id } = match.params;
69 |
70 | const search = utils.search(location.search.split("?")[1]);
71 |
72 | Peoplestore.getPeople(path, id, Number(search.page || 1));
73 | }
74 | }
75 | componentWillUnmount() {
76 | this.props.Peoplestore.setState({ loading: true, update: false });
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/pages/Editor/Write.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { observer, inject } from "mobx-react";
4 | import { toJS } from "mobx";
5 | import E from "wangeditor";
6 | import { app as ajax } from "@request";
7 | import Label from "./Label";
8 |
9 | @inject("Editorstore")
10 | @observer
11 | export default class Write extends Component {
12 | static propTypes = {
13 | Editorstore: PropTypes.object
14 | };
15 | constructor(props) {
16 | super(props);
17 | this.submit = this.submit.bind(this);
18 | this.getHtml = this.getHtml.bind(this);
19 | this.editor = null;
20 | }
21 | remove(e) {
22 | e.stopPropagation();
23 | if (e.target.tagName === "IMG") {
24 | ajax.removeFile({ folder: "editor", url: e.target.src });
25 | }
26 | }
27 |
28 | upload(files, insert) {
29 | if (files.length > 0) {
30 | ajax.uploadFile("editor", files[0]).then(({ url }) => {
31 | insert(url);
32 | });
33 | }
34 | }
35 | getHtml(html) {
36 | this.props.Editorstore.html = html;
37 | }
38 | submit(e) {
39 | e.stopPropagation();
40 | if (this.editor !== null) {
41 | this.props.Editorstore.send(this.editor.txt.html(), this.editor.txt.text());
42 | }
43 | }
44 | render() {
45 | return (
46 |
47 |
48 |
49 |
50 | 创建文章
51 |
52 |
53 | );
54 | }
55 |
56 | componentDidMount() {
57 | this.editor = new E("#write");
58 |
59 | this.editor.customConfig.debug = location.href.indexOf("/file") > 0; //debug
60 | this.editor.customConfig.lang = {
61 | //title
62 | 正文: "p",
63 | 链接文字: "链接文字",
64 | 链接: "链接",
65 | 上传图片: "上传图片",
66 | 创建: "init"
67 | };
68 |
69 | this.editor.customConfig.menus = [
70 | "head", //标题
71 | "bold", //粗体
72 | "italic", //斜体
73 | "quote", // 引用
74 | "code", // 插入代码
75 | "list", // 列表
76 | "link", // 插入链接
77 | "image", // 插入图片
78 | "video", // 插入视频
79 | "undo" // 撤销
80 | ];
81 | this.editor.customConfig.pasteIgnoreImg = true; //只允许上传图片,不允许任何复制过来的图片
82 | this.editor.customConfig.zIndex = 1;
83 | this.editor.customConfig.customUploadImg = this.upload;
84 | this.editor.customConfig.onchangeTimeout = 0;
85 | this.editor.customConfig.onchange = this.getHtml;
86 | this.editor.create();
87 | this.editor.txt.html(toJS(this.props.Editorstore.html));
88 | document
89 | .querySelector("#write")
90 | .addEventListener("DOMNodeRemoved", this.remove, false);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mengya
2 | 一个类知乎文章方面的应用项目,主要由react,mobx,react-router,axios,wangeditor,node.js,mongodb等编写
3 |
4 | ## 实现功能
5 | - [x] 登录注册
6 | - [x] 创建文章
7 | - [x] 关注标签
8 | - [x] 文章过滤
9 | - [x] 文章排序
10 | - [x] 用户设置
11 | - [x] 用户消息
12 | - [x] 用户动态
13 | - [x] 关注用户
14 | - [x] 收藏文章
15 | - [x] 评论文章
16 | - [x] 个人主页
17 | - [x] 后台管理系统等
18 |
19 | ## 注意事项
20 | 本项目是在pc端最新版chrome浏览器开发测试,且使用纯CSS3原生开发没有使用任何UI组件库和预编译器,除了后台管理系统是使用蚂蚁金服的antd UI组件开发,在其他pc端浏览器、移动端浏览器和旧版本浏览器运行可能会出现页面布局、样式怪异行为,所以请使用最新版pc端chrome浏览器运行测试
21 |
22 | ## 安装
23 | 在运行前端项目前请先安装后端项目[node-mengya](https://github.com/manyuewuxin/node-mengya)
24 | ,由于build没有配置过复杂的各种环境打包开发测试,需要后端的mongodb支持,不然打开浏览器运行前端项目没有数据响应会报错
25 |
26 | git clone https://github.com/manyuewuxin/mengya.git
27 | cd mengya
28 | npm install
29 | npm run build
30 | npm run dev
31 | 访问: http://localhost:3000
32 |
33 | ## 关联项目
34 | 后端:[node-mengya](https://github.com/manyuewuxin/node-mengya)
35 |
36 | 后台管理系统:[manage](https://github.com/manyuewuxin/manage)
37 |
38 | ## 项目布局
39 | ```
40 | .
41 | ├── build // webpack配置文件
42 | ├── config // 项目打包路径
43 | ├── dist // 打包目录
44 | ├── screenshots // 项目截图
45 | ├── src // 源码目录
46 | │ ├── assets
47 | │ │ ├── css // css样式文件
48 | │ │ ├── font // font字体文件
49 | │ │ └── img // 公共图片文件
50 | │ ├── components // 公共组件
51 | │ │ ├── Author // 作者模态框组件
52 | │ │ ├── Collect // 收藏夹模态框组件
53 | │ │ ├── Header // 页面头部组件
54 | │ │ ├── Label // 标签模态框
55 | │ │ ├── Modal // 模态框接口
56 | │ │ ├── Events // 统一文章列表整个click和mouseover事件的高阶组件
57 | │ │ ├── Forward // 转发
58 | │ │ └── Page // 分页组件
59 | │ ├── pages
60 | │ │ ├── Article // 单独一篇文章
61 | │ │ ├── Collect // 收藏文章列表展示
62 | │ │ ├── Editor // 编写文章
63 | │ │ ├── People // 用户个人主页
64 | │ │ ├── Posts // 文章列表展示
65 | │ │ ├── Setter // 用户设置个人资料
66 | │ │ ├── Login // 登录注册
67 | │ │ ├── Subscr // 所有标签
68 | │ │ ├── App.js
69 | │ │ └── router.js
70 | │ ├── store // mobx状态管理
71 | │ │ ├── index.js // 调出接口
72 | │ │ ├── Appstore.js
73 | │ │ ├── Editorstore.js
74 | │ │ ├── Peoplestore.js
75 | │ │ └── Setterstore.js
76 | │ ├── request // 统一请求处理
77 | │ ├── template // webpack打包HTML模板文件
78 | │ ├── utils // 常用方法
79 | │ └── index.js // 入口
80 | ├── .babelrc // babel配置
81 | ├── .eslintignore // eslint忽略配置
82 | ├── .eslintrc.json // eslint配置
83 | ├── .gitignore // git忽略配置
84 | └── package.json // npm
85 | .
86 |
87 | ```
88 |
89 | ## 部分截图
90 |
91 | 
92 |
93 | 
94 |
--------------------------------------------------------------------------------
/src/pages/Editor/Label.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer, inject } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import { app as ajax } from "@request";
5 |
6 | @inject("Editorstore")
7 | @observer
8 | export default class Label extends Component {
9 | static propTypes = {
10 | Editorstore: PropTypes.object
11 | };
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | quertList: [],
16 | value: ""
17 | };
18 | this.change = this.change.bind(this);
19 | this.setLabel = this.setLabel.bind(this);
20 | this.timeID = null;
21 | }
22 |
23 | change(e) {
24 | e.stopPropagation();
25 | const value = e.target.value;
26 | if (e.target.value === "") {
27 | this.quertList = [];
28 | this.setState({ quertList: [], value: "" });
29 | } else if (this.props.Editorstore.type.length < 3) {
30 | if (this.timeID !== null) {
31 | window.clearTimeout(this.timeID);
32 | this.timeID = null;
33 | }
34 | this.timeID = window.setTimeout(() => {
35 | ajax.getLabel(`search=${value}`, 1).then(({ label }) => {
36 | if (this.state.value !== "") this.setState({ quertList: label });
37 | window.clearTimeout(this.timeID);
38 | this.timeID = null;
39 | });
40 | }, 0);
41 | }
42 | this.setState({ value: e.target.value });
43 | }
44 | setLabel(e) {
45 | e.stopPropagation();
46 | if (e.target.dataset.removeindex) {
47 | this.props.Editorstore.removeType(Number(e.target.dataset.removeindex));
48 | } else if (e.target.dataset.querylabel) {
49 | if (this.props.Editorstore.type.length < 4) {
50 | this.props.Editorstore.addType(e.target.dataset.querylabel);
51 | this.quertList = [];
52 | this.setState({
53 | quertList: [],
54 | value: ""
55 | });
56 | }
57 | }
58 | }
59 | render() {
60 | const list = this.props.Editorstore.type.map((type, index) => {
61 | return (
62 |
66 |
67 | {type}
68 |
69 |
70 |
71 | );
72 | });
73 | const quertLists = this.state.quertList.map((label) => (
74 |
79 | {label.type}
80 |
81 | ));
82 |
83 | return (
84 |
101 | );
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/pages/People/Content/Follow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link } from "react-router-dom";
3 | import { inject, observer } from "mobx-react";
4 | import PropTypes from "prop-types";
5 |
6 | @inject("Appstore", "Peoplestore")
7 | @observer
8 | export default class Followers extends Component {
9 | static propTypes = {
10 | Appstore: PropTypes.object,
11 | Peoplestore: PropTypes.object,
12 | match: PropTypes.object,
13 | location: PropTypes.object
14 | };
15 | constructor(props) {
16 | super(props);
17 | this.follow = this.follow.bind(this);
18 | this.is_myhome = false;
19 | }
20 |
21 | follow(e) {
22 | e.stopPropagation(); //禁止冒泡
23 | this.props.Peoplestore.follow(Number(e.target.dataset.index));
24 | }
25 |
26 | render() {
27 | this.is_myhome =
28 | this.props.Appstore.id === this.props.match.params.id ? true : false;
29 |
30 | const following = this.props.Appstore.following;
31 |
32 | const { people } = this.props.Peoplestore;
33 |
34 | const list = people.map((f, index) => {
35 | const author = f.author[0];
36 | const is_follow = following.includes(author._id);
37 | return (
38 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {author.name}
52 |
53 |
54 |
{author.information}
55 |
{`${
56 | author.create_p_count
57 | } 篇文章 · ${author.followers_count} 关注者`}
58 |
59 |
67 |
68 | );
69 | });
70 | return (
71 |
72 |
73 |
74 | {`${this.is_myhome ? "我" : "他"}关注的用户`}
81 |
82 |
83 | {`关注${this.is_myhome ? "我" : "他"}的用户`}
90 |
91 |
92 |
93 |
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/store/Setterstore.js:
--------------------------------------------------------------------------------
1 | import { observable, action } from "mobx";
2 | import appstore from "./Appstore";
3 | import { app, setter } from "../request";
4 | class Setterstore {
5 | @observable
6 | form = {
7 | avatar: "",
8 | name: "",
9 | carrer: "",
10 | company: "",
11 | information: "",
12 | userlike: "",
13 | password: "",
14 | newpassword: "",
15 | newpasswords: ""
16 | };
17 |
18 | @action
19 | setState(obj) {
20 | if (Object.prototype.toString.call(obj) === "[object Object]") {
21 | for (var key in obj) {
22 | if (key in this) {
23 | this[key] = obj[key];
24 | }
25 | }
26 | }
27 | }
28 |
29 | @action
30 | init() {
31 | Promise.all([app.getUser(), app.getMessage(1)]).then(
32 | action(([u, m]) => {
33 | for (let key in this.form) {
34 | this.form[key] = u.user[key];
35 | }
36 | appstore.setState("app", { user: u.user });
37 | appstore.setUserMessage(m, 1);
38 | })
39 | );
40 | }
41 |
42 | @action
43 | change(key, value) {
44 | this.form[key] = value;
45 | }
46 |
47 | async uploadImage(file) {
48 | try {
49 | await app.removeFile({ folder: "avatar", url: this.form.avatar });
50 | } catch (err) {
51 | console.log(err);
52 | }
53 | const { url } = await app.uploadFile("avatar", file);
54 | await this.setAvatar(url);
55 | }
56 |
57 | @action
58 | setAvatar(url) {
59 | this.form.avatar = url;
60 | }
61 |
62 | @action
63 | setInform() {
64 | if (/^[a-zA-Z0-9\u4e00-\u9fa5]{2,8}$/.test(this.form.name)) {
65 | Promise.resolve(this.form)
66 | .then(({ password, newpassword, newpasswords, ...name }) => {
67 | const data = {};
68 | const user = appstore.user;
69 | for (let key in name) {
70 | if (name[key] !== user[key]) {
71 | data[key] = name[key];
72 | }
73 | }
74 | return data;
75 | })
76 | .then((data) => {
77 | return Object.keys(data).length > 0 ? setter.setAccount(data) : true;
78 | })
79 | .then(() => {
80 | appstore.setMessage({ text: "修改资料成功", is: true });
81 | })
82 | .catch(({ err }) => {
83 | appstore.setMessage({ text: err, is: false });
84 | });
85 | } else {
86 | appstore.setMessage({ text: "用户名必须为2-8个中英文字符组合", is: false });
87 | }
88 | }
89 |
90 | @action
91 | setPassword() {
92 | Promise.resolve(this.form)
93 | .then(({ password, newpassword, newpasswords }) => {
94 | const data = { password, newpassword, newpasswords };
95 | for (var key in data) {
96 | if (/^[a-zA-Z0-9\W]{6,15}$/.test(data[key]) === false) {
97 | return Promise.reject({ err: "密码错误" });
98 | }
99 | }
100 | if (newpassword !== newpasswords) {
101 | return Promise.reject({ err: "新旧密码不一致" });
102 | }
103 | return setter.setPassword(data);
104 | })
105 | .then(() => {
106 | return appstore.quit();
107 | })
108 | .then(() => {
109 | appstore.setMessage({ text: "修改密码成功,请重新登录", is: true });
110 | })
111 | .catch(({ err }) => {
112 | appstore.setMessage({ text: err, is: false });
113 | });
114 | }
115 | }
116 |
117 | export default new Setterstore();
118 |
--------------------------------------------------------------------------------
/src/pages/Subscr/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import { app as ajax } from "@request";
5 |
6 | import Search from "./Search";
7 | import List from "./List";
8 |
9 | @inject("Appstore")
10 | @observer
11 | export default class Index extends Component {
12 | static propTypes = {
13 | Appstore: PropTypes.object,
14 | history: PropTypes.object,
15 | location: PropTypes.object,
16 | match: PropTypes.object
17 | };
18 | constructor(props) {
19 | super(props);
20 | this.state = {
21 | label: null,
22 | page: 1,
23 | query: "type=all",
24 | updataPath: false
25 | };
26 | this.search = this.search.bind(this);
27 | this.follow_type = this.follow_type.bind(this);
28 | this.scroll = this.scroll.bind(this);
29 | this.time = null;
30 | }
31 | UNSAFE_componentWillReceiveProps(nextProps) {
32 | if (nextProps.location.pathname !== this.props.location.pathname) {
33 | this.setState({ updataPath: true });
34 | }
35 | }
36 | search(value) {
37 | if (value) {
38 | ajax.getLabel(`search=${value}`, 1).then(({ label }) => {
39 | this.setState({ label: label, query: `search=${value}` });
40 | });
41 | }
42 | }
43 |
44 | follow_type(e) {
45 | e.stopPropagation();
46 | const { index } = e.target.dataset;
47 | this.props.Appstore.follow_type(this.state.label[index].type);
48 | }
49 | scroll(e) {
50 | e.stopPropagation();
51 | if (this.time !== null) {
52 | window.clearTimeout(this.time);
53 | this.time = null;
54 | }
55 | this.time = window.setTimeout(() => {
56 | const scrollTop = Math.round(document.documentElement.scrollTop); //获取滚动高度像素
57 | const scrollHeight = scrollTop + window.innerHeight; //滚动高度像素+浏览器窗口屏幕可见高度
58 | if (scrollHeight >= document.documentElement.scrollHeight - 100) {
59 | const { label, page, query } = this.state;
60 | ajax.getLabel(query, page).then((data) => {
61 | const arr = label.concat(data.label);
62 |
63 | if (data.label.length > 0)
64 | this.setState({ label: arr, page: page + 1 });
65 |
66 | window.clearTimeout(this.time);
67 | this.time = null;
68 | });
69 | }
70 | }, 100);
71 | }
72 | render() {
73 | if (this.state.label === null) return
;
74 | return (
75 |
76 |
77 |
82 |
83 | );
84 | }
85 | componentDidMount() {
86 | ajax.getLabel(`type=${this.props.match.params.path}`, 1).then(({ label }) => {
87 | this.setState({
88 | label: label,
89 | page: this.state.page + 1,
90 | query: `type=${this.props.match.params.path}`
91 | });
92 | this.props.Appstore.getUser(1);
93 | });
94 | window.addEventListener("scroll", this.scroll, false);
95 | }
96 | componentDidUpdate() {
97 | if (this.state.updataPath) {
98 | ajax.getLabel(`type=${this.props.match.params.path}`, 1).then(({ label }) => {
99 | this.setState({
100 | label: label,
101 | page: this.state.page + 1,
102 | query: `type=${this.props.match.params.path}`,
103 | updataPath: false
104 | });
105 | });
106 | }
107 | }
108 | componentWillUnmount() {
109 | window.removeEventListener("scroll", this.scroll, false);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/pages/Login/Submit.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class Button extends Component {
5 | static propTypes = {
6 | captcha: PropTypes.number,
7 | login: PropTypes.func,
8 | pathname: PropTypes.string
9 | };
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | value: "",
14 | captcha: 0,
15 | error: null
16 | };
17 | this.send = this.send.bind(this);
18 | this.change = this.change.bind(this);
19 | this.keyup = this.keyup.bind(this);
20 | this.captcha = null;
21 | }
22 |
23 | send(e) {
24 | e.stopPropagation();
25 | if (this.props.captcha > 0) {
26 | if (this.captcha === this.state.value) {
27 | this.props.login();
28 | } else {
29 | this.setState({ captcha: this.state.captcha + 1, error: "验证码错误" });
30 | }
31 | } else {
32 | this.props.login();
33 | }
34 | }
35 | change(e) {
36 | e.stopPropagation();
37 | this.setState({ value: e.target.value });
38 | if (e.target.value === this.captcha) this.setState({ error: null });
39 | }
40 | createCaptcha() {
41 | const canvas = document.querySelector("#canvas"); //获取画布
42 | this.captcha = Math.random()
43 | .toString(36)
44 | .substring(2, 6); //随机生成四个字符串,或者for随机一个
45 | if (canvas) {
46 | var ctx = canvas.getContext("2d");
47 | ctx.clearRect(0, 0, 70, 32); //擦除画布
48 | ctx.fillStyle = "#FFFFFF";
49 | ctx.fillRect(0, 0, 70, 27);
50 | ctx.font = "23px arial";
51 | ctx.textAlign = "left";
52 | // 创建渐变
53 | var gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
54 | gradient.addColorStop("0", "magenta");
55 | gradient.addColorStop("0.5", "blue");
56 | gradient.addColorStop("1.0", "red");
57 | // 用渐变填色
58 | ctx.strokeStyle = gradient;
59 | ctx.strokeText(this.captcha, 10, 23); //画布上添加验证码
60 | }
61 | }
62 | keyup(e) {
63 | e.stopPropagation();
64 | if (e.key === "Enter") this.send(e);
65 | }
66 | render() {
67 | const { value, error } = this.state;
68 | return (
69 |
70 | {this.props.captcha > 0 ? (
71 |
72 |
81 |
91 | {error}
92 |
93 | ) : null}
94 |
101 | {this.props.pathname === "/signin" ? "登录" : "注册"}
102 |
103 |
104 | );
105 | }
106 | componentDidUpdate(prevProps, prevState) {
107 | if (
108 | this.props.captcha !== prevProps.captcha ||
109 | this.state.captcha !== prevState.captcha
110 | ) {
111 | this.createCaptcha();
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/pages/Setter/AccountView.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import { observer } from "mobx-react";
5 |
6 | @observer
7 | export default class AccountView extends Component {
8 | static propTypes = {
9 | changefile: PropTypes.func,
10 | openfile: PropTypes.func,
11 | change: PropTypes.func,
12 | refx: PropTypes.object,
13 | form: PropTypes.object
14 | };
15 | render() {
16 | const { avatar, name, carrer, company, information, userlike } = this.props.form;
17 | return (
18 |
103 | );
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/request/index.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import nprogress from "nprogress";
3 | import appstore from "../store/Appstore";
4 |
5 | const CancelToken=axios.CancelToken; //取消请求,'cancelToken':source.token,工厂返回每次只能一个cance
6 | axios.interceptors.request.use(
7 | function(config) {
8 | nprogress.start();
9 | return config;
10 | },
11 | function(error) {
12 | return Promise.reject(error);
13 | }
14 | );
15 |
16 | axios.interceptors.response.use(
17 | function(response) {
18 | nprogress.done();
19 | return response.data;
20 | },
21 | function(error) {
22 | nprogress.done();
23 | switch (error.response.status) {
24 | case 400:
25 | appstore.setMessage({ text: error.response.data.err, is: false });
26 | break;
27 |
28 | case 403:
29 | if(appstore.id !== null){
30 | appstore.quit();
31 | appstore.login("/signin", { text: "你的登录状态已到期,请重新登录", is: false });
32 | }
33 | else{
34 | appstore.login("/signin", { text: "你还没有登录,请登录后在操作", is: false });
35 | }
36 | break;
37 |
38 | case 404:
39 | appstore.setState("app", { error: `找不到该资源` });
40 | break;
41 |
42 | case 408:
43 | nprogress.done();
44 | appstore.setMessage({ text: "请求超时", is: false });
45 | break;
46 | }
47 | return Promise.reject(error.response.data);
48 | }
49 | );
50 |
51 |
52 | const req = {
53 | get: (url, config = {}) => {
54 | return axios({
55 | url: url,
56 | method: "get",
57 | timeout: 50000,
58 | "X-Requested-With": "XMLHttpRequest",
59 | ...config
60 | });
61 | },
62 | post: (url, body, config = {}) => {
63 | return axios({
64 | url: url,
65 | method: "post",
66 | timeout: 50000,
67 | "X-Requested-With": "XMLHttpRequest",
68 | data: body,
69 | ...config
70 | });
71 | }
72 | };
73 |
74 | const app = {
75 | getPostsList: (type, page) => req.get(`/posts?${type}&page=${page}`),
76 | getArticle: (posts_id) => req.get(`/posts/p/${posts_id}`),
77 | getOrder: () => req.get("/posts/order"),
78 | getUser: () => req.get("/user"),
79 | getLabel: (type,page) => req.get(`/posts/label?${type}&page=${page}`),
80 | getMessage: (skip) => req.get(`/user/message?page=${skip}`),
81 | getAuthor: (user_id, is_home) => req.get(`/user/author?user_id=${user_id}&is_home=${is_home}`),
82 | getCollectList: (skip) => req.get(`/user/collect?page=${skip}`),
83 | getCollect: (collect_id, page) => req.get(`/user/collect/${collect_id}?page=${page}`),
84 | getComment: (posts_id, page, sort) => req.get(`/posts/comment?posts_id=${posts_id}&page=${page}&sort=${sort}`),
85 |
86 | updateCollect: (data) => req.post("/user/collect/update", data),
87 | createCollect: (data) => req.post("/user/collect/create", data),
88 | good: (data) => req.post("/posts/comment/good", data),
89 | createComment: (data) => req.post("/posts/comment/create", data),
90 | removeComment: (data) => req.post("/posts/comment/remove", data),
91 | updateMessage: () => req.post("/user/message"),
92 | login: (path, data) => req.post(`/user${path}`, data),
93 | signout: () => req.post("/user/signout"),
94 | like: (data) => req.post("/posts/like", data),
95 | following: (data) => req.post("/user/following", data),
96 | followtype: (data) => req.post("/user/followtype",data),
97 |
98 | uploadFile: (key,file) => {
99 | if (file.size < 2000000) {
100 | if(/^image\/|^audio\/|^video\//.test(file.type)){
101 | const formdata = new FormData();
102 | formdata.append(key, file);
103 | return req.post("/file/upload", formdata, {
104 | headers: { "Content-Type": "multipart/form-data" }
105 | });
106 | }
107 | else{
108 | appstore.setMessage({text:"你上传的文件不是图片或视频格式", is:false});
109 | return Promise.reject();
110 | }
111 | }
112 | else {
113 | appstore.setMessage({text:"请上传低于2M的图片或视频", is:false});
114 | return Promise.reject();
115 | }
116 | },
117 | removeFile: (data) => req.post("/file/remove",data)
118 | };
119 |
120 | const people = {
121 | getPeople: (pathname, user_id, page) => req.get(`/user/people/${pathname}?user_id=${user_id}&page=${page}`),
122 | removeArticle: (data) => req.post("/posts/remove", data),
123 | removeCollect: (data) => req.post("/user/collect/remove", data)
124 | };
125 |
126 | const setter = {
127 | setAccount: (data) => req.post("/user/account?method=profile", data),
128 | setPassword: (data) => req.post("/user/account?method=password", data)
129 | };
130 |
131 | const editor = {
132 | createArticle: (data) => req.post("/posts/create", data)
133 | };
134 |
135 | export { app, people, setter, editor, CancelToken };
136 |
--------------------------------------------------------------------------------
/src/components/Events/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | export default function Events(Components) {
6 | return observer(
7 | class ArticleEvent extends Component {
8 | static propTypes = {
9 | history: PropTypes.object,
10 | location: PropTypes.object,
11 | Appstore: PropTypes.object
12 | };
13 | constructor(props) {
14 | super(props);
15 | this.click = this.click.bind(this);
16 | this.mouseover = this.mouseover.bind(this);
17 | }
18 | mouseover(e) {
19 | e.stopPropagation();
20 | if (e.target.dataset.hove) {
21 | const { offsetLeft, offsetTop } = e.target;
22 | const { hove, type, index } = e.target.dataset;
23 | const author_id = index
24 | ? this.props.Appstore.posts[Number(index)].author_id
25 | : null;
26 | this.props.Appstore.setState("hove", {
27 | article_type: type,
28 | hove_type: hove,
29 | author_id: author_id,
30 | opacity: 1,
31 | position: { left: offsetLeft + 15, top: offsetTop + 24 }
32 | });
33 | } else {
34 | this.props.Appstore.setState("hove", { opacity: 0 });
35 | }
36 | }
37 |
38 | click(e) {
39 | e.stopPropagation();
40 | if (e.target.dataset.click) {
41 | switch (e.target.dataset.click) {
42 | case "read_article":
43 | this.read(e);
44 | break;
45 |
46 | case "like":
47 | this.like(e);
48 | break;
49 |
50 | case "read_comment":
51 | this.read(e);
52 | break;
53 |
54 | case "collect":
55 | this.collect(e);
56 | break;
57 |
58 | case "forward":
59 | this.forward(e);
60 | break;
61 | }
62 | } else {
63 | this.default();
64 | }
65 | }
66 |
67 | like(e) {
68 | const index = Number(e.target.dataset.index);
69 | const { _id, author_id } = this.props.Appstore.posts[index];
70 | this.props.Appstore.like(index, _id, author_id);
71 | }
72 |
73 | read(e) {
74 | const index = Number(e.target.dataset.index);
75 | const { click } = e.target.dataset;
76 | if (click === "read_comment") {
77 | this.props.Appstore.read_article_comment("comment", index);
78 | } else if (click === "read_article") {
79 | const { offsetTop } = e.target;
80 | this.props.Appstore.read_article_comment("article", index, offsetTop);
81 | } else {
82 | this.props.Appstore.setMessage({ text: "read参数错误", is: false });
83 | }
84 | }
85 |
86 | collect(e) {
87 | const { index } = e.target.dataset;
88 | const { _id, author_id } = this.props.Appstore.posts[Number(index)];
89 | this.props.Appstore.setState("modal", {
90 | open: "collect",
91 | posts_id: _id,
92 | author_id: author_id
93 | });
94 | }
95 |
96 | forward(e) {
97 | const { index } = e.target.dataset;
98 | const { offsetLeft, offsetTop } = e.target;
99 | const { _id } = this.props.Appstore.posts[Number(index)];
100 | if (this.props.Appstore.modal.open === "forward") {
101 | this.props.Appstore.setState("modal", {
102 | open: null,
103 | position: null
104 | });
105 | } else {
106 | this.props.Appstore.setState("modal", {
107 | open: "forward",
108 | position: { left: offsetLeft - 60 + 30, top: offsetTop + 30 },
109 | posts_id: _id
110 | });
111 | }
112 | }
113 | default() {
114 | if (this.props.Appstore.modal.open === "forward") {
115 | this.props.Appstore.setState("modal", {
116 | open: null,
117 | position: null
118 | });
119 | }
120 | }
121 | render() {
122 | return (
123 |
124 |
125 |
126 | );
127 | }
128 | }
129 | );
130 | }
131 |
--------------------------------------------------------------------------------
/src/pages/Posts/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer, inject } from "mobx-react";
3 | import PropTypes from "prop-types";
4 |
5 | import List from "./List";
6 | import Sidebar from "./Sidebar";
7 | import Events from "@components/Events";
8 | import utils from "@utils";
9 |
10 | @inject("Appstore")
11 | @Events
12 | @observer
13 | export default class Content extends Component {
14 | static propTypes = {
15 | Appstore: PropTypes.object,
16 | history: PropTypes.object,
17 | location: PropTypes.object
18 | };
19 | constructor(props) {
20 | super(props);
21 | this.time = null;
22 | this.scroll = this.scroll.bind(this);
23 | }
24 | UNSAFE_componentWillReceiveProps(nextProps) {
25 | if (
26 | nextProps.location.pathname !== this.props.location.pathname ||
27 | nextProps.location.search !== this.props.location.search
28 | ) {
29 | this.props.Appstore.setState("app", { update: true });
30 | }
31 | }
32 | scroll(e) {
33 | e.stopPropagation();
34 | if (this.time !== null) {
35 | window.clearTimeout(this.time);
36 | this.time = null;
37 | }
38 | this.time = window.setTimeout(() => {
39 | const scrollTop = Math.round(document.documentElement.scrollTop); //获取滚动高度像素
40 | const scrollHeight = scrollTop + window.innerHeight; //滚动高度像素+浏览器窗口屏幕可见高度
41 | if (scrollHeight >= document.documentElement.scrollHeight - 50) {
42 | //等于或大于整个文档高度时也就是滚动到底部时执行请求
43 |
44 | const { path, page } = this.props.Appstore.app;
45 | this.props.Appstore.getPosts(path, page);
46 | window.clearTimeout(this.time);
47 | this.time = null;
48 | }
49 | }, 100);
50 | }
51 |
52 | req({ location }) {
53 | if (location.pathname === "/") {
54 | const search = utils.search(location.search.split("?")[1]);
55 | const type = search.type || "all";
56 | this.props.Appstore.getPosts(`type=${type}`, 1);
57 | } else if (location.pathname === "/hot") {
58 | this.props.Appstore.setState("app", { read_article: [], read_comment: [] });
59 | this.props.Appstore.getPosts(`type=hot`, 1);
60 | } else if (location.pathname === "/search") {
61 | const search = utils.search(location.search.split("?")[1]);
62 | this.props.Appstore.setState("app", { read_article: [], read_comment: [] });
63 | this.props.Appstore.getPosts(`search=${search.query}`, 1);
64 | } else if (location.pathname === "/follow") {
65 | this.props.Appstore.getPosts("type=follow", 1);
66 | }
67 | }
68 |
69 | render() {
70 | if (this.props.Appstore.app.posts_loading) return null;
71 | return (
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | );
81 | }
82 |
83 | componentDidMount() {
84 | this.req(this.props);
85 | window.addEventListener("scroll", this.scroll, false);
86 | }
87 |
88 | componentDidUpdate() {
89 | if (this.props.Appstore.app.update) this.req(this.props);
90 | }
91 | componentWillUnmount() {
92 | this.props.Appstore.setState("app", {
93 | posts_loading: true,
94 | update: false,
95 | posts: [],
96 | read_article: [],
97 | read_comment: [],
98 | page: 1
99 | });
100 | window.removeEventListener("scroll", this.scroll, false);
101 | }
102 | }
103 |
104 | /*
105 | UNSAFE_componentWillReceiveProps(nextProps) {
106 | if (nextProps.location.search !== this.props.location.search || nextProps.location.pathname !== this.props.location.pathname) {
107 | this.props.Appstore.setState("app", { update: true });
108 | }
109 | }
110 |
111 | shouldComponentUpdate(nextProps) {
112 | return this.props.Appstore.app.update;
113 | }
114 |
115 | render() {
116 | const { page, pagepath, posts_loading } = this.props.Appstore.app;
117 | const path = `${this.props.location.pathname}?${pagepath}&`;
118 | if (posts_loading) return null;
119 | return (
120 |
127 | );
128 | }
129 |
130 | componentDidMount() {
131 | if(this.props.location.pathname==="/"){
132 | this.props.Appstore.getPosts("type=all", 1);
133 | }
134 | else if(this.props.location.pathname==="/hot"){
135 | this.props.Appstore.getPosts("hot=p",1);
136 | }
137 | }
138 |
139 | componentDidUpdate() {
140 |
141 | if(this.props.Appstore.app.update){
142 | const { location, history, Appstore } = this.props;
143 |
144 | const search = utils.search(location.search.split("?")[1]);
145 |
146 | if (search.type && search.page || search.query && search.page) {
147 | const keys = Object.keys(search)[0];
148 | Appstore.getPosts(`${keys}=${search[keys]}`, Number(search.page));
149 |
150 | }
151 | else if(location.pathname === "/hot"){
152 | Appstore.getPosts("hot=p", Number(search.page || 1));
153 | }
154 | else if(location.pathname === "/" && location.search === "") {
155 | Appstore.getPosts("type=all", 1);
156 | }
157 | else {
158 | history.replace("/404");
159 | }
160 | }
161 | }
162 | componentWillUnmount() {
163 | this.props.Appstore.setState("app", { posts_loading: true, update: false }); //卸载后初始化掉共享状态
164 | }
165 | */
166 |
--------------------------------------------------------------------------------
/src/store/Peoplestore.js:
--------------------------------------------------------------------------------
1 | import { observable, action } from "mobx";
2 | import { app, people } from "../request";
3 | import appstore from "./Appstore";
4 |
5 | class Peoplestore {
6 | @observable people = [];
7 | @observable author = null;
8 | @observable count = 0;
9 | @observable loading = true;
10 | @observable update = false;
11 | @observable currentPage = 1;
12 | @observable visibility = false;
13 | @observable description = null;
14 |
15 | index = null;
16 | page = null;
17 | timeID = 0;
18 | people_user_id = {};
19 |
20 | @action
21 | setState(obj) {
22 | if (Object.prototype.toString.call(obj) === "[object Object]") {
23 | for (var key in obj) {
24 | if (key in this) {
25 | this[key] = obj[key];
26 | }
27 | }
28 | }
29 | }
30 |
31 | @action
32 | getPeople(path, user_id, page) {
33 | if(typeof path == "string" && typeof user_id == "string" && typeof page == "number"){
34 | Promise.all([
35 | people.getPeople(path, user_id, page),
36 | app.getAuthor(user_id, true),
37 | app.getUser(),
38 | app.getMessage(1)
39 | ]).then(([p, a, u, m]) => {
40 | if (path === "article") {
41 | appstore.setState("app", { posts: p.people });
42 | this.setState({
43 | count: p.count,
44 | author: a.author,
45 | currentPage: page,
46 | loading: false,
47 | update: false
48 | });
49 | } else {
50 | this.setState({
51 | people: p.people,
52 | count: p.count,
53 | author: a.author,
54 | currentPage: page,
55 | loading: false,
56 | update: false
57 | });
58 | }
59 | appstore.setState("app", { user: u.user });
60 | appstore.setUserMessage(m, 1);
61 | this.people_user_id = user_id;
62 | });
63 | }
64 | else {
65 | appstore.setMessage({ text: "getPeople params error", is: false });
66 | }
67 | }
68 |
69 | @action
70 | remove_article(index) {
71 | if (Number.isInteger(index) && Number.isNaN(index) === false) {
72 | this.index = index;
73 | this.description = { title: "你确定要删除该文章吗?" };
74 | this.visibility = true;
75 | } else {
76 | appstore.setMessage({ text: "remove_article params error", is: false });
77 | }
78 | }
79 |
80 | @action.bound
81 | ok_removeArticle() {
82 | if(Number.isInteger(this.index)){
83 | const { image, _id } = appstore.posts[this.index];
84 | Promise.resolve().then(() => {
85 | return image !== null
86 | ? app.removeFile({ url: image, folder: "editor" })
87 | : true;
88 | })
89 | .then(() => {
90 | return people.removeArticle({ posts_id: _id });
91 | })
92 | .then(() => {
93 | if(appstore.posts.length-1 > 0){
94 | appstore.removeArticle(this.index);
95 | }
96 | else{
97 | const page = this.currentPage - 1 > 0 ? this.currentPage - 1 : 1;
98 | this.getPeople("article", this.people_user_id, page);
99 | }
100 | this.setState({
101 | visibility: false,
102 | description: null,
103 | index: null
104 | });
105 | });
106 | }
107 | else{
108 | appstore.setMessage({ text: "removeArticle index is error ", is: false });
109 | }
110 | }
111 |
112 | @action
113 | removeCollect(index){
114 | if(typeof index === "number" && Number.isNaN(index) === false){
115 | this.people.splice(index,1);
116 | }
117 | else{
118 | appstore.setMessage({ text: "removeCollect params error", is: false });
119 | }
120 | }
121 |
122 | @action.bound
123 | ok_removeCollect() {
124 | if(Number.isInteger(this.index)){
125 | const { _id, image } = this.people[this.index];
126 |
127 | app.removeFile({ url: image, folder: "collect" })
128 | .then(() => {
129 | return people.removeCollect({ collect_id: _id });
130 | })
131 | .then(()=>{
132 | if(this.people.length-1 > 0){
133 | this.removeCollect(this.index);
134 | }
135 | else{
136 | const page = this.currentPage - 1 > 0 ? this.currentPage - 1 : 1;
137 | this.getPeople("collect", this.people_user_id, page);
138 | }
139 | this.setState({
140 | visibility: false,
141 | description: null,
142 | index: null
143 | });
144 | });
145 | }
146 | else{
147 | appstore.setMessage({ text: "removeCollect index is error", is: false });
148 | }
149 | }
150 |
151 | @action.bound
152 | no_remove() {
153 | this.index = null;
154 | this.visibility = false;
155 | this.description = null;
156 | }
157 |
158 | @action
159 | remove_collect(index) {
160 | if (Number.isInteger(index) && Number.isNaN(index) === false) {
161 | this.index = index;
162 | this.description = { title: "你确定要删除该收藏夹吗?" };
163 | this.visibility = true;
164 | } else {
165 | appstore.setMessage({ text: "remove_collect params error", is: false });
166 | }
167 | }
168 |
169 | @action
170 | follow(index) {
171 | if (Number.isInteger(index) && Number.isNaN(index) === false) {
172 | const user_id = this.people[index].author[0]._id;
173 | appstore.follow(user_id);
174 | } else {
175 | appstore.setMessage({ text: "follow params error", is: false });
176 | }
177 | }
178 | }
179 |
180 | export default new Peoplestore();
181 |
--------------------------------------------------------------------------------
/src/store/Editorstore.js:
--------------------------------------------------------------------------------
1 | import { observable, action, toJS } from "mobx";
2 | import { app, editor } from "../request";
3 | import appstore from "./Appstore";
4 | import xss from "xss";
5 |
6 | class Editorstore {
7 | @observable image = null;
8 | @observable type = [];
9 | @observable title = "";
10 | @observable message = null;
11 | @observable loading = true;
12 |
13 | html = null;
14 | is_update = false;
15 | timeID = null;
16 | article = null;
17 |
18 | @action
19 | setState(obj) {
20 | if (Object.prototype.toString.call(obj) === "[object Object]") {
21 | for (var key in obj) {
22 | if (key in this) {
23 | this[key] = obj[key];
24 | }
25 | }
26 | }
27 | }
28 |
29 | async getUMA(posts_id) {
30 | var p = null;
31 | const u = await app.getUser();
32 | const m = await app.getMessage(1);
33 | if (posts_id !== null) p = await app.getArticle(posts_id);
34 | return { p, u, m };
35 | }
36 |
37 | @action
38 | getArticle(posts_id) {
39 | this.getUMA(posts_id).then(
40 | action(({ p, u, m }) => {
41 | if (p !== null) {
42 | this.article = p.posts[0];
43 | this.html = this.article.html;
44 | this.image = this.article.image;
45 | this.type = this.article.type;
46 | this.title = this.article.title;
47 |
48 | this.is_update = true;
49 | this.loading = false;
50 | } else {
51 | this.loading = false;
52 | }
53 | appstore.setState("app", { user: u.user });
54 | appstore.setUserMessage(m, 1);
55 | })
56 | );
57 | }
58 |
59 | @action
60 | removeTitleImage() {
61 | app.removeFile({ url: this.image, folder: "editor" }).then(
62 | action(() => {
63 | this.image = null;
64 | })
65 | );
66 | }
67 |
68 | @action
69 | uploadTitleImage(file) {
70 | Promise.resolve(1)
71 | .then(() => {
72 | const url = toJS(this.image);
73 | return this.image !== null
74 | ? app.removeFile({ url: url, folder: "editor" })
75 | : true;
76 | })
77 | .then(() => {
78 | return app.uploadFile("editor", file);
79 | })
80 | .then(
81 | action(({ url }) => {
82 | this.image = url;
83 | })
84 | );
85 | }
86 |
87 | test(key, value) {
88 | switch (key) {
89 | case "title":
90 | return /^.{1,30}$/.test(value);
91 |
92 | case "text":
93 | return /^.{50,}$/.test(value);
94 |
95 | case "type":
96 | return value.length > 0;
97 |
98 | case "html":
99 | return true;
100 |
101 | case "image":
102 | return true;
103 | }
104 | }
105 |
106 | verify(html, text) {
107 | const form = {
108 | title: xss(toJS(this.title).replace(/\s+/g, "")),
109 | text: xss(text.replace(/\s+/g, "")),
110 | type: toJS(this.type),
111 | html: xss(html),
112 | image: toJS(this.image)
113 | };
114 |
115 | var message = {
116 | text: "文章字节不低于100以上,不包括空格",
117 | type: "请填写至少一个文章类型",
118 | title: "标题限制在1-30个字节,不包括空格"
119 | };
120 |
121 | var p = {};
122 |
123 | for (let key in form) {
124 | if (this.test(key, form[key]) === false) {
125 | return Promise.reject({ err: message[key] });
126 | }
127 | p[key] = form[key];
128 | }
129 | if (this.is_update) {
130 | const { author, ...article } = this.article;
131 | p = Object.assign({}, article, p);
132 | }
133 | p.text = p.text.length > 100 ? p.text.slice(0, 99) : p.text;
134 | return Promise.resolve(p);
135 | }
136 |
137 | @action
138 | send(html, text) {
139 | this.verify(html, text)
140 | .then((p) => {
141 | this.setMessage({ text: "发布文章中", is: false });
142 | return editor.createArticle(p);
143 | })
144 | .then(() => {
145 | this.timeID = window.setTimeout(() => {
146 | this.setMessage({
147 | text: this.is_update ? "更新文章成功" : "发布文章成功",
148 | is: true
149 | });
150 | window.clearTimeout(this.timeID);
151 | }, 1000);
152 | })
153 | .catch(({ err }) => {
154 | this.setMessage(null);
155 | appstore.setMessage({ text: err || "发布文章出错", is: false });
156 | });
157 | }
158 |
159 | @action
160 | setMessage(message) {
161 | this.message = message;
162 | }
163 |
164 | @action
165 | addType(type) {
166 | if (type) this.type.push(type);
167 | }
168 |
169 | @action
170 | removeType(index) {
171 | if (typeof index == "number") this.type.splice(index, 1);
172 | }
173 |
174 | @action
175 | changeTitle(value) {
176 | this.title = value;
177 | }
178 |
179 | @action
180 | empty() {
181 | this.image = null;
182 | this.type = [];
183 | this.title = "";
184 | this.html = null;
185 | this.is_update = false;
186 | this.article = null;
187 | this.loading = true;
188 | }
189 |
190 | @action
191 | update_init() {
192 | this.loading = false;
193 | }
194 |
195 | @action
196 | init() {
197 | this.loading = true;
198 | }
199 | /*
200 | @action changeHtml(html){//wangeditor这个简陋富文本编辑器无法因为状态重组html
201 | this.html = html;
202 | }*/
203 | }
204 |
205 | export default new Editorstore();
206 | /*
207 | storage(key,value){
208 | if(window.sessionStorage){
209 | window.sessionStorage.setItem(key, JSON.stringify(toJS(value)));
210 | }
211 | }
212 |
213 | @action removeItem(key,value){
214 | if(window.sessionStorage.getItem(key)){
215 | window.sessionStorage.removeItem(key);
216 | this[key] = value;
217 | }
218 | }*/
219 |
--------------------------------------------------------------------------------
/src/pages/Login/Controller.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Redirect } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 | import { observer, inject } from "mobx-react";
5 | import { app as ajax } from "@request";
6 |
7 | import Login from "./Login";
8 | import Register from "./Register";
9 | import Submit from "./Submit";
10 |
11 | @inject("Appstore")
12 | @observer
13 | export default class Index extends Component {
14 | static propTypes = {
15 | pathname: PropTypes.string,
16 | type: PropTypes.string,
17 | Appstore: PropTypes.object,
18 | history: PropTypes.object
19 | };
20 | constructor(props) {
21 | super(props);
22 | this.state = {
23 | name: "",
24 | email: "",
25 | password: "",
26 | passwords: "",
27 | error: null,
28 | captcha: 0 //验证码
29 | };
30 |
31 | this.login = this.login.bind(this); //提交事件
32 | this.switchLogin = this.switchLogin.bind(this);
33 | this.close = this.close.bind(this);
34 | this.change = this.change.bind(this);
35 | this.keyup = this.keyup.bind(this);
36 |
37 | //this.change=this.change.bind(this);
38 | //this.blur = this.blur.bind(this); //失去焦点
39 | //this.focus = this.focus.bind(this); //获得焦点
40 |
41 | this.messages = {
42 | name: "用户名必须为2-8个非符号字符",
43 | email: "邮箱格式错误",
44 | password: "密码必须为6个以上字母数字符号",
45 | passwords: "前后两次密码不一致"
46 | };
47 | }
48 |
49 | shouldComponentUpdate(nextProps, nextState) {
50 | return nextProps.pathname !== this.props.pathname || nextState !== this.state; //route 登录和注册共享一份组件强制更新
51 | }
52 |
53 | test(key, value) {
54 | switch (key) {
55 | case "name":
56 | return /^[a-zA-Z0-9\u4e00-\u9fa5]{2,8}$/.test(value);
57 |
58 | case "email":
59 | return /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(
60 | value
61 | );
62 |
63 | case "password":
64 | return /^[a-zA-Z0-9]{6,15}$/.test(value);
65 |
66 | case "passwords":
67 | return this.state.password === value && /^[a-zA-Z0-9]{6,15}$/.test(value);
68 | }
69 | }
70 |
71 | change(e) {
72 | e.stopPropagation();
73 | const name = e.target.name;
74 | this.setState({ [name]: e.target.value, error: null });
75 | }
76 |
77 | keyup(e) {
78 | e.stopPropagation();
79 | if (e.key === "Enter") this.login();
80 | }
81 |
82 | verify({ error, captcha, ...filds }) {
83 | const { pathname } = this.props;
84 | const data = {};
85 |
86 | if (pathname === "/signup") {
87 | for (let key in filds) {
88 | if (this.test(key, filds[key]) === false) {
89 | return Promise.reject({ err: this.messages[key] });
90 | } else if (key !== "passwords") {
91 | data[key] = filds[key];
92 | }
93 | }
94 | } else if (pathname === "/signin") {
95 | data.key = this.test("name", filds.name) ? "name" : "email"; //不验证对不对
96 | data.value = filds.name;
97 | data.password = filds.password;
98 | }
99 |
100 | return Promise.resolve(data);
101 | }
102 |
103 | login() {
104 | this.verify(this.state)
105 | .then((data) => {
106 | return ajax.login(this.props.pathname, data);
107 | })
108 | .then(() => {
109 | if (this.props.type === "click") {
110 | this.props.Appstore.login(null);
111 | this.props.Appstore.getUser(1);
112 | } else {
113 | this.props.history.push("/");
114 | }
115 | })
116 | .catch(({ err }) => {
117 | console.log(err);
118 | this.setState({
119 | error: err || "请求错误",
120 | captcha: this.state.captcha + 1
121 | });
122 | });
123 | }
124 |
125 | switchLogin(e) {
126 | e.stopPropagation();
127 |
128 | if (this.props.type === "click") {
129 | this.props.Appstore.login(e.target.dataset.path);
130 | } else if (this.props.type === "path") {
131 | this.props.history.push(e.target.dataset.path);
132 | }
133 | }
134 |
135 | close(e) {
136 | e.stopPropagation();
137 | if (this.props.type === "click") {
138 | this.props.Appstore.login(null);
139 | } else if (this.props.type === "path") {
140 | this.props.history.replace("/");
141 | }
142 | }
143 | render() {
144 | if (this.props.Appstore.id !== null) {
145 | return ;
146 | }
147 | const { pathname, type } = this.props;
148 |
149 | return (
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
161 | 登录
162 |
163 | ·
164 |
168 | 注册
169 |
170 |
171 |
{this.state.error}
172 | {pathname === "/signin" ? (
173 |
178 | ) : (
179 |
184 | )}
185 |
191 |
192 |
193 |
194 | );
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/src/components/Collect/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { inject, observer } from "mobx-react";
3 | import PropTypes from "prop-types";
4 | import { app as ajax } from "@request";
5 |
6 | import CollectTitle from "./Title";
7 | import CollectList from "./List";
8 | import CollectSwitch from "./Switch";
9 | import CollectCreate from "./Create";
10 |
11 | @inject("Appstore")
12 | @observer
13 | export default class Index extends Component {
14 | static propTypes = {
15 | Appstore: PropTypes.object,
16 | posts_id: PropTypes.string.isRequired,
17 | author_id: PropTypes.string.isRequired
18 | };
19 | constructor(props) {
20 | super(props);
21 |
22 | this.state = {
23 | collect: [], //数据
24 | is_create: false,
25 | page: 1, //滚动加载size
26 | name: "",
27 | image: "/collect/dafault.png",
28 | loading: true
29 | };
30 |
31 | this.change = this.change.bind(this);
32 | this.changeFile = this.changeFile.bind(this);
33 | this.openFile = this.openFile.bind(this);
34 | this.collectArticle = this.collectArticle.bind(this);
35 | this.switchs = this.switchs.bind(this);
36 |
37 | this.continues = this.continues.bind(this);
38 | this.animationiteration = this.animationiteration.bind(this);
39 | this.animationend = this.animationend.bind(this);
40 |
41 | this.scroll = this.scroll.bind(this);
42 |
43 | this.time = null;
44 | this.refc = React.createRef(); //控制过渡动画
45 | this.refx = React.createRef(); //获取elem
46 | }
47 | change(e) {
48 | e.stopPropagation();
49 | this.setState({ name: e.target.value });
50 | }
51 |
52 | openFile(e) {
53 | //模拟文件表单点击
54 | e.stopPropagation();
55 | this.refx.current.click();
56 | }
57 |
58 | changeFile(e) {
59 | e.stopPropagation();
60 | if (e.target.files.length > 0) {
61 | this.uploadImg(e.target.files[0]).then((url) => {
62 | this.setState({ image: url });
63 | });
64 | }
65 | }
66 | async uploadImg(file) {
67 | const { image } = this.state;
68 | const { url } = await ajax.uploadFile("collect", file);
69 | try {
70 | await ajax.removeFile({ url: image, folder: "collect" });
71 | } catch (err) {
72 | console.log(err);
73 | }
74 | return url;
75 | }
76 | switchs(e) {
77 | e.stopPropagation();
78 | if (this.state.is_create === false) {
79 | this.setState({ is_create: true, animation: false });
80 | } else {
81 | this.create();
82 | }
83 | }
84 |
85 | collectArticle(e) {
86 | e.stopPropagation();
87 | if (e.target.dataset.index) {
88 | const { posts_id, author_id } = this.props;
89 | const { collect } = this.state;
90 | const index = Number(e.target.dataset.index);
91 | const collect_id = collect[index]._id;
92 | const indexOf = collect[index].collect_article.indexOf(posts_id);
93 | const action = indexOf !== -1 ? "remove" : "add"; //获取收藏夹是否收藏了文章
94 |
95 | ajax.updateCollect({ collect_id, action, posts_id, author_id }).then(() => {
96 | if (indexOf !== -1) {
97 | collect[index].collect_article.splice(indexOf, 1);
98 | collect[index].collect_count = collect[index].collect_count - 1;
99 | } else {
100 | collect[index].collect_article.push(posts_id);
101 | collect[index].collect_count = collect[index].collect_count + 1;
102 | }
103 | this.setState({ collect: collect });
104 | });
105 | }
106 | }
107 | create() {
108 | if (/^[a-zA-Z0-9\u4e00-\u9fa5]{1,20}$/.test(this.state.name)) {
109 | const { collect, image, name } = this.state;
110 | ajax.createCollect({ image, name }).then((data) => {
111 | collect.unshift(data.collect);
112 | this.setState({
113 | collect: collect,
114 | is_create: false,
115 | image: "/collect/dafault.png"
116 | });
117 | });
118 | } else {
119 | this.props.Appstore.setMessage({
120 | text: "收藏夹名字必须为1-8个字符",
121 | is: false
122 | });
123 | }
124 | }
125 |
126 | animationiteration(e) {
127 | e.stopPropagation();
128 | this.refc.current.style.animationPlayState = "paused"; //停止
129 | }
130 | animationend(e) {
131 | e.stopPropagation();
132 | this.props.Appstore.setState("modal", { open: null });
133 | }
134 | continues(e) {
135 | e.stopPropagation();
136 | ajax.removeFile({ url: this.state.image, folder: "collect" }).finally(() => {
137 | this.refc.current.parentElement.style.opacity = 0;
138 | this.refc.current.style.animationPlayState = "running";
139 | });
140 | }
141 |
142 | scroll(e) {
143 | e.stopPropagation();
144 | if (this.time !== null) {
145 | window.clearTimeout(this.time);
146 | this.time = null;
147 | }
148 | this.time = window.setTimeout(() => {
149 | const scrollTop = Math.round(document.documentElement.scrollTop);
150 | const scrollHeight = scrollTop + window.innerHeight;
151 | if (scrollHeight >= document.documentElement.scrollHeight - 50) {
152 |
153 | const { collect, page } = this.state;
154 |
155 | ajax.getCollectList(page).then((data) => {
156 | const arr = collect.concat(data.collect);
157 |
158 | if (data.collect.length > 0)
159 | this.setState({ collect: arr, page: page + 1 });
160 |
161 | window.clearTimeout(this.time);
162 | this.time = null;
163 | });
164 | }
165 | }, 100);
166 | }
167 |
168 | render() {
169 | if (this.state.loading) return null;
170 | const { is_create, image, name, collect } = this.state;
171 | return (
172 |
173 |
174 |
179 |
180 | {is_create ? (
181 |
189 | ) : (
190 |
196 | )}
197 |
198 |
199 |
200 | );
201 | }
202 | componentDidMount() {
203 | ajax.getCollectList(1)
204 | .then(({ collect }) => {
205 | this.setState({
206 | collect: collect,
207 | page: this.state.page + 1,
208 | loading: false
209 | });
210 | })
211 | .catch(() => {
212 | this.props.Appstore.setState("modal", { open: null });
213 | });
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/components/Page/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { withRouter } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 |
5 | @withRouter
6 | export default class Page extends Component {
7 | static propTypes = {
8 | ulStyle: PropTypes.string.isRequired,
9 | currentStyle: PropTypes.string.isRequired,
10 | setPage: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
11 | count: PropTypes.number.isRequired,
12 | currentPage: PropTypes.number.isRequired,
13 | history: PropTypes.object
14 | };
15 | constructor(props) {
16 | super(props);
17 | this.state = {
18 | page: 1
19 | };
20 | this.replace = this.replace.bind(this); //跳转任意页
21 | this.go = this.go.bind(this); //下一页
22 | this.back = this.back.bind(this); //上一页
23 |
24 | this.arr = [];
25 | this.list = [];
26 | this.init(1, props.count);
27 | this.build(1, props.count);
28 | }
29 | UNSAFE_componentWillReceiveProps(nextProps) {
30 | if (nextProps.setPage !== this.props.setPage) {
31 | this.setState({ page: 1 });
32 | }
33 | }
34 | shouldComponentUpdate(nextProps, nextState) {
35 | return (
36 | this.props.count !== nextProps.count || this.state.page !== nextState.count
37 | );
38 | }
39 | UNSAFE_componentWillUpdate(nextProps, nextState) {
40 | if (nextProps.count !== this.props.count) {
41 | this.arr = [];
42 | this.init(nextProps.currentPage, nextProps.count);
43 | this.build(nextProps.currentPage, nextProps.count); //在render抽离出来
44 | }
45 | else{
46 | this.build(nextState.page, nextProps.count); //在render抽离出来
47 | }
48 | }
49 |
50 | setPage(page) {
51 | //调用传递参数
52 | if (typeof this.props.setPage === "function") {
53 | this.props.setPage(page);
54 | } else if (typeof this.props.setPage === "string") {
55 | window.scroll(0, 0);
56 | this.props.history.push(`${this.props.setPage}page=${page}`);
57 | }
58 | }
59 |
60 | init(start, count) {
61 | //当前页,父组件传递最大页数
62 | const length = start + 5;
63 |
64 | for (var index = start, i = 0; index < length; i++, index++) {
65 | if (index <= count) {
66 | this.arr[i] = index;
67 | } else {
68 | break;
69 | }
70 | }
71 | }
72 |
73 | setList(index, page) {
74 | //点击的索引,点击的页数
75 | const { count } = this.props;
76 | if (page < 5) {
77 | this.init(1, count);
78 | } else if (index === this.arr.length - 1) {
79 | for (let n = 1; n < 3; n++) {
80 | if (page + n < count + 1) {
81 | this.arr.splice(0, 1);
82 | this.arr.push(page + n);
83 | } else {
84 | break;
85 | }
86 | }
87 | } else if (index === 0) {
88 | for (let n = 1; n < 3; n++) {
89 | if (page - n > 0) {
90 | this.arr.splice(this.arr.length - 1, 1);
91 | this.arr.unshift(page - n);
92 | } else {
93 | break;
94 | }
95 | }
96 | } else if (page > count - 5) {
97 | this.init(count - 4, count);
98 | }
99 | }
100 |
101 | replace(e) {
102 | e.stopPropagation();
103 | const page = Number(e.target.dataset.page);
104 | const index = Number(e.target.dataset.index);
105 | if (page !== this.state.page) {
106 | this.setList(index, page);
107 | this.setPage(page);
108 | this.setState({ page: page });
109 | }
110 | }
111 |
112 | go(e) {
113 | //下一页
114 | e.stopPropagation();
115 | if (this.state.page + 1 <= this.props.count) {
116 | this.setList(this.arr.indexOf(this.state.page + 1), this.state.page + 1);
117 | this.setPage(this.state.page + 1);
118 | this.setState({ page: this.state.page + 1 });
119 | }
120 | }
121 |
122 | back(e) {
123 | //上一页
124 | e.stopPropagation();
125 | if (this.state.page - 1 > 0) {
126 | this.setList(this.arr.indexOf(this.state.page - 1), this.state.page - 1);
127 | this.setPage(this.state.page - 1);
128 | this.setState({ page: this.state.page - 1 });
129 | }
130 | }
131 |
132 | build(page, count) {
133 | //开始构建
134 | const style = this.props.currentStyle || "";
135 | this.list = this.arr.map((listpage, index) => {
136 | //列表
137 | const key = Math.random()
138 | .toString(36)
139 | .substring(2, 6);
140 | return (
141 |
147 | {listpage}
148 |
149 | );
150 | });
151 |
152 | if (page >= 5) {
153 | this.list.unshift(
154 |
158 |
165 | {1}
166 |
167 |
172 | ...
173 |
174 |
175 | );
176 | }
177 |
178 | if (page !== 1) {
179 | //上一页按钮
180 | this.list.unshift(
181 |
186 | 上一页
187 |
188 | );
189 | }
190 |
191 | if (page <= count - 4 && count > 5) {
192 | this.list.push(
193 |
197 |
202 | ...
203 |
204 |
211 | {count}
212 |
213 |
214 | );
215 | }
216 |
217 | if (page !== count) {
218 | //下一页按钮
219 | this.list.push(
220 |
225 | 下一页
226 |
227 | );
228 | }
229 | }
230 | render() {
231 | if (this.props.count > 0) {
232 | const { ulStyle } = this.props;
233 | return ;
234 | }
235 | return null;
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/src/components/Comment/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { observer, inject } from "mobx-react";
3 | import xss from "xss"; //过滤xss
4 | import PropTypes from "prop-types";
5 | import { app as ajax } from "@request";
6 |
7 | import CommentHeader from "./Header";
8 | import CommentList from "./List";
9 | import CommentTextarea from "./Textarea";
10 | import CommentPage from "../Page";
11 | import CommentRmoeve from "../Modal/remove";
12 | import Loading from "./Loading";
13 |
14 | @inject("Appstore")
15 | @observer
16 | export default class Comment extends Component {
17 | static propTypes = {
18 | Appstore: PropTypes.object,
19 | posts_id: PropTypes.string,
20 | author_id: PropTypes.string,
21 | p_index: PropTypes.number
22 | };
23 | constructor(props) {
24 | super(props);
25 | this.state = {
26 | comments: [],
27 | loading: true,
28 | count: 0,
29 | page: 0, //评论页数
30 | sort: 1, //-1是正序
31 | currentPage: 1,
32 | remove: null,
33 | visibility: false
34 | };
35 | this.setReversed = this.setReversed.bind(this);
36 | this.create = this.create.bind(this);
37 | this.setPage = this.setPage.bind(this);
38 | this.action = this.action.bind(this);
39 | this.onRemove = this.onRemove.bind(this);
40 | this.offRemove = this.offRemove.bind(this);
41 | this.refx = React.createRef();
42 | this.refc = React.createRef();
43 | this.accept_user_id = null;
44 | this.index = null;
45 | }
46 |
47 | getComment(posts_id, currentPage, sort) {
48 | ajax.getComment(posts_id, currentPage, sort).then(({ comment, count, page }) => {
49 | const timeID = window.setTimeout(() => {
50 | this.setState({
51 | comments: comment,
52 | loading: false,
53 | currentPage,
54 | count,
55 | page,
56 | sort
57 | });
58 | window.clearTimeout(timeID);
59 | }, 500);
60 | });
61 | }
62 |
63 | reply(e) {
64 | e.stopPropagation();
65 | const index = Number(e.target.dataset.index);
66 | const { comments } = this.state;
67 | const author = comments[index].author;
68 | this.accept_user_id = author[0]._id;
69 | this.refx.current.placeholder = `回复${author[0].name}:`;
70 | this.refx.current.focus();
71 | //const height = window.innerHeight/2;
72 |
73 | window.scroll(0, this.refx.current.parentElement.offsetTop - 300);
74 | }
75 |
76 | setReversed(e) {
77 | e.stopPropagation();
78 | const sort = this.state.sort === -1 ? 1 : -1;
79 | this.setState({ loading: true });
80 | this.getComment(this.props.posts_id, this.state.currentPage, sort);
81 | }
82 |
83 | setPage(page) {
84 | //跳到某页评论
85 | if (typeof page == "number") {
86 | window.scroll(0, this.refc.current.offsetTop - 50);
87 | this.setState({ loading: true });
88 | this.getComment(this.props.posts_id, page, this.state.sort);
89 | }
90 | }
91 |
92 | good(e) {
93 | //点赞评论
94 | e.stopPropagation();
95 |
96 | const index = Number(e.target.dataset.index);
97 | const { comments } = this.state;
98 |
99 | const comment_id = comments[index].comment.comment_id;
100 | const author = comments[index].author;
101 | const good = comments[index].comment.good;
102 |
103 | const posts_id = this.props.posts_id;
104 |
105 | if (this.props.Appstore.id !== author[0]._id) {
106 | const good_index = good.indexOf(this.props.Appstore.id);
107 | const action = good_index === -1 ? "add" : "remove";
108 |
109 | ajax.good({ comment_id, posts_id, action }).then(() => {
110 | good_index === -1
111 | ? comments[index].comment.good.push(this.props.Appstore.id)
112 | : comments[index].comment.good.splice(good_index, 1);
113 | this.setState({ comments });
114 | });
115 | } else {
116 | this.props.Appstore.setMessage({ text: "你不能点赞自己的评论", is: false });
117 | }
118 | }
119 |
120 | create(value) {
121 | if (/^.{1,300}$/.test(value)) {
122 | const text = xss(value);
123 | const user =
124 | this.accept_user_id !== null
125 | ? [this.props.Appstore.id, this.accept_user_id]
126 | : [this.props.Appstore.id];
127 | const { posts_id, author_id } = this.props;
128 |
129 | ajax.createComment({ posts_id, author_id, user, text }).then(
130 | ({ comment }) => {
131 | const { comments, count } = this.state;
132 | comments.push(comment);
133 | this.setState({ comments: comments, count: count + 1 });
134 | this.props.Appstore.comment_count("add", this.props.p_index);
135 | this.props.Appstore.setMessage({ text: "发表评论成功", is: true });
136 | }
137 | );
138 | } else {
139 | this.props.Appstore.setMessage({
140 | text: "评论内容限制在1-300个字节内",
141 | is: false
142 | });
143 | }
144 | }
145 |
146 | onRemove() {
147 | const { comments, currentPage, sort, count } = this.state;
148 |
149 | const { posts_id, author_id } = this.props;
150 | const comment_id = comments[this.index].comment.comment_id;
151 | const user = comments[this.index].comment.user;
152 |
153 | if (user[0] === this.props.Appstore.id) {
154 | ajax.removeComment({ comment_id, posts_id, author_id, user }).then(() => {
155 | if(comments.length-1 > 0){
156 | comments.splice(this.index, 1);
157 | this.props.Appstore.comment_count("remove", this.props.p_index);
158 | this.setState({
159 | comments: comments,
160 | count: count - 1,
161 | visibility: false,
162 | description: null
163 | });
164 | }
165 | else{
166 | const pages = currentPage - 1 > 0 ? currentPage-1 : 1;
167 | this.props.Appstore.comment_count("remove", this.props.p_index);
168 | this.setState({ visibility: false, description: null });
169 | this.getComment(posts_id, pages, sort);
170 | }
171 | });
172 | }
173 | }
174 |
175 | offRemove() {
176 | this.index = null;
177 | this.setState({ visibility: false, description: null });
178 | }
179 |
180 | remove(e) {
181 | e.stopPropagation();
182 | this.index = Number(e.target.dataset.index);
183 | this.setState({
184 | visibility: true,
185 | description: { title: "你确定要删除这条评论吗?" }
186 | });
187 | }
188 |
189 | action(e) {
190 | e.stopPropagation();
191 | if (e.target.dataset.action) {
192 | switch (e.target.dataset.action) {
193 | case "reply":
194 | this.reply(e);
195 | break;
196 |
197 | case "good":
198 | this.good(e);
199 | break;
200 |
201 | case "remove":
202 | this.remove(e);
203 | break;
204 | }
205 | }
206 | }
207 | render() {
208 | const {
209 | sort,
210 | comments,
211 | loading,
212 | count,
213 | page,
214 | visibility,
215 | description,
216 | currentPage
217 | } = this.state;
218 | return (
219 |
220 |
221 |
222 | {loading ? : }
223 |
230 |
231 |
232 |
238 |
239 | );
240 | }
241 | componentDidMount() {
242 | this.getComment(this.props.posts_id, 1, 1);
243 | }
244 | }
245 | //
246 |
--------------------------------------------------------------------------------