├── .babelrc
├── .gitignore
├── .gitmodules
├── README.md
├── asserts
├── antd-custom.less
└── styles.less
├── build
├── build-manifest.json
├── react-loadable-manifest.json
└── server
│ ├── pages-manifest.json
│ └── ssr-module-cache.js
├── components
├── Ad
│ └── index.js
├── ArticleTitle
│ ├── index.js
│ └── index.less
├── Comments
│ ├── index.js
│ ├── index.less
│ ├── mockData.js
│ └── until.js
├── Edit
│ └── index.js
├── EditArticle
│ ├── index.js
│ └── index.less
├── Footer
│ └── index.js
├── FormComment
│ ├── constants.js
│ └── index.js
├── Gallery
│ ├── ControllerUnit.js
│ ├── ImgFigure.js
│ ├── index.js
│ ├── index.less
│ └── until.js
├── Guitar
│ ├── index.js
│ └── index.less
├── Header
│ ├── index.js
│ └── index.less
├── ListTitle
│ ├── README.md
│ ├── index.js
│ └── index.less
├── MyCard
│ ├── index.js
│ └── index.less
├── MyHead
│ └── index.js
├── MyLayout
│ └── index.js
├── PrevNextPage
│ └── index.js
├── TopTips
│ ├── README.md
│ ├── index.js
│ └── index.less
└── loading.js
├── config
├── constantTag.js
├── constantsData.js
├── env.js
├── githubApi.js
├── index.js
├── markdown.js
├── qiniu_config.js
└── qiniuyun_cdn.js
├── lib
└── with-redux-store.js
├── next.config.js
├── package.json
├── pages
├── _app.js
├── _error.js
├── about
│ ├── index.js
│ └── index.less
├── admin
│ ├── index.js
│ ├── req-action.js
│ ├── setTable.js
│ └── table-data.js
├── adminDetail
│ └── index.js
├── blog
│ ├── README.md
│ ├── index.js
│ ├── index.less
│ └── searchType.js
├── detail
│ ├── index.js
│ ├── index.less
│ ├── markdown-navbar.less
│ ├── pop-tips.less
│ ├── util.js
│ └── viewer.min.less
├── gallery
│ ├── index.js
│ └── index.less
├── index.less
├── index
│ ├── index.js
│ └── index.less
├── life
│ └── index.js
├── markdown.less
├── postArticle
│ └── index.js
└── versions
│ ├── index.js
│ ├── index.less
│ └── mockData.js
├── server.js
├── static
├── 420style.jpg
├── ajax.png
├── all.png
├── blogimg
│ ├── 677e4af2jw1f51htgf9ofj20qe0s0tj1.jpg
│ ├── IMG_20161119_160013.jpg
│ ├── IMG_20170604_205917.jpg
│ ├── life1.png
│ ├── life2.png
│ ├── life3.png
│ ├── lkasdfghjk.jpg
│ ├── loader.gif
│ ├── time.png
│ ├── time2.png
│ ├── time3.png
│ └── time4.png
├── c_1.jpg
├── c_2.jpg
├── copy.png
├── css.png
├── css
│ ├── iconfont
│ │ ├── iconfont.css
│ │ ├── iconfont.eot
│ │ ├── iconfont.svg
│ │ ├── iconfont.ttf
│ │ └── iconfont.woff
│ └── nprogress.css
├── cv
│ └── liuweibo_FrontEnd_CV.doc
├── favicon.ico
├── fight.png
├── guitar.png
├── h5.png
├── home
│ ├── bg.png
│ └── borders1.png
├── interesting.png
├── jianli
│ ├── 1072586786_compressed.jpg
│ ├── 353938127_compressed.jpg
│ ├── IMG_20140329_011043_compressed.jpg
│ ├── IMG_20140408_094952_compressed.jpg
│ ├── IMG_20140702_002912_compressed.jpg
│ ├── IMG_20150306_180108_compressed.jpg
│ ├── IMG_20150515_093309_compressed.jpg
│ ├── IMG_20150716_130022_compressed.jpg
│ ├── IMG_20150723_090614_compressed.jpg
│ ├── IMG_20160120_203926_compressed.jpg
│ ├── IMG_20160309_151645_compressed.jpg
│ ├── IMG_20160312_145558_compressed.jpg
│ ├── IMG_20160628_182845_compressed.jpg
│ ├── IMG_20161004_101037_compressed.jpg
│ ├── IMG_20161103_210243_compressed.jpg
│ └── mmexport1448350892762_compressed.jpg
├── js.png
├── loader.gif
├── musicControl.png
├── mv.jpg
├── mysql.png
├── node.png
├── php.png
├── robots.txt
├── server.png
├── sitemap.html
├── sitemap.xml
├── ttround.png
└── user
│ ├── head-1.jpg
│ ├── head-2.jpg
│ ├── head-3.jpg
│ ├── head-4.jpg
│ └── header-my.jpg
├── store
├── action-types.js
├── actions.js
├── reducers.js
└── store.js
└── until
├── cookie.js
├── getiView.js
├── index.js
└── upload-file.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": [
4 | [
5 | "import",
6 | {
7 | "libraryName": "antd",
8 | "style": "less"
9 | }
10 | ],
11 | [
12 | "@babel/plugin-proposal-decorators",
13 | {
14 | "legacy": true
15 | }
16 | ]
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | .idea
6 | .next
7 |
8 | # production
9 | /dist
10 |
11 | # misc
12 | .DS_Store
13 | npm-debug.log*
14 |
15 | # umi
16 | .umi
17 | .umi-production
18 |
19 | .vscode
20 |
21 | package-lock.json
22 | .vscode/settings.json
23 |
24 | yarn.lock
25 | package-lock.json
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lwbBlogBuild"]
2 | path = lwbBlogBuild
3 | url = https://gitee.com/Weibozzz/lwbBlogBuild.git
4 |
--------------------------------------------------------------------------------
/asserts/antd-custom.less:
--------------------------------------------------------------------------------
1 | @primary-color: #017E66;
2 |
3 | @layout-header-height: 40px;
4 | @border-radius-base: 0px;
5 |
--------------------------------------------------------------------------------
/asserts/styles.less:
--------------------------------------------------------------------------------
1 | @import "~antd/dist/antd.less";
2 | @import "./antd-custom.less";
3 |
--------------------------------------------------------------------------------
/build/build-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "devFiles": [
3 | "static/development/dll/dll_e151f72dbdaf690b2a98.js"
4 | ],
5 | "pages": {
6 | "/_app": [
7 | "static/runtime/webpack.js",
8 | "static/css/styles.chunk.css",
9 | "static/chunks/styles.js",
10 | "static/runtime/main.js"
11 | ],
12 | "/_error": [
13 | "static/runtime/webpack.js",
14 | "static/runtime/main.js"
15 | ],
16 | "/about": [
17 | "static/runtime/webpack.js",
18 | "static/css/styles.chunk.css",
19 | "static/chunks/styles.js",
20 | "static/runtime/main.js"
21 | ]
22 | }
23 | }
--------------------------------------------------------------------------------
/build/react-loadable-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "./index.less": [
3 | {
4 | "id": "./components/Header/index.less",
5 | "name": "./components/Header/index.less",
6 | "file": "static/chunks/styles.js",
7 | "publicPath": "static/chunks/styles.js"
8 | },
9 | {
10 | "id": "./components/Guitar/index.less",
11 | "name": "./components/Guitar/index.less",
12 | "file": "static/chunks/styles.js",
13 | "publicPath": "static/chunks/styles.js"
14 | },
15 | {
16 | "id": "./pages/index.less",
17 | "name": "./pages/index.less",
18 | "file": "static/chunks/styles.js",
19 | "publicPath": "static/chunks/styles.js"
20 | },
21 | {
22 | "id": "./components/MyCard/index.less",
23 | "name": "./components/MyCard/index.less",
24 | "file": "static/chunks/styles.js",
25 | "publicPath": "static/chunks/styles.js"
26 | },
27 | {
28 | "id": "./components/Comments/index.less",
29 | "name": "./components/Comments/index.less",
30 | "file": "static/chunks/styles.js",
31 | "publicPath": "static/chunks/styles.js"
32 | },
33 | {
34 | "id": "./pages/about/index.less",
35 | "name": "./pages/about/index.less",
36 | "file": "static/chunks/styles.js",
37 | "publicPath": "static/chunks/styles.js"
38 | }
39 | ],
40 | "undefined": [
41 | {
42 | "id": 2,
43 | "name": null,
44 | "file": "static/chunks/styles.js",
45 | "publicPath": "static/chunks/styles.js"
46 | },
47 | {
48 | "id": 3,
49 | "name": null,
50 | "file": "static/chunks/styles.js",
51 | "publicPath": "static/chunks/styles.js"
52 | },
53 | {
54 | "id": 4,
55 | "name": null,
56 | "file": "static/chunks/styles.js",
57 | "publicPath": "static/chunks/styles.js"
58 | },
59 | {
60 | "id": 5,
61 | "name": null,
62 | "file": "static/chunks/styles.js",
63 | "publicPath": "static/chunks/styles.js"
64 | },
65 | {
66 | "id": 6,
67 | "name": null,
68 | "file": "static/chunks/styles.js",
69 | "publicPath": "static/chunks/styles.js"
70 | },
71 | {
72 | "id": 11,
73 | "name": null,
74 | "file": "static/chunks/styles.js",
75 | "publicPath": "static/chunks/styles.js"
76 | },
77 | {
78 | "id": 12,
79 | "name": null,
80 | "file": "static/chunks/styles.js",
81 | "publicPath": "static/chunks/styles.js"
82 | },
83 | {
84 | "id": 13,
85 | "name": null,
86 | "file": "static/chunks/styles.js",
87 | "publicPath": "static/chunks/styles.js"
88 | }
89 | ],
90 | "../asserts/styles.less": [
91 | {
92 | "id": "./asserts/styles.less",
93 | "name": "./asserts/styles.less",
94 | "file": "static/chunks/styles.js",
95 | "publicPath": "static/chunks/styles.js"
96 | }
97 | ],
98 | "./markdown.less": [
99 | {
100 | "id": "./pages/markdown.less",
101 | "name": "./pages/markdown.less",
102 | "file": "static/chunks/styles.js",
103 | "publicPath": "static/chunks/styles.js"
104 | }
105 | ],
106 | "./noop": [
107 | {
108 | "id": "./node_modules/_next@7.0.3@next/dist/client/noop.js",
109 | "name": "./node_modules/_next@7.0.3@next/dist/client/noop.js",
110 | "file": "static/chunks/0.js",
111 | "publicPath": "static/chunks/0.js"
112 | }
113 | ],
114 | "../../components/TopTips": [
115 | {
116 | "id": "./components/TopTips/index.js",
117 | "name": "./components/TopTips/index.js",
118 | "file": "static/chunks/1.js",
119 | "publicPath": "static/chunks/1.js"
120 | }
121 | ],
122 | "../../config/githubApi": [
123 | {
124 | "id": "./config/githubApi.js",
125 | "name": "./config/githubApi.js",
126 | "file": "static/chunks/1.js",
127 | "publicPath": "static/chunks/1.js"
128 | }
129 | ],
130 | "../_util/getDataOrAriaProps": [
131 | {
132 | "id": "./node_modules/_antd@3.26.20@antd/lib/_util/getDataOrAriaProps.js",
133 | "name": "./node_modules/_antd@3.26.20@antd/lib/_util/getDataOrAriaProps.js",
134 | "file": "static/chunks/1.js",
135 | "publicPath": "static/chunks/1.js"
136 | }
137 | ],
138 | "../_util/wave": [
139 | {
140 | "id": "./node_modules/_antd@3.26.20@antd/lib/_util/wave.js",
141 | "name": "./node_modules/_antd@3.26.20@antd/lib/_util/wave.js",
142 | "file": "static/chunks/1.js",
143 | "publicPath": "static/chunks/1.js"
144 | }
145 | ],
146 | "antd/lib/alert": [
147 | {
148 | "id": "./node_modules/_antd@3.26.20@antd/lib/alert/index.js",
149 | "name": "./node_modules/_antd@3.26.20@antd/lib/alert/index.js",
150 | "file": "static/chunks/1.js",
151 | "publicPath": "static/chunks/1.js"
152 | }
153 | ],
154 | "./button-group": [
155 | {
156 | "id": "./node_modules/_antd@3.26.20@antd/lib/button/button-group.js",
157 | "name": "./node_modules/_antd@3.26.20@antd/lib/button/button-group.js",
158 | "file": "static/chunks/1.js",
159 | "publicPath": "static/chunks/1.js"
160 | }
161 | ],
162 | "./button": [
163 | {
164 | "id": "./node_modules/_antd@3.26.20@antd/lib/button/button.js",
165 | "name": "./node_modules/_antd@3.26.20@antd/lib/button/button.js",
166 | "file": "static/chunks/1.js",
167 | "publicPath": "static/chunks/1.js"
168 | }
169 | ],
170 | "antd/lib/button": [
171 | {
172 | "id": "./node_modules/_antd@3.26.20@antd/lib/button/index.js",
173 | "name": "./node_modules/_antd@3.26.20@antd/lib/button/index.js",
174 | "file": "static/chunks/1.js",
175 | "publicPath": "static/chunks/1.js"
176 | }
177 | ],
178 | "css-animation/lib/Event": [
179 | {
180 | "id": "./node_modules/_css-animation@1.6.1@css-animation/lib/Event.js",
181 | "name": "./node_modules/_css-animation@1.6.1@css-animation/lib/Event.js",
182 | "file": "static/chunks/1.js",
183 | "publicPath": "static/chunks/1.js"
184 | }
185 | ],
186 | "whatwg-fetch": [
187 | {
188 | "id": "./node_modules/_whatwg-fetch@2.0.4@whatwg-fetch/fetch.js",
189 | "name": "./node_modules/_whatwg-fetch@2.0.4@whatwg-fetch/fetch.js",
190 | "file": "static/chunks/1.js",
191 | "publicPath": "static/chunks/1.js"
192 | }
193 | ]
194 | }
--------------------------------------------------------------------------------
/build/server/pages-manifest.json:
--------------------------------------------------------------------------------
1 | {"/_app":"static/development/pages/_app.js","/_error":"static/development/pages/_error.js","/_document":"static/development/pages/_document.js","/about":"static/development/pages/about.js"}
--------------------------------------------------------------------------------
/build/server/ssr-module-cache.js:
--------------------------------------------------------------------------------
1 |
2 | /* This cache is used by webpack for instantiated modules */
3 | module.exports = {}
4 |
--------------------------------------------------------------------------------
/components/Ad/index.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import {connect} from 'react-redux';
3 | import { List, Avatar, Icon ,Divider,Row,Col,Tooltip} from 'antd';
4 | import {COLORS_ARR} from '../../config/constantsData';
5 | import {getRandomMarginTop,getRandomMarginLeft,getRandomTxt} from '../../config/constantTag';
6 |
7 | class Ad extends Component {
8 | constructor(props) {
9 | super(props);
10 | this.state={
11 | dataSource:[]
12 | }
13 | }
14 | componentWillMount (){
15 | const {dataSource} = this.props;
16 | this.setState({
17 | dataSource
18 | })
19 | }
20 | componentWillReceiveProps (){
21 | const {dataSource} = this.props;
22 | this.setState({
23 | dataSource
24 | })
25 | }
26 | shouldComponentUpdate ({dataSource}){
27 | const {dataSource:hasUrlComment} = this.props;
28 | return dataSource.length!==hasUrlComment.length;
29 | }
30 | render(){
31 | const {dataSource:hasUrlComment} = this.props;
32 | return (
33 |
34 |
35 | {
36 | hasUrlComment.map((v,index)=>{
37 | let ran = Math.random()*COLORS_ARR.length | 0;
38 | let color = COLORS_ARR[ran];
39 | return (
40 |
41 |
44 | {getRandomTxt(v.user)}
45 |
46 |
47 | );
48 | })
49 | }
50 |
51 |
57 |
58 | );
59 | }
60 | }
61 |
62 | export default Ad
63 |
--------------------------------------------------------------------------------
/components/ArticleTitle/index.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import {connect} from 'react-redux';
3 | import { List, Avatar, Icon ,Divider,Row,Col} from 'antd';
4 | import {formatTime,regUrl} from '../../until';
5 | import {POST_ARTICLE_TYPE_MAPPING} from '../../config/constantsData';
6 | import {DEFAULT_TAG_ARR,getRandomMarginTop,getRandomMarginLeft,getRandomTxt} from '../../config/constantTag';
7 | import Ad from '../Ad';
8 | import './index.less'
9 |
10 |
11 | const ArticleTitle = (...args)=>{
12 | const {detailArticle={},commentsData=[],htmlStr=''} = args[0];
13 | const READING_TIME = Math.ceil(htmlStr.length/20677*29);
14 | let hasUrlComment = commentsData.filter(v=>regUrl.test(v.website)).slice(0,10)
15 | if(hasUrlComment.length<10){
16 | hasUrlComment=[...hasUrlComment,...DEFAULT_TAG_ARR].slice(0,10)
17 | }
18 | let {title,createTime,user,visitor,lastModify,modifyCount,type=['js'],url='',like=0} = detailArticle
19 | if(lastModify===0){
20 | lastModify=createTime
21 | }
22 | return (
23 |
24 |
25 |
28 |
29 | {title}
30 |
31 | {
32 | url===''?
33 | 原:''
34 | }
35 | {
36 | type.split(',').map(v=>({POST_ARTICLE_TYPE_MAPPING[v] || v}))
37 | }
38 | 读完大概需要{READING_TIME}分钟
39 |
40 |
41 |
42 | - 发布时间:{formatTime(createTime)}
43 | - {user}
44 | - {visitor}
45 | - 更新于: {formatTime(lastModify)}
46 | {/*- 修改次数:{modifyCount}
*/}
47 |
48 |
49 |
50 |
55 |
56 |
57 |
58 |
59 |
66 |
67 | )
68 | }
69 | const mapStateToProps = state => {
70 | const {getCommentsData} = state;
71 | return {getCommentsData};
72 | }
73 | export default connect(mapStateToProps)(ArticleTitle)
74 |
--------------------------------------------------------------------------------
/components/ArticleTitle/index.less:
--------------------------------------------------------------------------------
1 | .article-title-component{
2 | padding: 24px;
3 | background-color: #fff;
4 | .tag-wrapper{
5 | margin: 30px 0 50px 0;
6 | }
7 | .origin-article{
8 | display: inline-block;
9 | font-size: 13px;
10 | height: 22px;
11 | margin-right: 10px;
12 | line-height: 22px;
13 | padding: 0 9px;
14 | border-radius: 11px;
15 | font-weight: normal;
16 | background-color: rgba(242,174,67,0.25);
17 | color: #F2AE43;
18 | }
19 | }
20 |
21 |
22 | .advertisement-wrapper{
23 | .content{
24 | width: 255px;
25 | height: 255px;
26 | background-color: #f5f5f5;
27 | margin-left: 20px;
28 | overflow: hidden;
29 | .link-friends-a{
30 | display: inline-block;
31 | }
32 | span{
33 | display: inline-block;
34 | padding: 3px 5px;
35 | margin-top: 3px;
36 | margin-left: 3px;
37 | background-color: #000;
38 | color: #fff;
39 | font-size: 12px;
40 | }
41 | }
42 | .remark{
43 | text-align: center;
44 | width: 255px;
45 | margin-left: 20px;
46 | .my-link{
47 | color: #9E9E9E;
48 | font-size: 12px;
49 | &:hover{
50 | text-decoration: underline;
51 | }
52 | }
53 | }
54 | }
55 | .read-time{
56 | margin-left: 20px;
57 | font-size: 14px;
58 | color: #999;
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/components/Comments/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux';
3 | import {
4 | Layout, Menu, Breadcrumb, Row, Col, BackTop, Card, Form,
5 | Input, Tooltip, Cascader, Select, Checkbox, Button,
6 | AutoComplete, List, Avatar, Icon, Divider, message
7 | } from 'antd';
8 |
9 | import {COMMENT_IMAGES} from "../../config/constantsData";
10 | import {default_comment, DEFAULT_TAG_ARR} from "../../config/constantTag";
11 | import MyCard from '../MyCard';
12 | import FormComment from '../FormComment';
13 | import {mockCommentsData} from './mockData';
14 | import {getMyCommenData} from './until';
15 | import './index.less'
16 |
17 |
18 | class Comments extends Component {
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | autoCompleteResult: [],
23 | articleID: ''
24 | }
25 | }
26 |
27 |
28 | componentWillMount() {
29 | const {blogData = []} = this.props;
30 | let {id: articleID} = blogData[0] || {};
31 | this.setState({
32 | articleID
33 | })
34 | }
35 |
36 |
37 | render() {
38 | const {dataSource = {}, commentIndex = -1} = this.props;
39 | let {commentsData = [], commentTitle = '发表评论', commentRow = 8} = dataSource;
40 | commentsData = [...default_comment, ...getMyCommenData(commentsData)];
41 | return (
42 |
68 | )
69 | }
70 | }
71 |
72 | const mapStateToProps = state => {
73 | const {commentIndex} = state;
74 | return {commentIndex};
75 | }
76 |
77 | const WrappedRegistrationForm = Form.create()(Comments);
78 | export default connect(mapStateToProps)(WrappedRegistrationForm);
79 |
--------------------------------------------------------------------------------
/components/Comments/index.less:
--------------------------------------------------------------------------------
1 | .css-move-top{
2 | .ant-card-body{
3 | position: relative;
4 | .msg-p{
5 | padding-right: 50px;
6 | word-break: break-all;
7 | }
8 | .icon-comment{
9 | position: absolute;
10 | top: 50%;
11 | right: -10px;
12 | width: 50px;
13 | height: 50px;
14 | transform: translateY(-50%);
15 | overflow:hidden;
16 | .icon-posi{
17 | position: relative;
18 | img{
19 | position: absolute;
20 | left: 50%;
21 | height: 50px;
22 | transform: translateX(-50%);
23 | border-radius: 50%;
24 | }
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/components/Comments/mockData.js:
--------------------------------------------------------------------------------
1 | export const mockCommentsData = [
2 | {
3 | "id": 52,
4 | "a_id": 205,
5 | "user": "测试 grade 0",
6 | "remark": "",
7 | "website": null,
8 | "msg": "测试 grade 0",
9 | "createTime": "1533272366",
10 | grade: 0
11 | },
12 | {
13 | "id": 53,
14 | "a_id": 205,
15 | "user": "测试 grade 1",
16 | "remark": "测试 grade 0",
17 | "website": null,
18 | "msg": "测试 grade 1",
19 | "createTime": '1533272368',
20 | grade: 1
21 | },
22 | {
23 | "id": 54,
24 | "a_id": 205,
25 | "user": "测试 grade 2",
26 | "remark": "测试 grade 0",
27 | "website": null,
28 | "msg": "测试 grade 2",
29 | "createTime": '1533272369',
30 | grade: 2
31 | },
32 | {
33 | "id": 55,
34 | "a_id": 205,
35 | "user": "测试1 grade 0",
36 | "remark": "",
37 | "website": null,
38 | "msg": "测试1 grade 0",
39 | "createTime": '1533272379',
40 | grade: 0
41 | },
42 | ]
43 | export const resultMockCommentsData = {
44 | 0: [
45 | {
46 | "id": 52,
47 | "a_id": 205,
48 | "user": "测试 grade 0",
49 | "remark": "",
50 | "website": null,
51 | "msg": "测试 grade 0",
52 | "createTime": "1533272366",
53 | grade: 0
54 | },
55 | {
56 | "id": 54,
57 | "a_id": 205,
58 | "user": "测试1 grade 0",
59 | "remark": "",
60 | "website": null,
61 | "msg": "测试1 grade 0",
62 | "createTime": '1533272379',
63 | grade: 0
64 | },
65 | ]
66 | }
--------------------------------------------------------------------------------
/components/Comments/until.js:
--------------------------------------------------------------------------------
1 | export const getMyCommenData=(COMMENT_MOCK_DATA_ORIGIN)=> {
2 | let mapping = {answerId: []}
3 | let grande_one = COMMENT_MOCK_DATA_ORIGIN.filter(v => {
4 | let {answerId = ''} = v;
5 | if (answerId) {
6 | mapping[answerId] = []
7 | }
8 | return answerId === '' || answerId==null||answerId===0;
9 | })
10 | COMMENT_MOCK_DATA_ORIGIN.forEach(v => {
11 | let {answerId = ''} = v;
12 | if (answerId in mapping) {
13 | mapping[answerId].push(v)
14 | }
15 | })
16 | return grande_one.map(v => {
17 | let {id} = v;
18 | if (id in mapping) {
19 | v.answerArr = mapping[id]
20 | }
21 | return v;
22 | });
23 | }
--------------------------------------------------------------------------------
/components/Edit/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux';
3 |
4 | import {Row, Col,Input} from 'antd';
5 |
6 | import marked from 'marked'
7 | import hljs from 'highlight.js';
8 |
9 | import {getHtml, OldTime} from '../../until';
10 | import {markdownConfig} from "../../config/markdown";
11 | // import './index.less';
12 |
13 | const { TextArea } = Input;
14 |
15 | const {options,config} = markdownConfig
16 | hljs.configure(config)
17 | marked.setOptions({
18 | highlight: (code) => hljs.highlightAuto(code).value,
19 | ...options
20 | });
21 |
22 | class Edit extends Component {
23 | constructor(props) {
24 | super(props)
25 | this.state = {
26 | previewContent: '',
27 | aceBoxH: null,
28 | originContent: '',
29 | inputValue:'',
30 | }
31 |
32 | this.cacheValue()
33 | this.containerScroll = this.containerScroll.bind(this)
34 | this.onContentChange = this.onContentChange.bind(this)
35 | }
36 |
37 | componentWillMount() {
38 | const {editCont = '', createTime = ''} = this.props;
39 | let decodeOrigin;
40 | try {
41 | decodeOrigin = getHtml(decodeURIComponent(editCont))
42 | } catch (err) {
43 | decodeOrigin = editCont
44 | }
45 | let markedContent = marked(decodeOrigin);
46 | this.setState({
47 | previewContent: markedContent,
48 | originContent: decodeOrigin,
49 | })
50 | }
51 |
52 | componentDidMount() {
53 | this.setState({
54 | aceBoxH: document.documentElement.clientHeight - document.querySelector('.editor-main-a').offsetHeight + 'px'
55 | })
56 | }
57 | cacheValue() {
58 | this.currentTabIndex = 1
59 | this.hasContentChanged = false
60 | this.scale = 1
61 | }
62 |
63 | setCurrentIndex(index) {
64 | this.currentTabIndex = index
65 | }
66 |
67 | containerScroll(e) {
68 | this.hasContentChanged && this.setScrollValue()
69 | if (this.currentTabIndex === 1) {
70 | this.previewContainer.scrollTop = this.editContainer.scrollTop * this.scale
71 | } else {
72 | this.editContainer.scrollTop = this.previewContainer.scrollTop / this.scale
73 | }
74 | }
75 |
76 | onContentChange(e) {
77 | let innerText = e.target.innerText;
78 | innerText=innerText.replace(/script>/img,'script\\>')
79 | let markCont = marked(innerText)
80 | const {handleChangeMarkEdit} = this.props;
81 | handleChangeMarkEdit(innerText)
82 | this.setState({
83 | previewContent: markCont
84 | })
85 | !this.hasContentChanged && (this.hasContentChanged = true)
86 | }
87 |
88 | onOldArticleContentChange(e) {
89 | const {handleChangeMarkEdit} = this.props;
90 | handleChangeMarkEdit(e.target.innerHTML)
91 | }
92 |
93 | setScrollValue() {
94 | // 设置值,方便 scrollBy 操作
95 | this.scale = (this.previewWrap.offsetHeight - this.previewContainer.offsetHeight) / (this.editWrap.offsetHeight - this.editContainer.offsetHeight)
96 | this.hasContentChanged = false
97 | }
98 | onPaste(e){
99 |
100 | }
101 |
102 | render() {
103 |
104 | let {aceBoxH, previewContent, originContent} = this.state;
105 | const {createTime = '',id=''} = this.props;
106 |
107 | return (
108 |
109 |
110 |
112 |
113 | {
114 | createTime > OldTime || createTime === '' || id===1?
115 |
116 |
117 |
118 |
this.editContainer = node}>
120 |
this.editWrap = node}>
124 | {originContent}
125 |
126 |
127 |
128 |
129 |
130 |
131 |
this.previewContainer = node}
133 | onMouseOver={this.setCurrentIndex.bind(this, 2)} onScroll={this.containerScroll}>
134 |
this.previewWrap = node}
135 | dangerouslySetInnerHTML={{__html: previewContent}}>
136 |
137 |
138 |
139 |
140 | :
141 |
151 | }
152 |
153 |
154 |
155 |
170 |
171 |
172 | );
173 | }
174 |
175 | }
176 |
177 | export default connect()(Edit);
178 |
--------------------------------------------------------------------------------
/components/EditArticle/index.less:
--------------------------------------------------------------------------------
1 | .save-status{
2 | margin-left: 15px;
3 | font-size: 14px;
4 | color: #888;
5 | }
--------------------------------------------------------------------------------
/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import Link from 'next/link';
3 | import { Layout,BackTop} from 'antd';
4 |
5 | import {MIIT_BEIAN,MY_BLOG,LINK_ABOUT_ME} from '../../config/constantsData';
6 | const {Footer } = Layout;
7 |
8 | class blogFooter extends Component {
9 | constructor() {
10 | super();
11 |
12 | }
13 | render (){
14 | return (
15 |
38 | );
39 | }
40 | }
41 |
42 | export default blogFooter;
43 |
--------------------------------------------------------------------------------
/components/FormComment/constants.js:
--------------------------------------------------------------------------------
1 | export const formItemLayout = {
2 | labelCol: {
3 | xs: {span: 24},
4 | sm: {span: 24},
5 | lg: {span: 8},
6 | },
7 | wrapperCol: {
8 | xs: {span: 24},
9 | sm: {span: 24},
10 | lg: {span: 16},
11 | },
12 | };
13 | export const tailFormItemLayout = {
14 | wrapperCol: {
15 | xs: {
16 | span: 24,
17 | offset: 0,
18 | },
19 | sm: {
20 | span: 24,
21 | offset: 0,
22 | },
23 | lg: {
24 | span: 16,
25 | offset: 8,
26 | },
27 | },
28 | };
29 | export const tailCommentsTip = {
30 | xs: {
31 | span: 24,
32 | offset: 0,
33 | },
34 | sm: {
35 | span: 24,
36 | offset: 0,
37 | },
38 | lg: {
39 | span: 16,
40 | offset: 8,
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/components/FormComment/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux';
3 | import {
4 | Layout, Menu, Breadcrumb, Row, Col, BackTop, Card, Form,
5 | Input, Tooltip, Cascader, Select, Checkbox, Button, Spin,
6 | AutoComplete, List, Avatar, Icon, Divider, message, Tag, Alert
7 | } from 'antd';
8 |
9 | import Loading from '../loading'
10 | import {regUrl, real_ip} from "../../until";
11 | import {getCookie, setCookie} from "../../until/cookie";
12 | import {postComments, postUserComments, setAnswerId, setCommentIndex} from "../../store/actions";
13 | import {postCommentUrl, postUserCommentUrl} from "../../config";
14 | import {COMMENT_TIPS, COMMENT_LIMIT, commentPlaceHolder, BILILI_ADDRESS} from "../../config/constantsData";
15 | import {DEFAULT_TAG_ARR} from "../../config/constantTag";
16 | import {tailFormItemLayout, tailCommentsTip, formItemLayout} from './constants';
17 |
18 | const FormItem = Form.Item;
19 | const {TextArea} = Input;
20 | const AutoCompleteOption = AutoComplete.Option;
21 |
22 | class FormComment extends Component {
23 | constructor(props) {
24 | super(props);
25 | this.state = {
26 | isLoading: false,
27 | nickname: '',
28 | email: '',
29 | website: '',
30 | autoCompleteResult: [],
31 | }
32 | }
33 |
34 | handleWebsiteChange = (value) => {
35 | let autoCompleteResult;
36 | if (!value) {
37 | autoCompleteResult = [];
38 | } else {
39 | autoCompleteResult = ['.com', '.cn', '.org', '.net'].map(domain => `${value}${domain}`);
40 | }
41 | this.setState({autoCompleteResult});
42 | }
43 | // 存储 cookie
44 | handleSetCookie(nickname,email,website){
45 | setCookie('nickname', nickname, 180)
46 | setCookie('email', email, 180)
47 | setCookie('website', website, 180)
48 | }
49 | // 获取 cookie
50 | handleGetCookie(){
51 | return {
52 | nickname:getCookie('nickname') || '',
53 | email: getCookie('email') || '',
54 | website: getCookie('website') || ''
55 | };
56 | }
57 | componentDidMount(){
58 | this.setState(this.handleGetCookie())
59 | }
60 | handleSubmit = (e) => {
61 | e.preventDefault();
62 | const {dispatch, dataSource = {}, answerId} = this.props;
63 | const { password } = sessionStorage;
64 | const {commentsData: commentsDataOrigin = [], articleID: id, isUserSubmit = false} = dataSource;
65 | if (!id && !isUserSubmit) {
66 | return;
67 | }
68 | this.props.form.validateFieldsAndScroll(async (err, values) => {
69 | if (!err) {
70 | const {comment = '', email = '', nickname = '', website = ''} = values;
71 | if (website !== '' && !regUrl.test(website)) {
72 | message.warning('url不正确,示例:"http://www.xxx.com"')
73 | return;
74 | }
75 | if (nickname.length < 2 || comment.length < 2) {
76 | message.warning('用户名或者评论内容过少')
77 | return;
78 | }
79 | if (comment.length > COMMENT_LIMIT.value) {
80 | message.warning(COMMENT_LIMIT.key)
81 | return;
82 | }
83 |
84 | this.setState({
85 | isLoading: true
86 | })
87 | let realIp = ''
88 | try {
89 | realIp = await real_ip()
90 | } catch (err) {
91 | }
92 | let queryParamsObj = {};
93 | try {
94 | queryParamsObj = {real_ip: realIp, ip: returnCitySN['cip'], address: returnCitySN['cname']};
95 | } catch (err) {
96 | }
97 | const isExist = commentsDataOrigin.findIndex(v => {
98 | const {user, name} = v;
99 | return ((user || name) === nickname) && ((user || name) !== '刘伟波');
100 | }) !== -1;
101 | if (isExist && !answerId) {
102 | message.warning('用户名已存在')
103 | return;
104 | }
105 | const queryStringComment = {
106 | id,
107 | comment: comment.trim(),
108 | email: email.trim(),
109 | nickname: nickname.trim(),
110 | website: website.trim(),
111 | name: nickname.trim(),
112 | token: password,
113 | answerId,
114 | ...queryParamsObj
115 | }
116 | //如果是详情页提交它,如果是关于我,则不用关心id
117 | isUserSubmit ?
118 | postUserComments(dispatch, postUserCommentUrl(), queryStringComment).then(res => {
119 | const {getUserCommentsData} = res;
120 | if (getUserCommentsData.length) {
121 | this.handleSetCookie(nickname,email,website)
122 | message.success(`留言发表成功,感谢支持!`);
123 | }else {
124 | message.warning('您可能没权限');
125 | }
126 | this.setState({
127 | isLoading: false
128 | })
129 | })
130 | :
131 | postComments(dispatch, postCommentUrl(), queryStringComment).then(res => {
132 | if ((res.getCommentsData.length)) {
133 | this.handleSetCookie(nickname,email,website)
134 | message.success(`评论发表成功,感谢支持!`);
135 | }else {
136 | message.warning('您可能没权限');
137 | }
138 | this.setState({
139 | isLoading: false
140 | })
141 | })
142 |
143 | setCommentIndex(dispatch, -1)
144 | setAnswerId(dispatch, '')
145 | }
146 | });
147 | }
148 |
149 | render() {
150 | const {dataSource = {}} = this.props;
151 | const {commentBtnMsg = '提交评论'} = dataSource;
152 | const {nickname,email,website: commentWebsite} = this.state
153 | const {autoCompleteResult, isLoading} = this.state;
154 | const websiteOptions = autoCompleteResult.map(website => (
155 | {website}
156 | ));
157 | const {getFieldDecorator} = this.props.form;
158 | const sf = DEFAULT_TAG_ARR.find(v => v.user === 'segmentfault');
159 | const {user, website} = sf;
160 | const tipsRender =
161 | {COMMENT_TIPS()}
162 | ;
163 | return
164 | {
165 | isLoading &&
166 | }
167 |
168 |
169 |
170 |
171 |
172 |
251 |
252 |
253 | }
254 | }
255 |
256 | const mapStateToProps = state => {
257 | const {answerId} = state;
258 | return {answerId};
259 | }
260 | const WrappedRegistrationForm = Form.create()(FormComment);
261 | export default connect(mapStateToProps)(WrappedRegistrationForm);
262 |
--------------------------------------------------------------------------------
/components/Gallery/ControllerUnit.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 |
4 | /**
5 | * 底部控制栏组件
6 | */
7 | class ControllerUnit extends React.Component {
8 |
9 | /**
10 | * ControllerUnit的点击处理函数
11 | * 判断当前点击的图片是否是中心图片
12 | * 若是,则执行当前图片的翻转
13 | * 若否,则将点击图片居中,同时所有图片的位置都会有调整
14 | */
15 | handleClick(e){
16 | if(this.props.arrange.isCenter){
17 | this.props.inverse();
18 | }else {
19 | this.props.center();
20 | }
21 | e.stopPropagation();
22 | e.preventDefault();
23 | }
24 |
25 | render(){
26 |
27 | let spanClass = classNames({
28 | 'controller-unit': true,
29 | 'is-center': this.props.arrange.isCenter, // 居中按钮样式
30 | 'is-inverse': this.props.arrange.isInverse // 反转按钮样式
31 | });
32 |
33 | return (
34 |
35 | );
36 | }
37 | }
38 |
39 | export default ControllerUnit;
40 |
41 | /**
42 | 1. arrange
43 | {
44 | pos:{ left:0, top:0}
45 | rotate: 0 //旋转角度
46 | isInverse: false // 图片是否正反面
47 | isCenter: false //图片是否居中
48 | }
49 |
50 |
51 | **/
--------------------------------------------------------------------------------
/components/Gallery/ImgFigure.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 | import {CopyToClipboard} from 'react-copy-to-clipboard';
4 | class ImgFigure extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.state={
9 | copied: false
10 | }
11 | this.handleClick = this.handleClick.bind(this);
12 | }
13 |
14 | /**
15 | * imgFigure的点击处理函数
16 | * 判断当前点击的图片是否是中心图片
17 | * 若是,则执行当前图片的翻转
18 | * 若否,则将点击图片居中,同时所有图片的位置都会有调整
19 | */
20 | handleClick(e){
21 | if(this.props.arrange.isCenter){
22 | this.props.inverse();
23 | }else {
24 | this.props.center();
25 | }
26 | e.stopPropagation();
27 | e.preventDefault();
28 | }
29 |
30 | /**
31 | * 获取图片的样式,包括
32 | * 1. 设置position
33 | * 2. 设置旋转角度transform:rotate
34 | * 3. 设置z-index
35 | */
36 | getImgeStyle(){
37 | let styleObj = {};
38 |
39 | /* 如果props属性中指定了这张图片的位置,则使用 */
40 | if (this.props.arrange.pos) {
41 | Object.keys(this.props.arrange.pos).forEach(key => {
42 | styleObj[key] = this.props.arrange.pos[key]
43 | })
44 | }
45 |
46 | /* 添加图片旋转角度样式 */
47 | if(this.props.arrange.rotate){
48 | let prefixArr = ['MozTransform','MsTransform','WebkitTransform','transform'];
49 | prefixArr.forEach( value => {
50 | styleObj[value] = `rotate(${this.props.arrange.rotate}deg)`;
51 | });
52 | }
53 |
54 | /* 设置中心图片的z-index,保证中心图片始终在最上层 */
55 | if(this.props.arrange.isCenter){
56 | styleObj.zIndex = 11;
57 | }
58 | return styleObj;
59 | }
60 |
61 | onCopyLink(e){
62 | console.log(arguments)
63 | }
64 | render() {
65 | let styleObj = this.getImgeStyle();
66 |
67 | let figureClass = classNames({
68 | 'img-figure': true,
69 | 'my-img-figure': true,
70 | 'is-inverse': this.props.arrange.isInverse // 反转样式
71 | });
72 |
73 | let data = this.props.data;
74 | return (
75 |
80 |
81 | this.setState({copied: true})}>
83 |
85 |
86 |
87 | {data.title}
88 |
91 |
92 |
93 | );
94 | }
95 | }
96 |
97 | export default ImgFigure;
98 |
99 |
100 | /**
101 | 1. data
102 | {
103 | "fileName": "1.jpg",
104 | "title": "Heaven of time",
105 | "desc": "Here he comes Here comes Speed Racer."
106 | }
107 | 2. arrange
108 | {
109 | pos:{ left:0, top:0}
110 | rotate: 0 //旋转角度
111 | isInverse: false // 图片是否正反面
112 | isCenter: false //图片是否居中
113 | }
114 |
115 |
116 | **/
--------------------------------------------------------------------------------
/components/Gallery/index.js:
--------------------------------------------------------------------------------
1 | import ImgFigure from './ImgFigure.js';
2 | import ControllerUnit from './ControllerUnit.js';
3 | import React from 'react';
4 | import {findDOMNode} from 'react-dom';
5 | import {Button} from 'antd';
6 | import MyHead from '../MyHead';
7 | import {getRangeRandom, get30DegRandom} from './until';
8 | import {getRandomArr} from '../../until';
9 | import {qiniuyun_cdn_all,qiniuyun_cdn_all_type} from '../../config/qiniuyun_cdn';
10 | import './index.less'
11 |
12 |
13 | const extendArr = qiniuyun_cdn_all;
14 |
15 | /**
16 | * 获取图片的输出地址,imageJsonDatas和imageDatas的结构详见最下面
17 | * 这种图片地址获取方式是通过webpack的loader实现的
18 | */
19 |
20 |
21 | class GalleryByReactApp extends React.Component {
22 |
23 | constructor(props) {
24 | super(props);
25 | this.Constant = {
26 | centerPos: { // 中心图片位置
27 | left: 0,
28 | top: 0
29 | },
30 | leftSection: { // 左扇区,x和y临界值
31 | x: [0, 0],
32 | y: [0, 0]
33 | },
34 | rightSection: { // 右扇区,x和y临界值
35 | x: [0, 0],
36 | y: [0, 0]
37 | },
38 | topSection: { // 上扇区,x和y临界值
39 | x: [0, 0],
40 | y: [0, 0]
41 | }
42 | };
43 |
44 | /** imgsArrangeArr 存放每张图片的位置信息,旋转角度信息 */
45 | this.state = {
46 | imgsArrangeArr: [
47 | /* {
48 | pos:{ left:0, top:0}
49 | rotate: 0 //旋转角度
50 | isInverse: false // 图片是否正反面
51 | isCenter: false //图片是否居中
52 | } */
53 | ],
54 | extendArr,
55 | myImage: []
56 | };
57 | }
58 |
59 | /**
60 | * 重新布局所有图片
61 | * @param: centerIndex指定居中排布哪个图片
62 | */
63 | rearrange(centerIndex) {
64 | let {imgsArrangeArr} = this.state;
65 | let {centerPos, leftSection, rightSection, topSection} = this.Constant;
66 |
67 | /**
68 | * 1. 根据传入的索引分离出居中图片
69 | * 2. 设置居中图片的位置信息
70 | * 3. 最后需要将分离出的居中图片插回imgsArrangeArr(保证索引和imageDatas中一一对应)
71 | */
72 | let center = imgsArrangeArr.splice(centerIndex, 1);
73 | center[0] = {
74 | 'pos': centerPos,
75 | 'rotate': 0,
76 | 'isCenter': true
77 | };
78 |
79 | /**
80 | * 1. 获取需要布局上扇区的图片数量,0个或者1个,50%概率
81 | * 2. 获取一个布局到上扇区图片的索引值(范围是0-14或者0-15)
82 | * 3. 从imgsArrangeArr分离出该索引代表的对象,根据topImgNum是否为0, imgsArrangTopArr可能为空
83 | * 4. 最后也是要插回imgsArrangeArr
84 | **/
85 | let top = [];
86 | let topNum = Math.floor(Math.random() * 2); //取一个或者不取
87 | let topIndex = Math.floor(Math.random() * imgsArrangeArr.length);
88 | top = imgsArrangeArr.splice(topIndex, topNum);
89 |
90 | /** 设置布局位于上扇区的图片位置信息 */
91 | top.forEach((value, index) => {
92 | top[index] = {
93 | pos: {
94 | top: getRangeRandom(topSection.y[0], topSection.y[1]),
95 | left: getRangeRandom(topSection.x[0], topSection.x[1])
96 | },
97 | rotate: get30DegRandom(),
98 | isCenter: false
99 | };
100 | });
101 |
102 | /** 布局左两扇区的图片 */
103 | for (let i = 0, j = imgsArrangeArr.length, k = j / 2; i < j; i++) {
104 | //前半部分布局左边,右边部分布局右边,y值左右扇区多一样,所以这里取左扇区的值
105 | let xRang = i < k ? leftSection.x : rightSection.x;
106 | imgsArrangeArr[i] = {
107 | pos: {
108 | top: getRangeRandom(leftSection.y[0], leftSection.y[1]),
109 | left: getRangeRandom(xRang[0], xRang[1])
110 | },
111 | rotate: get30DegRandom(),
112 | isCenter: false
113 | };
114 | }
115 |
116 | /** 如果上扇区有图片,插回imgsArrangeArr */
117 | if (top && top[0]) {
118 | imgsArrangeArr.splice(topIndex, 0, top[0]);
119 | }
120 | /** 将中心图片插回imgsArrangeArr */
121 | imgsArrangeArr.splice(centerIndex, 0, center[0]);
122 |
123 | this.setState({imgsArrangeArr});
124 | }
125 |
126 | /**
127 | * 利用rearrange函数居中对应index的图片
128 | * @param index 需要被居中的图片的索引值
129 | * return {function}
130 | */
131 | center(index) {
132 | this.rearrange(index);
133 | }
134 |
135 | /**
136 | * 翻转图片
137 | * @param index 传入当前被执行inverse操作的图片对应的图片信息数组的index值
138 | */
139 | inverse(index) {
140 | let {imgsArrangeArr} = this.state;
141 | imgsArrangeArr[index].isInverse = !imgsArrangeArr[index].isInverse;
142 | this.setState({
143 | imgsArrangeArr
144 | });
145 | }
146 |
147 | componentWillMount() {
148 | this.changeImages()
149 | }
150 |
151 | changeImages(param) {
152 | //换一批
153 | let {extendArr} = this.state;
154 | let resultArr=[];
155 | if(Object.prototype.toString.call(param)==='[object String]'){
156 | if(param==='all'){
157 | resultArr=extendArr
158 | }else {
159 | resultArr=qiniuyun_cdn_all_type[param+'Images']
160 | }
161 | }else {
162 | resultArr=extendArr
163 | }
164 | this.setState({
165 | myImage: getRandomArr(resultArr, 20).map(v => {
166 | return {
167 | "imageUrl": resultArr[v].dl_remove_attname_url,
168 | "title": "Heaven of time",
169 | "desc": "Here he comes Here comes Speed Racer."
170 | };
171 | })
172 | })
173 | }
174 |
175 | /**
176 | * componentDidMount方法:组件渲染完成后(即已经出现在dom中)执行的操作
177 | * 操作:为每张图片计算其位置范围
178 | */
179 | componentDidMount() {
180 | /** 拿到舞台的大小,计算一半的值*/
181 | let stageDOM = findDOMNode(this.refs['stage']), // 拿到舞台dom节点
182 | stageW = stageDOM.scrollWidth, // 舞台宽度
183 | stageH = stageDOM.scrollHeight, // 舞台高度
184 | halfStageW = Math.ceil(stageW / 2), // 舞台一半宽度
185 | halfStageH = Math.ceil(stageH / 2); // 舞台一半高度
186 |
187 | /** 拿到一个imgFigure的大小,因为所有imgFigure都一样,所以这里去第一个imgFigure0*/
188 | let imgFigureDOM = findDOMNode(this.refs['imgFigure0']), // 拿到随便一个图片节点
189 | imgW = imgFigureDOM.scrollWidth, // 图片宽度
190 | imgH = imgFigureDOM.scrollHeight, // 图片高度
191 | halfImgW = Math.ceil(imgW / 2), // 图片一半宽度
192 | halfImgH = Math.ceil(imgH / 2); // 图片一半高度
193 |
194 | /** 计算中心图片的位置点, this.Constant存放不变的值 */
195 | this.Constant.centerPos = {
196 | left: halfStageW - halfImgW, // 中心图片left值,需要减去一半图片宽度
197 | top: halfStageH - halfImgH // 中心图片top值,需要减去一半图片高度
198 | };
199 |
200 | /** 计算左扇区,x和y的临界值 */
201 | this.Constant.leftSection.x[0] = -halfImgW; // 左扇区最左值,这里设定最多超多舞台左边界图片宽度的一半
202 | this.Constant.leftSection.x[1] = halfStageW - halfImgW * 3; // 左扇区最右值,注意这里绝对定位的left是指图片左边距离屏幕左边界的距离,所以这里是1.5倍图片宽度,临界情况是图片右边紧贴中心图片最左边
203 | this.Constant.leftSection.y[0] = -halfImgH; // 左扇区的最上,这里设定最多超多舞台上边界图片高度的一半
204 | this.Constant.leftSection.y[1] = stageH - halfImgH; // 左扇区的最下,这里设定高于舞台下边界图片高度的一半
205 | /** 计算右扇区,x和y的临界值*/
206 | this.Constant.rightSection.x[0] = halfStageW + halfImgW; // 右扇区最左值,贴到中心图片的右边,距离中心线半个图片宽度
207 | this.Constant.rightSection.x[1] = stageW - halfImgW; // 右扇区最右值,道理同左扇区最右值
208 | this.Constant.rightSection.y[0] = this.Constant.leftSection.y[0]; // 同左扇区最上
209 | this.Constant.rightSection.y[1] = this.Constant.leftSection.y[1]; // 同左扇区最下
210 | /** 计算上扇,x和y的临界值 */
211 | this.Constant.topSection.y[0] = -halfImgH; // 上扇区最上,同左右扇区最上
212 | this.Constant.topSection.y[1] = halfStageH - halfImgH * 3; // 上扇区最下,道理同左扇区最右值
213 | this.Constant.topSection.x[0] = halfStageW - imgW; // 上扇区最左,中轴线往左一个图片宽度
214 | this.Constant.topSection.x[1] = halfStageW; // 上扇区最右,中轴线(注意left值是以左边为准)
215 |
216 | this.rearrange(0); //默认指定第一张居中
217 | }
218 |
219 |
220 | render() {
221 | let cotrollerUnits = [],
222 | imgFigure = [];
223 |
224 | let {myImage: imageDatas} = this.state;
225 |
226 | imageDatas.forEach((value, index) => {
227 | if (!this.state.imgsArrangeArr[index]) {
228 | this.state.imgsArrangeArr[index] = {
229 | pos: {
230 | left: 0,
231 | top: 0
232 | },
233 | rotate: 0,
234 | isInverse: false,
235 | isCenter: false
236 | }
237 | }
238 | let commonProps = {
239 | key: index,
240 | arrange: this.state.imgsArrangeArr[index],
241 | inverse: this.inverse.bind(this, index),
242 | center: this.center.bind(this, index)
243 | };
244 | imgFigure.push();
245 | cotrollerUnits.push();
246 | });
247 |
248 | return (
249 |
250 |
251 |
252 |
255 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 | );
268 | }
269 | }
270 |
271 | export default GalleryByReactApp;
272 |
273 |
274 | /**
275 | 1. imageJsonDatas 的大致结构
276 | Array[
277 | {
278 | desc:"Here he comes Here comes Speed Racer. "
279 | fileName:"4.jpg"
280 | title:"Heaven of time"
281 | }
282 | ...
283 | ...
284 | ]
285 |
286 | ======================================================================================
287 |
288 | 2. imageDatas 的大致结构
289 | Array[
290 | {
291 | desc:"Here he comes Here comes Speed Racer. "
292 | fileName:"4.jpg"
293 | imageUrl:"/assets/ace3d5b785f01689d46740d26b55d68a.jpg"
294 | title:"Heaven of time"
295 | }
296 | ...
297 | ...
298 | ]
299 |
300 | ======================================================================================
301 |
302 | 3. 舞台示意图(外部虚线包围的是上扇区,左右扇区以此类推,没有下扇区)
303 |
304 | |-----—-上扇区线-----|
305 | | | |
306 | |—————舞台线——————|——-——————|—————————|————————————————————|
307 | | |<--------|-------->| |
308 | | | | | |
309 | | | | | |
310 | |----------------|---- ___|____ ----|--------------------|
311 | | | | |
312 | | | 中心图片| |
313 | | |________| |
314 | | | |
315 | | | |
316 | | | |
317 | | | |
318 | |——————————————————————————|—————舞台线————————————————————|
319 | |
320 | 中轴线
321 | */
--------------------------------------------------------------------------------
/components/Gallery/index.less:
--------------------------------------------------------------------------------
1 |
2 | html, body {
3 | width: 100%;
4 | height: 100%;
5 | background-color: #222;
6 | }
7 |
8 | .content {
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
13 | /* stage --- start */
14 |
15 | .stage {
16 | position: relative;
17 | width: 100%;
18 | height: 100%;
19 | }
20 |
21 | /* stage --- end */
22 |
23 | /* image --- start */
24 |
25 | .img-sec {
26 | position: relative;
27 | width: 100%;
28 | height: 100%;
29 | overflow: hidden;
30 | background-color: #ddd;
31 |
32 | perspective: 1800px;
33 |
34 | .img-figure {
35 | position: absolute;
36 | width: 320px;
37 | min-height: 360px;
38 | margin: 0;
39 | padding: 40px;
40 | background-color: #fff;
41 | cursor:pointer;
42 | box-sizing: border-box;
43 | transform-style: preserve-3d;
44 | transform-origin: 0 50% 0;
45 | transition: transform 0.6s ease-in-out, left 0.6s ease-in-out, top 0.6s ease-in-out;
46 |
47 | &.is-inverse{
48 | transform: translate(320px) rotateY(180deg);
49 | }
50 | }
51 | .my-img-figure{
52 | img{
53 | width: 100%;
54 | }
55 | }
56 |
57 | figcaption {
58 | text-align: center;
59 |
60 | .img-title {
61 | margin:20px 0 0 0;
62 | color: #a7a0a2;
63 | font-size:16px;
64 | }
65 |
66 | .img-back {
67 | position: absolute;
68 | top: 0;
69 | left: 0;
70 |
71 | width: 100%;
72 | height: 100%;
73 | padding: 50px 40px;
74 | overflow: auto;
75 |
76 | color: #a7a0a2;
77 | font-size: 22px;
78 | line-height: 1.25;
79 | text-align: left;
80 |
81 | background-color: #fff;
82 |
83 | box-sizing: border-box;
84 | transform: rotateY(180deg) translateZ(1px);
85 | backface-visibility: hidden;
86 |
87 | p {
88 | margin: 0;
89 | }
90 | }
91 | }
92 | }
93 |
94 | /* image --- end */
95 |
96 |
97 | /* controller-nav --- start */
98 |
99 | .controller-nav {
100 | position: absolute;
101 | left: 0;
102 | bottom: 30px;
103 | z-index: 101;
104 |
105 | width: 100%;
106 | text-align: center;
107 |
108 | .controller-unit{
109 | display:inline-block;
110 | margin:0 5px;
111 | width:30px;
112 | height:30px;
113 | text-align: center;
114 | vertical-align: middle;
115 |
116 | cursor: pointer;
117 | border-radius: 50%;
118 | background-color: #aaa;
119 | transform: scale(0.5);
120 | transition: transform .6s ease-in-out,background-color .3s;
121 |
122 | &.is-center {
123 | transform: scale(1);
124 | background-color: #888;
125 |
126 | &::after {
127 | color:#fff;
128 | font-family: "icons-turn-arrow";
129 | content:"\e600";
130 | line-height: 30px;
131 | font-size: 80%;
132 | //-webkit-font-smoothing: antialiased;
133 | //-moz-osx-font-smoothing: grayscale;
134 | }
135 |
136 | &.is-inverse {
137 | background-color: #555;
138 | transform: rotateY(180deg);
139 | }
140 | }
141 | }
142 | }
143 |
144 | /* controller-nav --- end */
145 | .gallery-component{
146 | .btn-group{
147 | position: fixed;
148 | top: 20px;
149 | right: 50px;
150 | }
151 | }
--------------------------------------------------------------------------------
/components/Gallery/until.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 获取区间内的一个随机值
4 | */
5 | export const getRangeRandom = (low, high) => Math.floor(Math.random() * (high - low) + low );
6 |
7 | /**
8 | * 随机生成一个0-30度正负值角度
9 | */
10 | export const get30DegRandom = () => Math.floor(Math.random()*60-30);
--------------------------------------------------------------------------------
/components/Guitar/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import Link from 'next/link';
3 | import './index.less'
4 | import { BILILI_ADDRESS } from '../../config/constantsData'
5 |
6 | class MyCard extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {}
10 | }
11 | render() {
12 | return (
13 |
14 |
15 |

16 |
17 |
18 | )
19 | }
20 | }
21 |
22 | export default MyCard;
23 |
--------------------------------------------------------------------------------
/components/Guitar/index.less:
--------------------------------------------------------------------------------
1 | .guitar{
2 | position: fixed;
3 | top: 4px;
4 | right: -51px;
5 | z-index: 10;
6 | width: 200px;
7 | }
8 | @media (max-width: 992px){
9 | .guitar{
10 | width: 50px;
11 | top: 24px;
12 | right: -9px;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/components/Header/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { Layout, Menu, Breadcrumb, Row, Col, message } from 'antd';
4 | import Link from 'next/link';
5 | // prefetch预加载
6 | import dynamic from 'next/dynamic';
7 | import * as ROUTER from '../../config/constantsData';
8 | import { getBlogUrl, postAdminPasswordUrl, getTotalUrl } from '../../config';
9 | import { getSearchList, getSearchTotal, postAdminPassword, collectArticleList } from '../../store/actions';
10 | import MyHead from '../../components/MyHead';
11 | import { MENU_RULE, pageNum } from '../../config/constantsData';
12 | import './index.less';
13 | import { VERSIONS_TXT } from '../../config/constantsData';
14 |
15 | //toptis不需要seo
16 | const DynasicTopTipsNoSsr = dynamic(import('../../components/TopTips'), {
17 | ssr: false
18 | });
19 | const { Header, Content } = Layout;
20 | const routes = [
21 | {
22 | href: ROUTER.INDEX,
23 | txt: ROUTER.INDEX_TXT,
24 | isSuperAdmin: false
25 | },
26 | {
27 | href: ROUTER.BLOG,
28 | txt: ROUTER.BLOG_TXT,
29 | isSuperAdmin: false,
30 | isArticleList: true
31 | },
32 | {
33 | href: ROUTER.LIFE,
34 | txt: ROUTER.LIFE_TXT,
35 | isSuperAdmin: false
36 | },
37 | {
38 | href: ROUTER.ONLINE_GITBOOK,
39 | txt: ROUTER.DOCS_TXT,
40 | isSuperAdmin: false
41 | },
42 | {
43 | href: ROUTER.ABOUT,
44 | txt: ROUTER.ABOUT_TXT,
45 | isSuperAdmin: false
46 | },
47 | {
48 | href: ROUTER.BLOG,
49 | txt: '我的收藏',
50 | isSuperAdmin: false,
51 | isMyCollcet: true
52 | },
53 | {
54 | href: ROUTER.VERSIONS,
55 | txt: VERSIONS_TXT,
56 | isSuperAdmin: false,
57 | },
58 | {
59 | href: ROUTER.ADMIN,
60 | txt: ROUTER.ADMIN_TXT,
61 | isSuperAdmin: true
62 | },
63 | {
64 | href: ROUTER.POST_ARTICLE,
65 | txt: ROUTER.POST_ARTICLE_TXT,
66 | isSuperAdmin: true
67 | },
68 | {
69 | href: 'javascript:;',
70 | txt: '退出',
71 | isSuperAdmin: true,
72 | exit: true
73 | },
74 | ];
75 |
76 | class TopNav extends Component {
77 | constructor(props) {
78 | super(props);
79 | this.state = {
80 | isLogin: false,
81 | isAll: false,
82 | defaultSelectedKeys: '/'
83 | };
84 | }
85 |
86 | componentDidMount() {
87 | const { pathname = '/' } = location;
88 | const { dispatch } = this.props;
89 | const { password = '' } = sessionStorage;
90 | this.setState({
91 | selectedKeys: MENU_RULE[pathname] || MENU_RULE['/']
92 | });
93 | if (!password) {
94 | return;
95 | }
96 | postAdminPassword(dispatch, postAdminPasswordUrl(), { password: password })
97 | .then(res => {
98 | const { postAdminPasswordData = [] } = res;
99 | if (postAdminPasswordData.length) {
100 | this.setState({
101 | isLogin: true
102 | });
103 | }
104 | });
105 | }
106 |
107 | onSelect({ keyPath, key }) {
108 | this.setState({
109 | selectedKeys: keyPath
110 | });
111 | }
112 |
113 | onExit() {
114 | //退出清除sessionStorage
115 | sessionStorage.removeItem('password');
116 | this.setState({
117 | isLogin: false
118 | });
119 | }
120 |
121 | onMyCollect(articleList) {
122 | const { isAll } = this.state;
123 | let bool = articleList === 'articleList';
124 | const { dispatch } = this.props;
125 | !bool && this.setState({
126 | isAll: !isAll
127 | });
128 | let collectArrStr = localStorage.getItem('collectArrStr');
129 | let collectArr;
130 | try {
131 | collectArr = JSON.parse(collectArrStr);
132 | } catch (err) {
133 | collectArr = [];
134 | }
135 | if (!Array.isArray(collectArr) || !collectArr.length) {
136 | message.warning(`还没有收藏过文章,点击全部文章,然后点击小星星进行本地收藏!`);
137 | return;
138 | }
139 |
140 | if (this.state.isAll || bool) {
141 | let queryStringObj = {
142 | type: 'all',
143 | num: 1,
144 | pageNum,
145 | wd: ''
146 | };
147 | let queryTotalString = {
148 | type: 'all',
149 | wd: ''
150 | };
151 | getSearchList(dispatch, getBlogUrl(queryStringObj));
152 | getSearchTotal(dispatch, getTotalUrl(queryTotalString));
153 | if (bool) {
154 | collectArticleList(dispatch, false);
155 | return;
156 | }
157 | } else {
158 | getSearchList(dispatch, 'myCollect', collectArr);
159 | }
160 | collectArticleList(dispatch, !this.state.isAll);
161 | }
162 |
163 | onMyFn(item) {
164 | const { isCollectArticle } = this.props;
165 | let fn = () => {
166 | };
167 | if (item.exit) {
168 | fn = this.onExit.bind(this);
169 | } else if (item.isMyCollcet) {
170 | fn = this.onMyCollect.bind(this);
171 | }
172 | else if (item.isArticleList && isCollectArticle) {
173 | fn = this.onMyCollect.bind(this, 'articleList');
174 | }
175 | return
176 |
177 | {item.txt}
178 |
179 | ;
180 | }
181 |
182 | render() {
183 | let { isLogin, selectedKeys = ['/'] } = this.state;
184 | const { userAgent = 'pc' } = this.props;
185 | if (Object.prototype.toString.call(selectedKeys) === '[object String]') {
186 | selectedKeys = ['/'];
187 | }
188 | const regSelectKey = /\/blog[\/]?[0-9]?/;
189 | if (selectedKeys[0] && regSelectKey.test(selectedKeys[0])) {
190 | selectedKeys = ['/blog'];
191 | }
192 | let newRoutes;
193 | if (isLogin) {
194 | newRoutes = routes;
195 | } else {
196 | newRoutes = routes.filter(v => !v.isSuperAdmin);
197 | }
198 | return (
199 |
200 |
201 |
202 |
209 |
210 |
211 |
214 |
229 |
230 |
231 |
232 |
233 |
234 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 | );
246 | }
247 | }
248 |
249 | const mapStateToProps = state => {
250 | const { postAdminPasswordData, isCollectArticle } = state;
251 | return { postAdminPasswordData, isCollectArticle };
252 | };
253 | export default connect(mapStateToProps)(TopNav);
254 |
255 |
--------------------------------------------------------------------------------
/components/Header/index.less:
--------------------------------------------------------------------------------
1 | .my-collect{
2 | color: #fff;
3 | padding: 0 20px;
4 | }
5 |
--------------------------------------------------------------------------------
/components/ListTitle/README.md:
--------------------------------------------------------------------------------
1 | # 这是列表页面的显示
2 | 生活夜念和文章列表页面
3 |
--------------------------------------------------------------------------------
/components/ListTitle/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {
3 | Layout, Menu, Breadcrumb, Row, Col,
4 | List, Avatar, Icon, Pagination, Alert, Input, Button, Radio, Tooltip
5 | } from 'antd'
6 | import {connect} from 'react-redux'
7 | import Link from 'next/link';
8 | import {formatTime, getRandomArr} from '../../until';
9 | import {qiniuyun_cdn_all_type, qiniuyun_cdn_icon} from '../../config/qiniuyun_cdn';
10 | import {POST_ARTICLE_TYPE_MAPPING} from '../../config/constantsData'
11 | import './index.less'
12 |
13 | const lifeImages = qiniuyun_cdn_all_type.lifeImages;
14 | const IconText = ({type, text}) => (
15 |
16 |
17 | {text}
18 |
19 | )
20 |
21 |
22 | class ListTitle extends Component {
23 | constructor() {
24 | super();
25 | this.state = {
26 | collectArr: [],
27 | }
28 | }
29 |
30 | componentDidMount() {
31 | let collectArrStr = localStorage.getItem('collectArrStr');
32 | let collectArr;
33 | try {
34 | collectArr = JSON.parse(collectArrStr)
35 | } catch (err) {
36 | collectArr = []
37 | }
38 | this.setState({
39 | collectArr
40 | })
41 | }
42 |
43 | onCollect(item) {
44 | let collectArrStr = localStorage.getItem('collectArrStr');
45 | try {
46 | collectArrStr = JSON.parse(collectArrStr) || []
47 | } catch (err) {
48 | console.log('收藏语法错误,请规范使用')
49 | localStorage.setItem('collectArrStr', '[]')
50 | collectArrStr = []
51 | }
52 | const {id} = item;
53 | if (collectArrStr.length) {
54 | let index = collectArrStr.findIndex(v => v.id === id);
55 | if (index === -1) {
56 | collectArrStr.push(item)
57 | localStorage.setItem('collectArrStr', JSON.stringify(collectArrStr))
58 | } else {
59 | collectArrStr.splice(index, 1);
60 | localStorage.setItem('collectArrStr', JSON.stringify(collectArrStr))
61 | console.log('文章已经收藏过了,删除收藏')
62 | }
63 | } else {
64 | collectArrStr.push(item);
65 | localStorage.setItem('collectArrStr', JSON.stringify(collectArrStr))
66 | }
67 | this.setState({
68 | collectArr: collectArrStr
69 | })
70 | }
71 |
72 | render() {
73 | const {dataSource = {}, searchType = ''} = this.props;
74 | const {listData = [], pathname = ''} = dataSource;
75 | let {collectArr = []} = this.state;
76 | if (!collectArr) {
77 | collectArr = []
78 | }
79 | collectArr = collectArr.map(v => v.id)
80 | const noeDate = Date.now() / 1000 | 0;
81 | const diff = noeDate - 10 * 24 * 60 * 60;
82 | //随机图片 热门文章(每一页排名前三) 最新文章(最近10天)
83 | let randomArr = [], hotData = [], newData = [];
84 |
85 | if (listData.length && pathname) {
86 | randomArr = getRandomArr(lifeImages, listData.length);
87 | hotData = listData.map(v => ({id: v.id, visitor: v.visitor})).sort((a, b) => b.visitor - a.visitor).slice(0, 3)
88 | newData = listData.filter(v => v.createTime - diff >= 0).map(v => v.id);
89 | }
90 | return (
91 | 我是有底线的 ……}
97 | renderItem={(item, index) => {
98 | let lifeImageSrc = randomArr.length ? randomArr[index] : null;
99 |
100 | let ResultLifeUrl = lifeImageSrc !== null && lifeImages[lifeImageSrc].dl_remove_attname_url;
101 |
102 | let isIcon = pathname === '';
103 | const {type = ['js']} = item;
104 | const srcImg = type.split(',')[0] || 'js';
105 | const qiniu_srcImg = qiniuyun_cdn_icon[srcImg] || qiniuyun_cdn_icon.others;
106 | let isCollect = collectArr.indexOf(item.id) !== -1;
107 | return (
108 |
114 |
115 |
116 |
117 | ,
118 | ,
119 | ,
120 |
121 |
122 |
123 |
124 | ,
125 | ]}
126 | extra={isIcon ?
127 |
128 | :
129 |
}
130 | >
131 |
134 |
135 |
136 | {item.title}
137 |
138 | {
139 | hotData.map(v => v.id).indexOf(item.id) !== -1
140 | ? HOT
141 | : ''
142 | }
143 | {
144 | newData.indexOf(item.id) !== -1 ?
145 | HEW
146 | :
147 | ''
148 | }
149 | {
150 | type.split(',').map((v, index) => (
151 | {POST_ARTICLE_TYPE_MAPPING[v] || v}))
152 | }
153 |
154 |
155 |
156 |
157 | }
158 | description={pathname === '' ? searchType === 'article' ? '暂不支持模糊查询高亮,可以点击进去搜索' : '' : item.short}
159 | />
160 | {
161 | pathname === '' ? '' :
162 |
163 | 阅读全文......
164 |
165 | }
166 |
167 | );
168 | }}
169 | />
170 | );
171 | }
172 | }
173 |
174 | export default ListTitle
175 |
--------------------------------------------------------------------------------
/components/ListTitle/index.less:
--------------------------------------------------------------------------------
1 |
2 | .article-title{
3 | .article-status{
4 | float: left;
5 | }
6 | .new,.hot{
7 | padding: 0 6px;
8 | margin-left: 10px;
9 | background-color: #FF0060;
10 | color: #fff;
11 | border-radius: 12px;
12 | font-size: 12px;
13 | letter-spacing: -1px;
14 | }
15 | .new{
16 | background: #FF6C00;
17 | }
18 | &:after{
19 | content: '';
20 | display: block;
21 | clear: both;
22 | }
23 | }
24 | .list-title-wrapper{
25 | .ant-list-item{
26 | transition: .3s all linear;
27 | &:hover{
28 | transition: .7s all linear;
29 | transform: translateX(10px);
30 | .img{
31 | transition: .3s all linear;
32 | box-shadow: 0 8px 16px 0 rgba(7,17,27,.1);
33 | }
34 | }
35 | }
36 | }
37 |
38 | .life-img-wrapper .ant-list-item-extra{
39 | position: relative;
40 | width: 272px;
41 | height: 200px;
42 | max-height: 200px;
43 | overflow: hidden;
44 | .life-img{
45 | position: absolute;
46 | top: 50%;
47 | left: 0;
48 | transform: translateY(-50%);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/components/MyCard/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux';
3 | import Link from 'next/link';
4 | import {
5 | Layout, Menu, Breadcrumb, Row, Col, BackTop, Card, Form,
6 | Input, Tooltip, Cascader, Select, Checkbox, Button,
7 | AutoComplete, List, Avatar, Icon, Divider, message
8 | } from 'antd';
9 | import marked from 'marked'
10 | import hljs from 'highlight.js';
11 |
12 | import {formatTime, getHtml, regUrl} from "../../until";
13 | import {COMMENT_IMAGES} from "../../config/constantsData";
14 | import {setCommentIndex,setAnswerId} from '../../store/actions';
15 | import {qiniuyun_cdn_icon_i} from '../../config/qiniuyun_cdn';
16 | import './index.less'
17 | import {markdownConfig} from "../../config/markdown";
18 |
19 | const {options,config} = markdownConfig
20 | hljs.configure(config)
21 | marked.setOptions({
22 | highlight: (code) => hljs.highlightAuto(code).value,
23 | ...options
24 | });
25 | class MyCard extends Component {
26 | constructor(props) {
27 | super(props);
28 | this.state = {}
29 | }
30 |
31 | onFormActive(index,answerId){
32 | const {dispatch} = this.props;
33 | setCommentIndex(dispatch,index)
34 | setAnswerId(dispatch,answerId)
35 | }
36 | getUserReactDom(v){
37 | return v.website && regUrl.test(v.website) ?
38 |
39 | {v.user || v.name}
40 |
41 | :
42 | {v.user || v.name};
43 | }
44 | isAuth(v){
45 | return (v.user || v.name)==='刘伟波'?作者:'';
46 | }
47 | render() {
48 | const {dataSource = {}, children = {},commentIndex=-1} = this.props;
49 | const {index, v, i} = dataSource
50 | const markHtml = v.id === -1 ? v.msg : marked(getHtml(v.msg, v.createTime))
51 | return
56 | {
57 | this.getUserReactDom(v)
58 | }
59 | {this.isAuth(v)}
60 | 说道:
61 |
62 |
63 | }
64 | extra={{formatTime(v.createTime)}}>
65 |
66 |
69 |
72 | {
73 | v.id!==-1?
:''
74 | }
75 |
76 |
77 |
78 | {
79 | v.answerArr?
80 | {
81 | v.answerArr.map((s,k)=>(
82 | -
83 |
84 | — {this.getUserReactDom(s)}
85 | {this.isAuth(s)}
86 | {formatTime(s.createTime)}
87 |
88 | ))
89 | }
90 |
:''
91 | }
92 | {commentIndex===i?children:''}
93 |
94 |
95 | ;
96 | }
97 | }
98 | const mapStateToProps = state => {
99 | const {commentIndex} = state;
100 | return {commentIndex};
101 | }
102 |
103 | export default connect(mapStateToProps)(MyCard);
104 |
--------------------------------------------------------------------------------
/components/MyCard/index.less:
--------------------------------------------------------------------------------
1 | .link-comment-user{
2 | &:hover{
3 | text-decoration: underline;
4 | }
5 | }
6 | .answer-wrapper-ul{
7 | margin-top: 10px;
8 | padding: 10px 15px 10px 30px;
9 | background-color: #f0f2f5;
10 | li{
11 | padding: 10px 0;
12 | color: #666;
13 | font-size: 13px;
14 | line-height: 24px;
15 | word-break: break-all;
16 | &:not(:last-child){
17 | border-bottom: 1px dashed rgba(0,0,0,0.09);
18 | }
19 | .time{
20 | margin-left: 20px;
21 | color: #999;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/components/MyHead/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import Head from 'next/head';
3 | import * as ROUTER from '../../config/constantsData';
4 |
5 | const MyHead = ()=>(
6 |
7 |
8 |
9 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {ROUTER.INDEX_TITLE} » {ROUTER.COMMON_TITLE}
24 |
25 | )
26 | export default MyHead
27 |
28 |
--------------------------------------------------------------------------------
/components/MyLayout/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Layout, Row, Col} from 'antd';
3 |
4 | const MyLayout = (props) => {
5 | return (
6 |
7 |
8 |
9 |
14 | {props.children}
15 |
16 |
17 |
23 |
24 |
25 | )
26 | }
27 | export default MyLayout
--------------------------------------------------------------------------------
/components/PrevNextPage/index.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import {regUrl} from "../../until";
3 |
4 | const PrevNextPage = ({dataSource = {}}) => {
5 | const {url = '', lastIdData = [], nextIdData = []} = dataSource;
6 | return
7 | {
8 | url && regUrl.test(url) &&
9 |
10 |
11 | 参考url:
12 | {url}
13 |
14 |
15 | }
16 | {
17 | lastIdData.map(v =>
18 |
26 | )
27 | }
28 | {
29 | nextIdData.map(v =>
30 |
38 | )
39 | }
40 |
;
41 | }
42 | export default PrevNextPage
--------------------------------------------------------------------------------
/components/TopTips/README.md:
--------------------------------------------------------------------------------
1 | # 这是每个页面顶部的提示
2 | 现在的github和开发中
--------------------------------------------------------------------------------
/components/TopTips/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | // import Link from 'next/link';
3 | import Router from 'next/router'
4 | import 'whatwg-fetch'
5 | import {withRouter} from 'next/router'
6 | import {
7 | Layout,
8 | Menu,
9 | Breadcrumb,
10 | Row,
11 | Col,
12 | List,
13 | Avatar,
14 | Icon,
15 | Pagination,
16 | Alert,
17 | Input,
18 | Button,
19 | Radio,
20 | Tooltip
21 | } from 'antd'
22 | import {GITHUB_ADDRESS, TOP_TIPS} from '../../config/constantsData';
23 | // import {getDetailUrl} from "../../config";
24 | import {githubApiStar} from '../../config/githubApi';
25 |
26 | let routerUrl;
27 | Router.onRouteChangeStart = (url) => {
28 | routerUrl = url;
29 | }
30 | const ButtonGroup = Button.Group;
31 |
32 | class TopTips extends Component {
33 | constructor(props) {
34 | super(props);
35 | this.state = {
36 | stargazers_count: 0
37 | }
38 | }
39 | async componentWillMount() {
40 | const blog = await fetch(githubApiStar)
41 | const {stargazers_count=0} = await blog.json();
42 | this.setState({stargazers_count})
43 | }
44 | render() {
45 | const {stargazers_count} = this.state;
46 | return (
47 |
48 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
76 |
77 | );
78 | }
79 | }
80 |
81 | // https://api.github.com/repos/Weibozzz/next-blog stargazers_count
82 | export default withRouter(TopTips)
83 |
--------------------------------------------------------------------------------
/components/TopTips/index.less:
--------------------------------------------------------------------------------
1 | .github-style {
2 | background-color: #eff3f6;
3 | backgroung-image: linear-gradient(-180deg, #fafbfc 0%, #eff3f6 90%);
4 | border-radius: 3px 0 0 3px;
5 | }
6 | .bm-no-border{
7 | border-bottom: none;
8 | border-radius: 0 3px 3px 0;
9 | }
10 |
--------------------------------------------------------------------------------
/components/loading.js:
--------------------------------------------------------------------------------
1 | import {
2 | Spin
3 | } from 'antd'
4 | const loading = () =>{
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 | export default loading;
12 |
--------------------------------------------------------------------------------
/config/constantTag.js:
--------------------------------------------------------------------------------
1 | import {GITHUB_DOCS_ADDRESS_URL_STRING, BILILI_ADDRESS_URL} from './constantsData'
2 | const STEP = 20;
3 | const MARGIN_TOPS = 2;
4 | const MARGIN_LEFTS = 5;
5 | const MAX_LENGTH_TXT = 6;
6 | export const getRandomMarginTop = () => {
7 | return Math.floor(Math.random() * MARGIN_TOPS) * STEP;
8 | }
9 | export const getRandomMarginLeft = () => {
10 | return Math.ceil(Math.random() * MARGIN_LEFTS) * STEP;
11 | }
12 | export const getRandomTxt = (text) => {
13 | let len = text.length;
14 | let random = Math.random() * len;
15 | //最少截取4个字符
16 | let result = Math.max(Math.ceil(random), 4)
17 | if (len > MAX_LENGTH_TXT) {
18 | return text.slice(0, result);
19 | }
20 | return text;
21 | }
22 |
23 | export const DEFAULT_TAG_ARR = [
24 | {
25 | website: 'http://www.liuweibo.cn',
26 | user: '刘伟波网站'
27 | },
28 | {
29 | website: 'https://segmentfault.com/u/weibozzz',
30 | user: 'sf'
31 | },
32 | {
33 | website: 'https://segmentfault.com/n/1330000016909563',
34 | user: 'segmentfault'
35 | },
36 | {
37 | website: 'https://github.com/Weibozzz',
38 | user: 'github'
39 | },
40 | {
41 | website: 'http://www.liuweibo.cn:4321/',
42 | user: '文档中心'
43 | },
44 | {
45 | website: 'https://github.com/Weibozzz/next-blog',
46 | user: '本网站源码'
47 | },
48 | ]
49 | export const POPUP_TIPS = [
50 | '人生可以没有名利,金钱,但必须拥有美好心情',
51 | '岁月会让你的大脑变得轻松,也会让你的脚步变得沉重',
52 | '你的努力不是为了给谁看,而是让人生有更多的选择',
53 | '有实力才会有面子,面子是要实力来支撑',
54 | '生活对我们每个人都是公平的,只是心态的不同造就了人生的不同',
55 | '滚石不生苔,转业不聚财 -- 智哥',
56 | '有时候你以为天就要塌下来了 其实是自己站歪了',
57 | '时间能把你想要的东西都能慢慢给你,但也会把你舍不得的都慢慢带走',
58 | '衣带渐宽终不悔,为伊消得人憔悴',
59 | '做事不需人人都理解,只需尽心尽力',
60 | '真正的大师永远都怀着一颗学徒的心 ——易',
61 | '敢想,敢说,敢做是我们成功的首要前提',
62 | '当你拥有一切的时候你就失去了一切',
63 | '时间就像海绵里的水,只要你去挤,他总会有的',
64 | '再冷的石头,坐上三年也会暖',
65 | '金钱损失了还能挽回,一旦失去信誉就很难挽回',
66 | '人之所以能,是相信能',
67 | ]
68 | export const default_comment = [{
69 | "id": -1,
70 | "a_id": -1,
71 | "user": "刘伟波",
72 | "website": "http://www.liuweibo.cn",
73 | "msg": `
74 | 关于技术问题或者有啥不懂的都可以来 ${BILILI_ADDRESS_URL} 提问,
75 | 推荐作者总结知识点${GITHUB_DOCS_ADDRESS_URL_STRING},
76 | 感謝支持!
77 | `,
78 | "createTime": +new Date()/1000,
79 | "answerId": null
80 | }];
81 |
--------------------------------------------------------------------------------
/config/env.js:
--------------------------------------------------------------------------------
1 | import {DEV_DOMAIN, ONLINE_DOMAIN} from './constantsData';
2 | const dev = process.env.NODE_ENV !== 'production';
3 | const isShow = false; //是否只是演示
4 | export function getDomain() {
5 | if (isShow) {
6 | return 'http://www.liuweibo.cn:7654/';
7 | }
8 | return dev ? DEV_DOMAIN : ONLINE_DOMAIN + '/';
9 | }
10 |
--------------------------------------------------------------------------------
/config/githubApi.js:
--------------------------------------------------------------------------------
1 | export const githubApiStar = 'https://api.github.com/repos/Weibozzz/next-blog'
2 | export const githubApiCommits = 'https://api.github.com/repos/Weibozzz/next-blog/commits'
3 | export const ALL_COMMITS = 'https://github.com/Weibozzz/next-blog/commits?author=Weibozzz'
4 | export const githubApiUser = 'https://api.github.com/users/Weibozzz'
5 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | import {getDomain} from "./env";
2 | import {toQueryStr} from '../until';
3 |
4 | //前台
5 | export const getBlogUrl = (obj) => {
6 | return getDomain() + 'api/getBlog' + toQueryStr(obj);
7 | }
8 | export const addZanUrl = (obj) => {
9 | return getDomain() + 'api/zan' + toQueryStr(obj);
10 | }
11 | export const getDetailUrl = (obj) => {
12 | return getDomain() + 'api/detail' + toQueryStr(obj);
13 | }
14 | export const getTotalUrl = (obj) => {
15 | return getDomain() + 'api/total' + toQueryStr(obj);
16 | }
17 | export const getLastIdUrl = (obj) => {
18 | return getDomain() + 'api/lastId' + toQueryStr(obj);
19 | }
20 |
21 | export const getNextIdUrl = (obj) => {
22 | return getDomain() + 'api/nextId' + toQueryStr(obj);
23 | }
24 | export const getIpUrl = (obj) => {
25 | return getDomain() + 'api/getIp' + toQueryStr(obj);
26 | }
27 | export const postSaveIpUrl = ()=>{
28 | return getDomain() + 'api/saveIp';
29 | }
30 | export const getCreateTimeUrl = ()=>{
31 | return getDomain() + 'api/getCreateTime';
32 | }
33 |
34 | //评论
35 | export const getCommentsUrl = (obj) => {
36 | return getDomain() + 'api/comments' + toQueryStr(obj);
37 | }
38 | export const postCommentUrl = () => {
39 | return getDomain() + 'api/postComment';
40 | }
41 | //留言
42 | export const postUserCommentUrl = () => {
43 | return getDomain() + 'api/postUserComment';
44 | }
45 |
46 | export const getUserCommentUrl = (obj) => {
47 | //获得用户留言
48 | return getDomain() + 'api/getUserComment'+ toQueryStr(obj);
49 | }
50 | export const getQiniuTokenUrl = (obj) => {
51 | //获得七牛云存储的token
52 | return getDomain() + 'api/getQiniuToken'+ toQueryStr(obj);
53 | }
54 |
55 | /*export const getAdminCommentUrl = () => {
56 | //获得用户评论
57 | return getDomain() + 'api/getAdminComment';
58 | }*/
59 |
60 | export const postArticleUrl = () => {
61 | //发表文章
62 | return getDomain() + 'api/postArticle';
63 | }
64 |
65 | export const getLifeUrl = () => {
66 | //生活板块
67 | return getDomain() + 'api/life';
68 | }
69 | export const getViewUrl = () => {
70 | //生活板块
71 | return getDomain() + 'api/getView';
72 | }
73 |
74 | //后台
75 | export const getAdminBlogUrl = (obj) => {
76 | return getDomain() + 'api/getAdminBlog'+ toQueryStr(obj);
77 | }
78 | export const postAdminDetailUrl = () => {
79 | return getDomain() + 'api/postAdminDetail';
80 | }
81 | export const postAdminPasswordUrl = () => {
82 | return getDomain() + 'api/postAdminPassword';
83 | }
84 |
85 | // export const getBlogUrl = domain+'/api/getBlog'
86 | // export const getTotalUrl = domain+'/api/total'
87 | // export const getLifeUrl = domain+'/api/life'
88 | // export const getDetailUrl = domain+'/api/detail'
89 | // export const getLastIdUrl = domain+'/api/lastId'
90 | // export const getNextIdUrl = domain+'/api/nextId'
91 | // export const getCommentsUrl = domain+'/api/comments'
92 | // export const postArticleUrl = domain+'/api/postArticle'
93 | // export const postCommentUrl = domain+'/api/postComment'
94 |
95 |
96 | // export const getAdminBlogUrl = domain + '/api/getAdminBlog'
97 | // export const getAdminCommentsUrl = domain + '/api/getAdminComments'
98 | // export const postAdminDetailUrl = domain + '/api/postAdminDetail'
99 |
100 |
--------------------------------------------------------------------------------
/config/markdown.js:
--------------------------------------------------------------------------------
1 | /** markdownConfig
2 | * 文档 https://marked.js.org/#/USING_ADVANCED.md
3 | * @type {{options: {gfm: boolean, tables: boolean, breaks: boolean, pedantic: boolean, smartLists: boolean, xhtml: boolean, smartypants: boolean, sanitize: (function(*): boolean)}, config: {tabReplace: string, classPrefix: string, languages: string[]}}}
4 | */
5 | export const markdownConfig = {
6 | options:{
7 | gfm: true,
8 | tables: true,
9 | breaks: true,
10 | pedantic: false,
11 | smartLists: true,
12 | xhtml:true,
13 | // If true, use "smart" typographic punctuation for things like quotes and dashes.
14 | smartypants: false,
15 | // If true, sanitize the HTML passed into markdownString with the sanitizer function.
16 | sanitize: true
17 | },
18 | config:{
19 | tabReplace: ' ',
20 | classPrefix: 'hljs-',
21 | languages: ['CSS', 'HTML, XML', 'JavaScript', 'PHP', 'Python', 'Stylus', 'TypeScript', 'Markdown']
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/config/qiniu_config.js:
--------------------------------------------------------------------------------
1 | export const bucket_domin = {
2 | article:'http://images.liuweibo.cn/',
3 | static:'http://images.static.liuweibo.cn'
4 | }
5 |
--------------------------------------------------------------------------------
/lib/with-redux-store.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {initializeStore} from '../store/store'
3 |
4 | const isServer = typeof window === 'undefined'
5 | const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'
6 |
7 | function getOrCreateStore(initialState) {
8 | // Always make a new store if server, otherwise state is shared between requests
9 | if (isServer) {
10 | return initializeStore(initialState)
11 | }
12 |
13 | // Create store if unavailable on the client and set it on the window object
14 | if (!window[__NEXT_REDUX_STORE__]) {
15 | window[__NEXT_REDUX_STORE__] = initializeStore(initialState)
16 | }
17 | return window[__NEXT_REDUX_STORE__]
18 | }
19 |
20 | export default (App) => {
21 | return class AppWithRedux extends React.Component {
22 | static async getInitialProps (appContext) {
23 | // Get or Create the store with `undefined` as initialState
24 | // This allows you to set a custom default initialState
25 | const reduxStore = getOrCreateStore()
26 |
27 | // Provide the store to getInitialProps of pages
28 | appContext.ctx.reduxStore = reduxStore
29 |
30 | let appProps = {}
31 | if (typeof App.getInitialProps === 'function') {
32 | appProps = await App.getInitialProps.call(App, appContext)
33 | }
34 |
35 | return {
36 | ...appProps,
37 | initialReduxState: reduxStore.getState()
38 | }
39 | }
40 |
41 | constructor(props) {
42 | super(props)
43 | this.reduxStore = getOrCreateStore(props.initialReduxState)
44 | }
45 |
46 | render() {
47 | return
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const withLess = require('@zeit/next-less')
3 |
4 | module.exports = withLess(
5 | {
6 | lessLoaderOptions: {
7 | javascriptEnabled: true,
8 | importLoaders: 1,
9 | localIdentName: "[local]___[hash:base64:5]",
10 |
11 | },
12 | distDir: 'build'
13 | }
14 | )
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-blog",
3 | "version": "1.0.0",
4 | "description": "staticCdnMapping",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "node server.js",
8 | "dev:next": "next",
9 | "build": "next build",
10 | "export": "next export",
11 | "start": "cross-env NODE_ENV=production node server.js",
12 | "prd": "cross-env NODE_ENV=production pm2 start server.js",
13 | "test": "echo \"Error: no test specified\" && exit 1"
14 | },
15 | "keywords": [],
16 | "author": "",
17 | "license": "ISC",
18 | "dependencies": {
19 | "@zeit/next-css": "^0.2.0",
20 | "@zeit/next-less": "^1.0.1",
21 | "@zeit/next-sass": "^1.0.1",
22 | "antd": "^3.6.4",
23 | "axios": "^0.18.0",
24 | "babel-polyfill": "^6.26.0",
25 | "child_process": "^1.0.2",
26 | "classnames": "^2.2.6",
27 | "copy-text-to-clipboard": "^1.0.4",
28 | "css-loader": "^0.28.11",
29 | "date-format": "^1.2.0",
30 | "dns": "^0.2.2",
31 | "draft-js": "^0.10.5",
32 | "express": "^4.16.3",
33 | "install": "^0.12.2",
34 | "isomorphic-unfetch": "^2.0.0",
35 | "less": "^3.8.1",
36 | "markdown-navbar": "^1.0.7",
37 | "marked": "^0.5.0",
38 | "moment": "^2.22.2",
39 | "net": "^1.0.2",
40 | "next": "^7.0.0",
41 | "next-redux-wrapper": "^2.0.0",
42 | "npm": "^6.9.0",
43 | "nprogress": "^0.2.0",
44 | "postcss-loader": "^2.1.5",
45 | "public-ip": "^2.4.0",
46 | "qiniu": "^7.2.1",
47 | "qiniu-js": "^2.4.0",
48 | "qs": "^6.5.2",
49 | "rc-queue-anim": "^1.6.11",
50 | "react": "^16.4.1",
51 | "react-copy-to-clipboard": "^5.0.1",
52 | "react-dom": "^16.4.1",
53 | "react-highlight": "^0.12.0",
54 | "react-lazyload": "^2.3.0",
55 | "react-markdown": "^3.4.1",
56 | "react-mde": "^5.6.0",
57 | "react-redux": "^5.0.7",
58 | "react-scroll": "^1.7.10",
59 | "real-ip": "^1.0.0",
60 | "redux": "^4.0.0",
61 | "redux-thunk": "^2.3.0",
62 | "robots.txt": "^1.1.0",
63 | "showdown": "^1.8.6",
64 | "sticky-js": "^1.2.0",
65 | "style-loader": "^0.23.1",
66 | "timeago.js": "^3.0.2",
67 | "viewerjs": "^1.3.2",
68 | "whatwg-fetch": "^2.0.4"
69 | },
70 | "devDependencies": {
71 | "@babel/plugin-proposal-decorators": "^7.1.2",
72 | "babel-plugin-import": "^1.8.0",
73 | "babel-plugin-transform-decorators-legacy": "^1.3.5",
74 | "blueimp-md5": "^2.10.0",
75 | "compression": "^1.7.2",
76 | "cross-env": "^5.2.0",
77 | "fetch": "^1.1.0",
78 | "highlight.js": "^9.12.0",
79 | "html2markdown": "^1.1.0",
80 | "marked": "^0.4.0",
81 | "node-sass": "^4.10.0",
82 | "prismjs": "^1.15.0",
83 | "react-addons-shallow-compare": "^15.6.2",
84 | "react-lowlight": "^2.0.0",
85 | "react-syntax-highlighter": "^8.0.1",
86 | "redux-devtools-extension": "^2.13.5"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import App, {Container} from 'next/app'
2 | import React from 'react'
3 | import {withRouter} from 'next/router'
4 | import withReduxStore from '../lib/with-redux-store'
5 | import {Provider} from 'react-redux'
6 | import Header from '../components/Header';
7 | import Footer from '../components/Footer'
8 | import Guitar from '../components/Guitar'
9 | import 'babel-polyfill'
10 | import '../asserts/styles.less'
11 | import './index.less'
12 | import './markdown.less'
13 | import Router from 'next/router'
14 | import NProgress from 'nprogress'
15 |
16 |
17 | class MyApp extends App {
18 | constructor() {
19 | super()
20 | this.state = {
21 | userAgent: {
22 | userAgent: 'pc'
23 | }
24 | }
25 | }
26 |
27 | componentDidMount() {
28 | const ua = navigator.userAgent;
29 | let userAgent;
30 | if (ua.indexOf("Android") > 0 || ua.indexOf("iPhone") > 0 || ua.indexOf("iPad") > 0) {
31 | //移动端
32 | userAgent = 'mobile'
33 | } else {
34 | userAgent = 'pc'
35 | }
36 |
37 | this.setState({
38 | userAgent: {
39 | userAgent
40 | }
41 | })
42 | }
43 |
44 | render() {
45 | Router.onRouteChangeStart = (url) => {
46 | NProgress.start()
47 | }
48 | Router.onRouteChangeComplete = () => NProgress.done()
49 | Router.onRouteChangeError = () => NProgress.done()
50 | const {Component, pageProps, reduxStore, router: {pathname}} = this.props;
51 | const {userAgent} = this.state;
52 | let myPageProps = {...pageProps, ...userAgent};
53 | return (
54 |
55 |
56 |
57 | {
58 | pathname === '/' || pathname === '/gallery'
59 | ?
60 | ''
61 | :
62 | }
63 |
64 |
65 | {
66 | pathname === '/' || pathname === '/gallery'
67 | ?
68 | ''
69 | :
70 |
71 | }
72 |
73 |
74 |
75 |
110 |
111 | )
112 | }
113 | }
114 |
115 | export default withReduxStore(withRouter(MyApp))
116 |
--------------------------------------------------------------------------------
/pages/_error.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react';
2 | import Head from 'next/head';
3 | import Link from 'next/link';
4 | import {
5 | Layout, Menu, Breadcrumb, Row, Col,
6 | List, Avatar, Icon, Pagination, Alert,
7 | Input, Button, Radio, Tooltip, Spin
8 | } from 'antd'
9 | import {COMMON_TITLE, ABOUT_TXT, MY_BLOG} from '../config/constantsData';
10 | const {Content} = Layout;
11 | class Error extends Component {
12 | static getInitialProps({ res, err }) {
13 | const statusCode = res ? res.statusCode : err ? err.statusCode : null;
14 | return { statusCode }
15 | }
16 |
17 | constructor() {
18 | super();
19 |
20 | }
21 | render(){
22 | return (
23 |
24 |
25 |
error-{COMMON_TITLE}
26 |
27 |
28 |
29 |
30 |
31 |
32 | {this.props.statusCode
33 | ?
34 |
35 | An error {this.props.statusCode} occurred on server
36 |
37 |
38 | 返回首页
39 |
40 |
41 |
42 | : 'An error occurred on client'}
43 |
44 |
45 |
46 |
47 |
59 |
60 |
61 |
62 | );
63 | }
64 | }
65 | export default Error;
--------------------------------------------------------------------------------
/pages/about/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux';
3 | import Head from 'next/head';
4 | import Link from 'next/link';
5 | import 'whatwg-fetch'
6 | import marked from 'marked'
7 | import hljs from 'highlight.js';
8 | import {
9 | Layout, Menu, Breadcrumb, Row, Col,
10 | List, Avatar, Icon, Pagination, Alert,
11 | Input, Button, Radio, Tooltip, Spin, Divider
12 | } from 'antd'
13 | import {COMMON_TITLE, ABOUT_TXT, LINK_ABOUT_ME, commentPlaceHolder} from '../../config/constantsData';
14 | import Comments from '../../components/Comments';
15 | import {getUserCommentUrl,getDetailUrl} from "../../config";
16 | import {POPUP_TIPS} from '../../config/constantTag';
17 | import {fnTextPopup} from '../../until';
18 | import MyLayout from '../../components/MyLayout';
19 | import './index.less'
20 | import { markdownConfig } from '../../config/markdown';
21 |
22 | const {Content} = Layout;
23 | const { options, config } = markdownConfig;
24 | hljs.configure(config);
25 | marked.setOptions({
26 | highlight: (code) => hljs.highlightAuto(code).value,
27 | ...options
28 | });
29 | class About extends Component {
30 | constructor(props) {
31 | super(props);
32 | this.state={
33 | fn:null
34 | }
35 | }
36 | componentDidMount(){
37 | //点击页面出现 富强 民主
38 | let fn=fnTextPopup(POPUP_TIPS);
39 | this.setState({
40 | fn
41 | })
42 | }
43 | componentWillUnmount(){
44 | let {fn} = this.state;
45 | document.documentElement.removeEventListener('click', fn);
46 | }
47 | render() {
48 | const {commentsUserData=[],getUserCommentsData=[],aboutMeData=[],userAgent='pc'} = this.props;
49 | const {content=''} = aboutMeData[0] || {};
50 | let myContent = marked(decodeURIComponent(content));
51 | const dataSourceObj = {
52 | commentsData:getUserCommentsData.length?getUserCommentsData:commentsUserData,
53 | commentTitle: '留言',
54 | commentPlaceHolder,
55 | commentBtnMsg: '提交留言',
56 | commentRow: 20,
57 | isUserSubmit: true
58 | }
59 | return (
60 |
61 |
62 |
{ABOUT_TXT}»{COMMON_TITLE}
63 |
64 |
65 |
66 |
67 |
68 |
69 |

70 |
71 |
72 |
刘伟波
73 |
Web前端工程师
74 |
75 |
76 | 多年来一直从事web前端开发,熟悉h5,vue,react,曾就职与蚂蚁金服无线端技术,
77 | 任web高级开发工程师一职,一直致力与Web技术的研究
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | );
89 | }
90 | }
91 | About.getInitialProps = async function (context) {
92 | try {
93 | //评论
94 | const comments = await fetch(getUserCommentUrl({hasToken:'no'}))
95 | const commentsUserData = await comments.json()
96 | //关于自己 id为1
97 | let queryStrObj = {id:1};
98 | const aboutMe = await fetch(getDetailUrl(queryStrObj))
99 | const aboutMeData = await aboutMe.json()
100 |
101 |
102 | return {commentsUserData,aboutMeData}
103 | } catch (error) {
104 | return {};
105 | }
106 | }
107 | const mapStateToProps = state => {
108 | const {getUserCommentsData} = state;
109 | return {getUserCommentsData};
110 | }
111 | export default connect(mapStateToProps)(About);
112 |
--------------------------------------------------------------------------------
/pages/about/index.less:
--------------------------------------------------------------------------------
1 |
2 | //手机端样式
3 | .about.is-mobile{
4 | .about-me{
5 |
6 | & ul{
7 | margin-left: 3em;
8 | }
9 | &>p{
10 | padding:0 1em;
11 | }
12 | h2{
13 | margin: 20px 0;
14 | }
15 | }
16 | .comment-wrapper{
17 | h2{
18 | text-indent: 1em;
19 | }
20 | }
21 | }
22 | .about{
23 | position:relative;
24 | margin: 0 auto;
25 | box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
26 | background: #fff;
27 | color: #3d5064;
28 | &.is-mobile{
29 | position:static;
30 | overflow: hidden;
31 | .cv-wrapper{
32 | top:40px;
33 | left: 50%;
34 | transform: translateX(-50%);
35 | }
36 | .about-me{
37 | padding-top: 280px;
38 | }
39 | }
40 |
41 | .cv-wrapper{
42 | position: absolute;
43 | right: 50px;
44 | top: 150px;
45 | width: 216px;
46 | height: 248px;
47 | text-align: center;
48 | background: rgba(0,0,0,.1);
49 | box-shadow: 0 4px 8px 0 rgba(7,17,27,.05);
50 | border-radius: 12px;
51 | transition: all .3s;
52 | cursor:pointer;
53 | .img{
54 | position: absolute;
55 | top: -16.6px;
56 | left: 50%;
57 | width: 96px;
58 | height: 96px;
59 | border-radius: 50%;
60 | transform: translateX(-50%);
61 | transition: all .3s;
62 | overflow: hidden;
63 | img{
64 | position: absolute;
65 | left: 0;
66 | width: 96px;
67 | animation: top-move 16s ease-in infinite alternate;
68 | transition: all .3s;
69 | }
70 | &:before{
71 | position: absolute;
72 | top: -125px;
73 | left: 44px;
74 | content: '';
75 | display: block;
76 | width: 2px;
77 | height: 184px;
78 | background-color: #fff;
79 | transform: rotate(-45deg);
80 | z-index: 10;
81 | box-shadow: 2px 0px 3px rgba(255,255,255,0.8), -2px 0px 3px rgba(255,255,255,0.8);
82 | animation: light-show 5s ease infinite;
83 | }
84 | }
85 | .info{
86 | .title{
87 | font-size: 16px;
88 | line-height: 24px;
89 | margin-top: 92px;
90 | white-space: nowrap;
91 | color: #07111b;
92 | font-weight: 700;
93 | }
94 | .remark{
95 | font-size: 12px;
96 | margin-bottom: 12px;
97 | color: #4D555D;
98 | height: 24px;
99 | line-height: 24px;
100 | }
101 | }
102 | .content{
103 | position:relative;
104 | top:0;
105 | font-size: 12px;
106 | line-height: 24px;
107 | color: #4D555D;
108 | height: 72px;
109 | padding: 0 36px;
110 | text-align: justify;
111 | font-weight: 400;
112 | word-wrap: break-word;
113 | word-break: break-all;
114 | overflow: hidden;
115 | transition: all .3s;
116 | }
117 | &:hover{
118 | background: rgba(0,0,0,0);
119 | transition: all .3s;
120 | .img{
121 | width:60px;
122 | height:60px;
123 | transition: all .3s;
124 | img{
125 | width:60px;
126 | transition: all .3s;
127 | }
128 | }
129 | .info{
130 | opacity:0;
131 | transition: all .3s;
132 | }
133 | .content{
134 | top:-82px;
135 | transition: all .3s;
136 | height:168px;
137 | }
138 | }
139 | }
140 | .about-me{
141 | padding: 20px;
142 | h2:not(:first-child){
143 | margin-top: 38px !important;
144 | }
145 | h2{
146 | margin: 20px;
147 | padding-bottom: 20px;
148 | text-align: center;
149 | font-weight:bold;
150 | color: #3d5064;
151 | font-size:2em;
152 | border-bottom: 1px dashed #3d5064;
153 | }
154 | &>p{
155 | padding:0 40px;
156 | font-size: 1.5em;
157 | font-weight: 700;
158 | }
159 | .mid{
160 | text-indent: 5.5rem;
161 | .title{
162 | font-weight: 700;
163 | color: #3d5064;
164 | font-size: 20px;
165 | }
166 | .txt{
167 | font-weight: 700;
168 | color: #3d5064;
169 | }
170 | }
171 | ul{
172 | margin: 1.5em 0 1.5em 5em;
173 | padding-left: 0;
174 | font-weight: 700;
175 | color: #3d5064;
176 | }
177 | li{
178 | width:80%;
179 | display: list-item;
180 | margin:.3em 0;
181 | list-style: disc;
182 | }
183 | }
184 |
185 | .comment-wrapper{
186 | h2{
187 | text-indent: 5em;
188 | }
189 | }
190 | }
191 | @keyframes top-move {
192 | 0%{
193 | top: 0;
194 | }
195 | 100%{
196 | top: -25px;
197 | }
198 | }
199 | @keyframes light-show {
200 | 0%{
201 | top: -125px;
202 | }
203 | 30%{
204 | top: 125px;
205 | }
206 | 100%{
207 | top: 1250px;
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/pages/admin/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import Head from 'next/head'
4 | import md5 from 'blueimp-md5';
5 |
6 |
7 | import {
8 | Form, Button, Checkbox,
9 | Layout, Menu, Breadcrumb, Row, Col, Pagination, Input, Tabs, Table, List, Avatar, Icon, message, Modal
10 | } from 'antd';
11 |
12 | import {
13 | getAdminBlogUrl,
14 | getTotalUrl,
15 | postAdminPasswordUrl,
16 | getUserCommentUrl,
17 | postCommentUrl,
18 | getIpUrl,
19 | getViewUrl
20 | } from '../../config';
21 | import {
22 | getAdminBlogList,
23 | postAdminPassword,
24 | postComments,
25 | getIpList,
26 | getCommentsUserList,
27 | getViewList
28 | } from '../../store/actions';
29 | import {ALL, pageNum, TITLE, ADMIN_TXT, COMMON_TITLE} from "../../config/constantsData";
30 | import MyLayout from '../../components/MyLayout';
31 | import {REQ_ACTION} from './req-action';
32 | import {TABLE_DATA} from './table-data';
33 | import {getView} from '../../until/getiView';
34 |
35 | const FormItem = Form.Item;
36 | const TabPane = Tabs.TabPane;
37 | const {Content} = Layout;
38 | const Search = Input.Search;
39 |
40 | class Admin extends Component {
41 | constructor() {
42 | super()
43 | this.state = {
44 | pageNum,
45 | currentPage: 1,
46 | inputVal: '',
47 | isLogin: false,
48 | isLoading: false,
49 | tabKey: '1',
50 | pageSize: 1,
51 | defaultConfirmObj: {
52 | title: 'Are you sure delete this article?',
53 | content: 'Some descriptions',
54 | okText: 'Yes',
55 | okType: 'danger',
56 | cancelText: 'No',
57 | }
58 | }
59 | }
60 |
61 | componentDidMount() {
62 | const {dispatch} = this.props;
63 | const {password} = sessionStorage
64 | const queryStringObj = {
65 | type: ALL,
66 | num: 1,
67 | pageNum,
68 | token: password
69 | }
70 | getAdminBlogList(dispatch, getAdminBlogUrl(queryStringObj))
71 | postComments(dispatch, postCommentUrl(), queryStringObj)
72 | getIpList(dispatch, getIpUrl(queryStringObj))
73 | getCommentsUserList(dispatch, getUserCommentUrl(queryStringObj))
74 | getViewList(dispatch, getViewUrl())
75 |
76 | window.onscroll = () => {
77 | REQ_ACTION.scrollBTMLoading(this)
78 | }
79 | window.onresize = () => {
80 | REQ_ACTION.scrollBTMLoading(this)
81 | }
82 | }
83 |
84 | componentWillUnmount() {
85 | window.onscroll = null;
86 | window.onresize = null;
87 | }
88 |
89 |
90 | itemRender(current, type, originalElement) {
91 | if (type === 'prev') {
92 | return Previous;
93 | } else if (type === 'next') {
94 | return Next;
95 | }
96 | return originalElement;
97 | }
98 |
99 |
100 | onTabChangeCallback(key) {
101 | this.setState({
102 | tabKey: key
103 | })
104 | }
105 | //登录
106 | handleSubmit = (e) => {
107 | const {dispatch} = this.props;
108 | e.preventDefault();
109 | this.props.form.validateFields((err, values) => {
110 | if (!err) {
111 | const {password} = values;
112 | postAdminPassword(dispatch, postAdminPasswordUrl(), {password: md5(password)}).then(res => {
113 | const {postAdminPasswordData = []} = res;
114 | if (!postAdminPasswordData.length) {
115 | message.warning('密码不正确,重新输入!!!')
116 | return;
117 | }
118 | const {password} = postAdminPasswordData[0];
119 | sessionStorage.setItem('password', password)
120 | this.setState({
121 | isLogin: true
122 | })
123 | location.reload()
124 | })
125 | }
126 | });
127 | }
128 |
129 |
130 |
131 | render() {
132 | function onChange(pagination, filters, sorter) {
133 | }
134 |
135 | function onClick(pagination, filters, sorter) {
136 | }
137 |
138 | let {adminBlogData = [], totalPageData = [], searchData = [],
139 | viewListData={},
140 | getCommentsUserData: commentsUserData = [], getUserCommentsData = [],
141 | getCommentsData = [], ipListData = []} = this.props;
142 | //昨日今日浏览记录
143 | const {curView,yesView} = getView(viewListData)
144 | //浏览记录
145 | const {ipColumns,ipData} = TABLE_DATA.ipData(ipListData)
146 | //文章
147 | const {columns,data} = TABLE_DATA.articleData(searchData,adminBlogData,this)
148 | //留言
149 | const {columnsUserComments,dataCommentsUserData} = TABLE_DATA.articleUserCommentData(getUserCommentsData,commentsUserData,this)
150 |
151 | //评论
152 | const {columnsAdminComments,dataAdminCommentsData} = TABLE_DATA.articleComment(getCommentsData,this)
153 |
154 |
155 | //分页
156 | const {total} = totalPageData[0] || {};
157 | const {postAdminPasswordData = []} = this.props;
158 | const {getFieldDecorator} = this.props.form;
159 | const {isLoading} = this.state;
160 | return (
161 |
162 |
163 |
{ADMIN_TXT}»{COMMON_TITLE}
164 |
165 |
166 |
167 | {
168 | postAdminPasswordData.length ?
169 |
170 |
171 |
173 |
174 |
175 | 昨日访问量:{yesView} 今日访问量:{curView}
176 |
177 |
178 |
179 | {
187 | return {
188 | onClick: () => {
189 | }, // 点击行
190 | onMouseEnter: () => {
191 | }, // 鼠标移入行
192 | };
193 | }}
194 | />
195 |
199 |
200 |
201 | {
209 | return {
210 | onClick: () => {
211 | }, // 点击行
212 | onMouseEnter: () => {
213 | }, // 鼠标移入行
214 | };
215 | }}
216 | />
217 |
218 |
219 |
220 | {
228 | return {
229 | onClick: () => {
230 | }, // 点击行
231 | onMouseEnter: () => {
232 | }, // 鼠标移入行
233 | };
234 | }}
235 | />
236 |
237 |
238 |
239 | {
247 | return {
248 | onClick: () => {
249 | }, // 点击行
250 | onMouseEnter: () => {
251 | }, // 鼠标移入行
252 | };
253 | }}
254 | />
255 |
256 |
257 | {
258 | isLoading ? loading……
259 | : ''
260 | }
261 |
262 |
263 |
264 |
265 | :
266 |
267 |
289 |
290 |
291 | }
292 |
293 |
294 |
295 | )
296 | }
297 | }
298 |
299 | Admin.getInitialProps = async function () {
300 | try {
301 | let queryTotalString = {type: ALL};
302 | const totalPage = await fetch(getTotalUrl(queryTotalString))
303 | const totalPageData = await totalPage.json()
304 |
305 | return {totalPageData}
306 | } catch (error) {
307 | return {};
308 | }
309 | }
310 | const mapStateToProps = state => {
311 | const {adminBlogData, searchData, postAdminPasswordData, getUserCommentsData, getCommentsData, ipListData, getCommentsUserData,viewListData} = state
312 | return {
313 | adminBlogData,
314 | searchData,
315 | postAdminPasswordData,
316 | getUserCommentsData,
317 | getCommentsData,
318 | ipListData,
319 | getCommentsUserData,
320 | viewListData
321 | };
322 | }
323 | const WrappedNormalLoginForm = Form.create()(Admin);
324 | export default connect(mapStateToProps)(WrappedNormalLoginForm)
325 |
--------------------------------------------------------------------------------
/pages/admin/req-action.js:
--------------------------------------------------------------------------------
1 | import {
2 | Form, Button, Checkbox,
3 | Layout, Menu, Breadcrumb, Row, Col, Pagination, Input, Tabs, Table, List, Avatar, Icon, message, Modal
4 | } from 'antd';
5 | import {getAdminBlogList, getIpList, getSearchList, postComments, postUserComments} from "../../store/actions";
6 | import {getAdminBlogUrl, getBlogUrl, getIpUrl, postCommentUrl, postUserCommentUrl} from "../../config";
7 | import {ALL, pageNum, TITLE} from "../../config/constantsData";
8 |
9 | const confirm = Modal.confirm;
10 | export const REQ_ACTION = {
11 | //搜索文章
12 | onSearch(_this,val) {
13 | _this.setState({
14 | inputVal: val,
15 | currentPage: 1
16 | })
17 |
18 | const {dispatch} = _this.props;
19 | let queryStringObj;
20 | _this.setState({
21 | keyWard: val
22 | })
23 | if (val) {
24 | queryStringObj = {
25 | type: TITLE,
26 | num: 1,
27 | pageNum,
28 | wd: val
29 | }
30 | } else {
31 | queryStringObj = {
32 | type: ALL,
33 | num: 1,
34 | pageNum
35 | }
36 | }
37 |
38 | getSearchList(dispatch, getBlogUrl(queryStringObj))
39 | },
40 |
41 | //分页变化查询
42 | onChange(_this,page, pageSize) {
43 | console.log(arguments)
44 | const {dispatch} = _this.props;
45 | const {password} = sessionStorage;
46 |
47 | const queryStringObj = {
48 | type: ALL,
49 | num: page,
50 | pageNum,
51 | token: password
52 | }
53 | getAdminBlogList(dispatch, getAdminBlogUrl(queryStringObj))
54 | },
55 | //删除文章
56 | handleDelArticle(_this,id) {
57 | const {dispatch} = _this.props;
58 | const {defaultConfirmObj} = _this.state;
59 | const confirmObj = {
60 | title: 'Are you sure delete this article?',
61 | content: 'Some descriptions',
62 | okText: 'Yes',
63 | okType: 'danger',
64 | cancelText: 'No',
65 | }
66 | const {password} = sessionStorage;
67 | const queryStringObj = {
68 | type: 'del',
69 | num: id,
70 | token: password
71 | };
72 | confirm({
73 | ...confirmObj,
74 | ...defaultConfirmObj,
75 | onOk() {
76 | getAdminBlogList(dispatch, getAdminBlogUrl(queryStringObj)).then(res => {
77 | const {adminBlogData = []} = res;
78 | if (!adminBlogData.length) {
79 | message.warning('您可能没权限')
80 | return;
81 | }
82 | if (res) {
83 | message.success(`id为${id}的文章删除成功`)
84 | } else {
85 | message.error('删除失败')
86 | }
87 | })
88 | },
89 | onCancel() {
90 | },
91 | });
92 |
93 | },
94 |
95 | //删除留言
96 | handleDelUserComment(_this,id) {
97 | const {dispatch} = _this.props;
98 | const {defaultConfirmObj} = _this.state;
99 | const {password} = sessionStorage;
100 | const queryStringObj = {
101 | type: 'del',
102 | num: id,
103 | token: password
104 | };
105 | confirm({
106 | ...defaultConfirmObj,
107 | onOk() {
108 | postUserComments(dispatch, postUserCommentUrl(), queryStringObj).then(res => {
109 | const {getUserCommentsData} = res;
110 | if (!getUserCommentsData.length) {
111 | message.warning('您可能没权限')
112 | return;
113 | }
114 | if (res) {
115 | message.success(`id为${id}的用户留言删除成功`)
116 | } else {
117 | message.error('删除失败')
118 | }
119 | })
120 | },
121 | onCancel() {
122 | },
123 | });
124 |
125 | },
126 | //删除评论
127 | handleDelAdminComment(_this,id) {
128 | const {dispatch} = _this.props;
129 | const {defaultConfirmObj} = _this.state;
130 | const {password} = sessionStorage;
131 | const queryStringObj = {
132 | type: 'del',
133 | delNum: id,
134 | token: password
135 | };
136 | confirm({
137 | ...defaultConfirmObj,
138 | onOk() {
139 | postComments(dispatch, postCommentUrl(), queryStringObj).then(res => {
140 | const {getCommentsData} = res;
141 | if (!getCommentsData.length) {
142 | message.warning('您可能没权限')
143 | return;
144 | }
145 | if (res) {
146 | message.success(`id为${id}的用户评论删除成功`)
147 | } else {
148 | message.error('删除失败')
149 | }
150 | })
151 | },
152 | onCancel() {
153 | },
154 | });
155 | },
156 |
157 | //上拉加载数据
158 | scrollBTMLoading(_this) {
159 | const {dispatch} = _this.props;
160 | let {pageSize: num, tabKey} = _this.state
161 | const {password} = sessionStorage;
162 | const queryStringObj = {
163 | type: ALL,
164 | num,
165 | pageNum,
166 | token: password
167 | }
168 | let footerDom = document.getElementsByClassName('footer-content')[0];
169 | let {innerHeight: windowHeight} = window;
170 | let {bottom} = footerDom.getBoundingClientRect();
171 | if (bottom - windowHeight < 1 && tabKey !== '1') {
172 | console.log('该请求数据了')
173 | let newNum = ++num;
174 | _this.setState({
175 | isLoading: true,
176 | pageSize: newNum
177 | })
178 | let newQueryStringObj = {...queryStringObj, num: newNum};
179 | if (tabKey === '4') {
180 | //浏览记录分页
181 | getIpList(dispatch, getIpUrl(newQueryStringObj)).then(res => {
182 | if (res) {
183 | _this.setState({
184 | isLoading: false
185 | })
186 | }
187 | })
188 | } else if (tabKey === '3') {
189 | //评论管理
190 | postComments(dispatch, postCommentUrl(), newQueryStringObj).then(res => {
191 | if (res) {
192 | _this.setState({
193 | isLoading: false
194 | })
195 | }
196 | })
197 | }
198 | }
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/pages/admin/setTable.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import {REQ_ACTION} from './req-action';
3 |
4 | export const SET_TABLE = {
5 | //设置文章评论
6 | setCommentWidth(v, _this) {
7 | let extend = {};
8 | if (v === '操作') {
9 | extend = {
10 | width: 80, fixed: 'right', render: (text, row, index) =>
11 | 删除
12 | }
13 | }
14 | if (v === 'a_id') {
15 | extend = {
16 | width: 50, render: (text, row, index) =>
17 |
18 | {text}
19 |
20 | }
21 | }
22 | if (v === 'id') {
23 | extend = {width: 80, fixed: 'left'}
24 | }
25 | if (v === 'msg') {
26 | extend = {width: 300}
27 | }
28 | if (v === 'user') {
29 | extend = {width: 80};
30 | }
31 | return {title: v, dataIndex: v, ...extend};
32 | },
33 | //设置用户留言
34 | setUserCommentWidth(v, _this) {
35 | let extend = {};
36 | if (v === 'id') {
37 | extend = {
38 | width: 80, fixed: 'left'
39 | }
40 | }
41 | if (v === '操作') {
42 | extend = {
43 | width: 80, fixed: 'right', render: (text, row, index) =>
44 | 删除
45 | }
46 | }
47 | if (v === 'address' || v === 'ip' || v === 'real_ip' || v === 'website') {
48 | extend = {width: 100}
49 | }
50 | return {title: v, dataIndex: v, ...extend};
51 | },
52 | //设置浏览记录
53 | setHistoryRecode(v, _this) {
54 | let extend = {};
55 | if (v === 'id') {
56 | extend = {
57 | width: 80, fixed: 'left'
58 | }
59 | }
60 | if (v === 'account') {
61 | extend = {
62 | fixed: 'right'
63 | }
64 | }
65 | return {title: v, dataIndex: v, ...extend};
66 | },
67 | //设置文章增删改查
68 | setArticle(v, _this) {
69 | let extend = {};
70 | if (v === 'id') {
71 | extend = {width: 80, fixed: 'left'}
72 | }
73 | if (v === 'title') {
74 | extend = {
75 | render: (text, row, index) =>
76 |
77 | {text}
78 |
79 | }
80 | } else {
81 | if (v === '操作') {
82 | extend = {
83 | width: 80, fixed: 'right', render: (text, row, index) =>
84 | 删除
85 | }
86 | }
87 | }
88 | return {title: v, dataIndex: v, ...extend};
89 | },
90 | }
91 |
--------------------------------------------------------------------------------
/pages/admin/table-data.js:
--------------------------------------------------------------------------------
1 | //浏览记录
2 | import {formatTime} from "../../until";
3 | import {SET_TABLE} from "./setTable";
4 |
5 | export const TABLE_DATA = {
6 | ipData(ipListData){
7 | const ipKeys = ipListData.map(v => ([...Object.keys(v)]));
8 | const ipColumns = ipKeys && ipKeys[0] ? ipKeys[0].map(v => {
9 | return SET_TABLE.setHistoryRecode(v,this);
10 | }) : [];
11 | const ipData = ipListData.map((v, i) => Object.assign({}, v, {key: i}, {createTime: formatTime(v.createTime)}))
12 | return {
13 | ipColumns,ipData
14 | };
15 | },
16 | articleData(searchData,adminBlogData,_this){
17 | if (searchData.length) {
18 | adminBlogData = searchData
19 | }
20 | const keys = adminBlogData.map(v => ([...Object.keys(v), '操作']));
21 | const columns = keys && keys[0] ? keys[0].map(v => {
22 | return SET_TABLE.setArticle(v,_this);
23 | }) : [];
24 | const data = adminBlogData.map((v, i) => Object.assign({}, v, {key: i}, {createTime: formatTime(v.createTime)}))
25 | return {columns,data};
26 | },
27 | articleUserCommentData(getUserCommentsData,commentsUserData,_this){
28 |
29 | if (getUserCommentsData.length) {
30 | commentsUserData = getUserCommentsData
31 | }
32 | const keysUserComments = commentsUserData.map(v => ([...Object.keys(v), '操作']));
33 | const columnsUserComments = keysUserComments && keysUserComments[0] ? keysUserComments[0].map(v => {
34 | return SET_TABLE.setUserCommentWidth(v,_this);
35 | }) : [];
36 | const dataCommentsUserData = commentsUserData.map((v, i) => Object.assign({}, v, {key: i}, {createTime: formatTime(v.createTime)}))
37 | return {columnsUserComments,dataCommentsUserData};
38 | },
39 | articleComment(getCommentsData,_this){
40 | const keysAdminComments = getCommentsData.map(v => ([...Object.keys(v), '操作']));
41 | const columnsAdminComments = keysAdminComments && keysAdminComments[0] ? keysAdminComments[0].map(v => {
42 | return SET_TABLE.setCommentWidth(v,_this);
43 | }) : [];
44 | const dataAdminCommentsData = getCommentsData.map(
45 | (v, i) => Object.assign({}, v, {key: i}, {createTime: formatTime(v.createTime)})
46 | )
47 | return {columnsAdminComments,dataAdminCommentsData};
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/pages/adminDetail/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux'
3 | import {
4 | Layout, Menu, Breadcrumb, Row, Col, BackTop, Card, Form,
5 | Input, Tooltip, Cascader, Select, Checkbox, Button,
6 | AutoComplete, List, Avatar, Icon, Divider
7 | } from 'antd';
8 | import 'whatwg-fetch'
9 | import Head from 'next/head';
10 |
11 | import ArticleTitle from '../../components/ArticleTitle';
12 | import EditArticle from '../../components/EditArticle';
13 | import {COMMON_TITLE} from '../../config/constantsData';
14 | import {getDetailUrl} from '../../config';
15 | import {getArticleInfo} from '../../until';
16 | import MyLayout from '../../components/MyLayout';
17 |
18 | const {Content} = Layout;
19 |
20 | class AdminDetail extends Component {
21 | constructor(props) {
22 | super(props);
23 | this.state={
24 | autoCompleteResult:[1,2],
25 | articleID:''
26 | }
27 | }
28 | componentWillMount(){
29 | const {adminBlogDetailData = []} = this.props;
30 | let {id:articleID} = adminBlogDetailData[0] || {};
31 | this.setState({
32 | articleID
33 | })
34 | }
35 | handleWebsiteChange = (value) => {
36 | let autoCompleteResult;
37 | if (!value) {
38 | autoCompleteResult = [];
39 | } else {
40 | autoCompleteResult = ['.com', '.org', '.net'].map(domain => `${value}${domain}`);
41 | }
42 | this.setState({autoCompleteResult});
43 | }
44 | handleSubmit = (e) => {
45 | e.preventDefault();
46 | const {dispatch} = this.props;
47 | let {articleID:id} = this.state;
48 | if(!id){
49 | return;
50 | }
51 | this.props.form.validateFieldsAndScroll((err, values) => {
52 | if (!err) {
53 | const {comment,email,nickname,website} = values;
54 |
55 | }
56 | });
57 | }
58 | render() {
59 | //接口
60 | let {adminBlogDetailData=[]} = this.props;
61 | let {articleID=''} = this.state;
62 | const objArticleInfo = getArticleInfo(adminBlogDetailData)
63 | const {title='',url='',short='',type=''} = objArticleInfo
64 | adminBlogDetailData=adminBlogDetailData[0] || {}
65 |
66 |
67 | return (
68 |
69 |
70 |
{title}»{COMMON_TITLE}
71 |
72 |
73 |
74 |
78 |
79 |
80 |
81 |
82 | );
83 | }
84 | }
85 | AdminDetail.getInitialProps = async function (context) {
86 | try {
87 | const {id} = context.query
88 | let queryStrObj = {id};
89 | const adminBlogDetail = await fetch(getDetailUrl(queryStrObj))
90 | const adminBlogDetailData = await adminBlogDetail.json()
91 |
92 |
93 | return {adminBlogDetailData}
94 | } catch (error) {
95 | return {};
96 | }
97 | }
98 | const WrappedRegistrationForm = Form.create()(AdminDetail);
99 | export default WrappedRegistrationForm;
100 |
--------------------------------------------------------------------------------
/pages/blog/README.md:
--------------------------------------------------------------------------------
1 | ## type 传参
2 | - handSearch 手动搜索 type转化为 title or article
3 | - timeRange|11.11 点击文章归档 type 为 timeRange|11.11
4 | - 全部文章 type为 all ,其他为相应标签
5 |
--------------------------------------------------------------------------------
/pages/blog/index.less:
--------------------------------------------------------------------------------
1 | .blog-right{
2 | margin-bottom: 20px;
3 | &>div{
4 | padding: 24px 24px 0;
5 | }
6 | .tag-container{
7 | min-height: 152px;
8 | }
9 | .title{
10 | font-size: 16px;
11 | line-height: 24px;
12 | font-weight: bold;
13 | -webkit-font-smoothing:antialiased;
14 | }
15 | .tag{
16 | margin-top: 6px;
17 | }
18 | .time-active{
19 | cursor: pointer;
20 | &.active{
21 | color: #017E66;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pages/blog/searchType.js:
--------------------------------------------------------------------------------
1 | const typeArr = ['title', 'article', 'handSearch'];
2 | export const getType = (type, searchType) => {
3 | if (type === 'handSearch') {
4 | return searchType;
5 | }
6 | if (type === '全部文章') {
7 | return 'all';
8 | }
9 | return type;
10 | }
11 | export const isHighLightAll = (type) => {
12 | return type === 'all';
13 | }
14 | export const tagHighLight = (type) => {
15 | if(!type.startsWith('timeRange') && typeArr.indexOf(type) === -1){
16 | return type;
17 | }
18 | return false;
19 | }
20 | export const getTimeIndex = (type) => {
21 | if (!type.startsWith('timeRange')) {
22 | return -1;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pages/detail/index.less:
--------------------------------------------------------------------------------
1 | .detail{
2 | word-break: break-all;
3 | img{
4 | max-width: 100%;
5 | }
6 | }
7 |
8 | .new-detail,.old-detail{
9 | /*img{
10 | transition: .5s all linear;
11 | &:hover{
12 | transition: .5s all linear;
13 | box-shadow: 0 8px 30px 0 rgba(7,17,27,.2);
14 | transform: translate3D(-3px,-3px ,0);
15 | }
16 | }*/
17 | pre{
18 | position: relative;
19 | max-height: 35em;
20 | overflow: auto;
21 | .copy-code{
22 | position: absolute;
23 | top: 0;
24 | right: 4px;
25 | width: 20px;
26 | height: 20px;
27 | border: none;
28 | outline: none;
29 | color: #abb2bf;
30 | font-size: 12px;
31 | background: url("/static/copy.png") no-repeat;
32 | //background-color: transparent;
33 | background-size: 20px;
34 | z-index: 3;
35 | cursor: pointer;
36 | }
37 | }
38 | }
39 | .new-detail{
40 | ::-webkit-scrollbar {/*滚动条整体样式*/
41 | width: 6px; /*高宽分别对应横竖滚动条的尺寸*/
42 | }
43 | ::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
44 | background: #282c34;
45 | }
46 | ::-webkit-scrollbar-track {/*滚动条里面轨道*/
47 | background: #393c42;
48 | }
49 | img{
50 | position: relative;
51 | transform: translateX(-50%);
52 | left: 50%;
53 | }
54 | }
55 | .zan-wrapper{
56 | margin-top: 30px;
57 | .split-line{
58 | margin: 0 8px;
59 | color: #a79494;
60 | }
61 | }
62 | .left-icon-wrapper{
63 | position: sticky !important;
64 | top: 140px;
65 | text-align: center;
66 | margin-top: 24px;
67 | .remark-num{
68 | margin-bottom: 2px;
69 | }
70 | .my-button-icon{
71 | padding: 0 11px;
72 | font-size: 22px;
73 | }
74 | .icon{
75 | display: block;
76 | width: 22px;
77 | margin: 10px auto 0 auto;
78 | cursor: pointer;
79 | font-size: 22px;
80 | &:hover{
81 | color: #EC0117;
82 | }
83 | }
84 | }
85 |
86 | #read-nprogress {
87 | background: #29d;
88 | position: fixed;
89 | z-index: 1031;
90 | top: 0;
91 | left: 0;
92 | transition: all .3s ease-in;
93 | height: 2px;
94 | }
95 | .recommend-post-ul{
96 | margin-left: 30px;
97 | h3{
98 | margin-bottom: 20px;
99 | }
100 | li{
101 | font-size: 14px;
102 | margin-bottom: 3px;
103 | line-height: 28px;
104 | list-style: disc;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/pages/detail/markdown-navbar.less:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | .markdown-navigation {
5 | font-size: 14px;
6 | font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Helvetica", "Arial", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
7 | width: 100%;
8 | margin-left: 20px;
9 | overflow-y: auto;
10 | max-height: 600px;
11 | border-left: 1px solid #f5f5f5;
12 | }
13 | .markdown-navigation .title-anchor {
14 | display: block;
15 | color: #bbb;
16 | transition: all 0.2s;
17 | margin: 0.8em 0;
18 | font-weight: lighter;
19 | line-height: 2em;
20 | padding-right: 1.8em;
21 | }
22 |
23 | .markdown-navigation .title-anchor:hover,
24 | .markdown-navigation .title-anchor.active {
25 | background-color: #f8f8f8;
26 | text-decoration: inherit;
27 | }
28 |
29 | .markdown-navigation .title-anchor.active {
30 | color: #007fff;
31 | }
32 |
33 | .markdown-navigation .title-anchor small {
34 | margin: 0 0.8em;
35 | }
36 |
37 | .markdown-navigation .title-level1 {
38 | color: #000;
39 | font-size: 1.2em;
40 | padding-left: 1em;
41 | font-weight: normal;
42 | }
43 |
44 | .markdown-navigation .title-level2 {
45 | color: #333;
46 | font-size: 1em;
47 | padding-left: 1em;
48 | font-weight: normal;
49 | }
50 |
51 | .markdown-navigation .title-level3 {
52 | color: #666;
53 | font-size: 0.8em;
54 | padding-left: 3em;
55 | font-weight: normal;
56 | }
57 |
58 | .markdown-navigation .title-level4 {
59 | color: #999;
60 | font-size: 0.72em;
61 | padding-left: 5em;
62 | }
63 |
64 | .markdown-navigation .title-level5 {
65 | color: #aaa;
66 | font-size: 0.72em;
67 | padding-left: 7em;
68 | }
69 |
70 | .markdown-navigation .title-level6 {
71 | color: #bbb;
72 | font-size: 0.72em;
73 | padding-left: 9em;
74 | }
75 |
--------------------------------------------------------------------------------
/pages/detail/pop-tips.less:
--------------------------------------------------------------------------------
1 | .text-popup {
2 | display: inline-block;
3 | width: 240px;
4 | animation: textPopup 5s;
5 | color: red;
6 | user-select: none;
7 | position: absolute;
8 | z-index: 99;
9 | text-align: center;
10 | //background-image: linear-gradient(to right, red, green, red);
11 | -webkit-background-clip: text;
12 | -webkit-text-fill-color: transparent;
13 | -webkit-background-size: 200% 100%;
14 | }
15 | @keyframes textPopup {
16 | 0%, 100% {
17 | opacity: 0;
18 | background-position: 0 0;
19 | }
20 | 5% {
21 | opacity: 1;
22 | }
23 | 100% {
24 | transform: translateY(-50px);
25 | background-position: -100% 0;
26 | }
27 | }
--------------------------------------------------------------------------------
/pages/detail/util.js:
--------------------------------------------------------------------------------
1 | import { message } from 'antd';
2 | /**
3 | * 设置图片宽高 最大宽或者高 为300px
4 | * @param len
5 | */
6 | export function untilMaxWidthOrHeight(len = 300) {
7 | const imgList=document.querySelectorAll('.detail img');
8 | for (let i = 0; i < imgList.length; i++) {
9 | if(imgList[i].width>imgList[i].height){
10 | imgList[i].style.height=len+'px'
11 | imgList[i].style.width='auto'
12 | }else {
13 | imgList[i].style.width=len+'px'
14 | imgList[i].style.height='auto'
15 | }
16 | }
17 | }
18 |
19 | /**
20 | * 点击复制代码到剪贴板
21 | */
22 | export function clickCopyCode () {
23 | let btn = document.createElement('button');
24 | btn.className = 'copy-code'
25 | btn.title = '点击复制代码'
26 | btn.innerText = ''
27 | let preList = document.getElementsByTagName('pre')
28 | for (let i=0;iimg{height:auto;margin:15px auto;max-width:90%!important;width:auto}.viewer-footer{bottom:0;left:0;overflow:hidden;position:absolute;right:0;text-align:center}.viewer-navbar{background-color:rgba(0,0,0,.5);overflow:hidden}.viewer-list{-webkit-box-sizing:content-box;box-sizing:content-box;height:50px;margin:0;overflow:hidden;padding:1px 0}.viewer-list>li{color:transparent;cursor:pointer;float:left;font-size:0;height:50px;line-height:0;opacity:.5;overflow:hidden;-webkit-transition:opacity .15s;transition:opacity .15s;width:30px}.viewer-list>li:hover{opacity:.75}.viewer-list>li+li{margin-left:1px}.viewer-list>.viewer-loading{position:relative}.viewer-list>.viewer-loading:after{border-width:2px;height:20px;margin-left:-10px;margin-top:-10px;width:20px}.viewer-list>.viewer-active,.viewer-list>.viewer-active:hover{opacity:1}.viewer-player{background-color:#000;bottom:0;cursor:none;display:none;right:0}.viewer-player,.viewer-player>img{left:0;position:absolute;top:0}.viewer-toolbar>ul{display:inline-block;margin:0 auto 5px;overflow:hidden;padding:3px 0}.viewer-toolbar>ul>li{background-color:rgba(0,0,0,.5);border-radius:50%;cursor:pointer;float:left;height:24px;overflow:hidden;-webkit-transition:background-color .15s;transition:background-color .15s;width:24px}.viewer-toolbar>ul>li:hover{background-color:rgba(0,0,0,.8)}.viewer-toolbar>ul>li:before{margin:2px}.viewer-toolbar>ul>li+li{margin-left:1px}.viewer-toolbar>ul>.viewer-small{height:18px;margin-bottom:3px;margin-top:3px;width:18px}.viewer-toolbar>ul>.viewer-small:before{margin:-1px}.viewer-toolbar>ul>.viewer-large{height:30px;margin-bottom:-3px;margin-top:-3px;width:30px}.viewer-toolbar>ul>.viewer-large:before{margin:5px}.viewer-tooltip{background-color:rgba(0,0,0,.8);border-radius:10px;color:#fff;display:none;font-size:12px;height:20px;left:50%;line-height:20px;margin-left:-25px;margin-top:-10px;position:absolute;text-align:center;top:50%;width:50px}.viewer-title{color:#ccc;display:inline-block;font-size:12px;line-height:1;margin:0 5% 5px;max-width:90%;opacity:.8;overflow:hidden;text-overflow:ellipsis;-webkit-transition:opacity .15s;transition:opacity .15s;white-space:nowrap}.viewer-title:hover{opacity:1}.viewer-button{background-color:rgba(0,0,0,.5);border-radius:50%;cursor:pointer;height:80px;overflow:hidden;position:absolute;right:-40px;top:-40px;-webkit-transition:background-color .15s;transition:background-color .15s;width:80px}.viewer-button:focus,.viewer-button:hover{background-color:rgba(0,0,0,.8)}.viewer-button:before{bottom:15px;left:15px;position:absolute}.viewer-fixed{position:fixed}.viewer-open{overflow:hidden}.viewer-show{display:block}.viewer-hide{display:none}.viewer-backdrop{background-color:rgba(0,0,0,.5)}.viewer-invisible{visibility:hidden}.viewer-move{cursor:move;cursor:-webkit-grab;cursor:grab}.viewer-fade{opacity:0}.viewer-in{opacity:1}.viewer-transition{-webkit-transition:all .3s;transition:all .3s}@-webkit-keyframes viewer-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes viewer-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.viewer-loading:after{-webkit-animation:viewer-spinner 1s linear infinite;animation:viewer-spinner 1s linear infinite;border:4px solid hsla(0,0%,100%,.1);border-left-color:hsla(0,0%,100%,.5);border-radius:50%;content:"";display:inline-block;height:40px;left:50%;margin-left:-20px;margin-top:-20px;position:absolute;top:50%;width:40px;z-index:1}@media (max-width:767px){.viewer-hide-xs-down{display:none}}@media (max-width:991px){.viewer-hide-sm-down{display:none}}@media (max-width:1199px){.viewer-hide-md-down{display:none}}
--------------------------------------------------------------------------------
/pages/gallery/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux';
3 | import Gallery from '../../components/Gallery';
4 | import './index.less'
5 |
6 | class Gallert extends Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | render() {
12 | return (
13 |
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 | export default (Gallert);
--------------------------------------------------------------------------------
/pages/gallery/index.less:
--------------------------------------------------------------------------------
1 | .gallery-wrapper{
2 | height:100%;
3 | }
--------------------------------------------------------------------------------
/pages/index.less:
--------------------------------------------------------------------------------
1 | .css-move-top{
2 | transition: .3s all linear;
3 | &:hover{
4 | transition: .3s all linear;
5 | box-shadow: 0 18px 39px 0 rgba(7,17,27,.6);
6 | cursor: pointer;
7 | }
8 | }
9 | .tag{
10 | display: inline-block;
11 | padding: 0 6px;
12 | color: #017E66;
13 | background-color: rgba(1,126,102,0.08);
14 | height: 22px;
15 | margin-right: 10px;
16 | line-height: 22px;
17 | font-weight: normal;
18 | font-size: 13px;
19 | text-align: center;
20 | transition:all .3s ease ;
21 | cursor: pointer;
22 | &:hover,&.active{
23 | background-color: #017E66;
24 | color: #fff;
25 | transition:all .3s ease ;
26 | }
27 | }
28 | .markdown-style {
29 | ul{
30 | margin: 1.5em 0 1.5em 3em;
31 | padding-left: 0;
32 | li{
33 | list-style: disc;
34 | }
35 | }
36 | ol{
37 | li{
38 | list-style: decimal;
39 | }
40 | }
41 | li{
42 | display: list-item;
43 | margin:.3em 0;
44 | }
45 | }
46 | .author-comment{
47 | background: #FCF8E3;
48 | border-radius: 3px;
49 | font-size: 12px;
50 | color: #8A6D3B ;
51 | padding: 2px 4px;
52 | margin-left: 10px;
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/pages/index/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {connect} from 'react-redux'
3 | import QueueAnim from 'rc-queue-anim';
4 | import {Button, Switch,Row,Col} from 'antd';
5 | import Link from 'next/link';
6 | import MyHead from '../../components/MyHead';
7 | import * as ROUTER from '../../config/constantsData';
8 | import './index.less'
9 |
10 |
11 | let timer;
12 | class Index extends React.Component {
13 | constructor() {
14 | super()
15 | this.state = {
16 | bg: '',
17 | defaultIndexBg: 0
18 | }
19 | }
20 | componentWillMount(){
21 | this.setState({
22 | defaultIndexBg: this.getRandom()
23 | })
24 | }
25 | componentDidMount(){
26 | timer=setInterval(()=>{
27 | this.setState({
28 | defaultIndexBg:this.getRandom()
29 | })
30 | },ROUTER.defaultTimer)
31 |
32 | }
33 | componentWillUnmount(){
34 | clearInterval(timer)
35 | }
36 | getRandom(){
37 | const {BG_INDEX} = ROUTER;
38 | return Math.random() * BG_INDEX.length | 0;
39 | }
40 |
41 | onChangeBg() {
42 | const {defaultIndexBg} = this.state;
43 | let random = this.getRandom();
44 | if(random===defaultIndexBg){
45 | random = this.getRandom();
46 | }
47 | this.setState({
48 | defaultIndexBg: random
49 | })
50 | }
51 |
52 | render() {
53 | const {defaultIndexBg} = this.state;
54 | return (
55 |
56 |
57 |
58 |
63 |
64 |
65 |
66 |
67 | {ROUTER.INDEX_TITLE}
68 |
69 |
70 | 2017年7月 - 刘伟波 刘伟波,16年西安文理学院毕业,曾担任支付宝城市服务等业务,热爱web前端,多年来一直从事web前端开发,熟练h5,vue,react,著作:伟波前端。
71 |
72 |
73 | {ROUTER.INDEX_ENGLISH}
74 |
75 |
76 |
95 |
96 |
97 |
Contact
98 |
99 |
|
100 | http://www.liuweibo.cn
101 |
102 |
103 |
104 |
105 |
109 |
110 |
111 | )
112 | }
113 | }
114 |
115 | export default connect()(Index)
116 |
--------------------------------------------------------------------------------
/pages/index/index.less:
--------------------------------------------------------------------------------
1 | .index{
2 | background-size: cover;
3 | background-repeat: no-repeat;
4 | background-position: 50% 50%;
5 | transition: All 2s ease;
6 | }
7 | .index{
8 | height: 100%;
9 | .my-row{
10 | position: relative;
11 | height: 100%;
12 |
13 | }
14 | .my-col{
15 | height: 100%;
16 | }
17 | .img{
18 | position: absolute;
19 | width: 100%;
20 | }
21 | .layout{
22 | padding-top: 20px;
23 | background-color: rgba(0,0,0,0.3);
24 | border-radius: 1.5em;
25 | color: #fefefe;
26 | overflow: hidden;
27 | position: absolute;
28 | left: 50%;
29 | top: 50%;
30 | width: 100%;
31 | max-height: 80%;
32 | transform: translate(-50%, -50%);
33 | transition: height 1s ease;
34 | transition: top 1s ease;
35 | a{
36 | color: #fefefe;
37 | text-shadow: 0 1px 1px #333;
38 | }
39 |
40 | .header{
41 | padding-bottom: 1em;
42 | margin: 0 20px;
43 | text-align: center;
44 | border-bottom: 1px solid #d3d3d3;
45 | h1{
46 | font-size: 1.4rem;
47 | line-height: 90%;
48 | color: #fefefe;
49 | margin: auto;
50 | padding-top: 0.4rem;
51 | text-shadow: 0 1px 1px #333;
52 | }
53 | h2{
54 | height: 0;
55 | overflow: hidden;
56 | }
57 | p{
58 | line-height: 38px;
59 | color: #fefefe;
60 | text-shadow: 0 1px 1px #333;
61 | }
62 | }
63 |
64 | .header:hover h1{
65 | color: #fefefe;
66 | text-shadow: 0 1px 1px #333;
67 | cursor: pointer;
68 | text-decoration: underline;
69 | }
70 | .mid{
71 | min-height: 126px;
72 | margin: 1.5em 1em 1em 1em;
73 | li{
74 | width: 90%;
75 | margin: 1em auto 1em auto;
76 | line-height: 180%;
77 | background-color: rgba(0,0,0,0.5);
78 | padding: 0.25rem;
79 | border-radius: 0.5em;
80 | text-shadow: 0 1px 1px #333;
81 | }
82 | li:hover{
83 | background-color: #fefefe;
84 | a{
85 | color: #222;
86 | text-decoration: underline;
87 | }
88 | }
89 | }
90 | .footer{
91 | margin: 0 2em;
92 | line-height: 45px;
93 | border-top: 1px solid #d3d3d3;
94 | text-align: right;
95 | span{
96 | padding: 0 5px;
97 | }
98 | a:hover{
99 | background-color: #fefefe;
100 | color: #222;
101 | text-decoration: underline;
102 | padding: 0.25rem;
103 | border-radius: 0.5em;
104 | }
105 | }
106 | }
107 | .sitemap{
108 | position: absolute;
109 | left: -9999px;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/pages/life/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {
4 | Layout, Menu, Breadcrumb, Row, Col,
5 | List, Avatar, Icon, Pagination, Alert,
6 | Input, Button, Radio, Tooltip, Spin
7 | } from 'antd'
8 |
9 | import 'whatwg-fetch'
10 | import Head from 'next/head'
11 |
12 | import {getLifeList} from '../../store/actions'
13 | import ListTitle from '../../components/ListTitle';
14 | import {getLifeUrl} from '../../config';
15 | import {COMMON_TITLE,LIFE_TXT} from '../../config/constantsData';
16 | import MyLayout from '../../components/MyLayout';
17 |
18 | const {Content} = Layout;
19 |
20 |
21 | class Life extends Component {
22 | constructor() {
23 | super()
24 | this.state = {
25 | }
26 | }
27 | componentWillMount(){
28 | const {dispatch} = this.props;
29 | getLifeList(dispatch, getLifeUrl())
30 | }
31 |
32 | render() {
33 |
34 | const {lifeData:listData,userAgent='pc'} = this.props
35 | return (
36 |
37 |
38 |
{LIFE_TXT}»{COMMON_TITLE}
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | )
50 | }
51 | }
52 |
53 | //这里根据需要传入redux
54 | const mapStateToProps = state => {
55 | const {lifeData} = state
56 | return {lifeData};
57 | }
58 |
59 | export default connect(mapStateToProps)(Life)
60 |
--------------------------------------------------------------------------------
/pages/markdown.less:
--------------------------------------------------------------------------------
1 | table {
2 | border-collapse: collapse;
3 | margin: 1rem 0;
4 | }
5 | tr {
6 | border-top: 1px solid #dfe2e5;
7 | }
8 | td, th {
9 | border: 1px solid #dfe2e5;
10 | padding: .6em 1em;
11 | }
12 | tr:nth-child(2n) {
13 | background-color: #f6f8fa;
14 | }
15 | blockquote {
16 | padding: 10px 20px;
17 | border-left: 2px solid #009A61;
18 | background: #F6F6F6;
19 | color: #555;
20 | font-size: 1em;
21 | }
22 | code {
23 | padding: 2px 4px;
24 | font-size: 90%;
25 | color: #c7254e;
26 | background-color: #f9f2f4;
27 | border-radius: 4px;
28 | }
29 | [class*=language-]
30 | {
31 | display: block;
32 | overflow-x: auto;
33 | padding: 0.5em;
34 | color: #abb2bf;
35 | background: #282c34;
36 | position: relative;
37 | border-radius: 6px;
38 | }
39 | [class*=language-]:before {
40 | content: "";
41 | position: absolute;
42 | z-index: 2;
43 | top: 0;
44 | right: 0;
45 | width: 3.5rem;
46 | height: 100%;
47 | border-radius: 6px 0 0 6px;
48 | border-right: 1px solid rgba(0, 0, 0, .66);
49 | background-color: #282c34
50 | }
51 |
52 | [class~=language-js]:before {
53 | content: "js"
54 | }
55 |
56 | [class~=language-ts]:before {
57 | content: "ts"
58 | }
59 |
60 | [class~=language-html]:before {
61 | content: "html"
62 | }
63 |
64 | [class~=language-md]:before {
65 | content: "md"
66 | }
67 |
68 | [class~=language-vue]:before {
69 | content: "vue"
70 | }
71 |
72 | [class~=language-css]:before {
73 | content: "css"
74 | }
75 |
76 | [class~=language-sass]:before {
77 | content: "sass"
78 | }
79 |
80 | [class~=language-scss]:before {
81 | content: "scss"
82 | }
83 |
84 | [class~=language-less]:before {
85 | content: "less"
86 | }
87 |
88 | [class~=language-stylus]:before {
89 | content: "stylus"
90 | }
91 |
92 | [class~=language-go]:before {
93 | content: "go"
94 | }
95 |
96 | [class~=language-java]:before {
97 | content: "java"
98 | }
99 |
100 | [class~=language-c]:before {
101 | content: "c"
102 | }
103 |
104 | [class~=language-sh]:before {
105 | content: "sh"
106 | }
107 |
108 | [class~=language-yaml]:before {
109 | content: "yaml"
110 | }
111 |
112 | [class~=language-javascript]:before {
113 | content: "js"
114 | }
115 |
116 | [class~=language-typescript]:before {
117 | content: "ts"
118 | }
119 |
120 | [class~=language-markup]:before {
121 | content: "html"
122 | }
123 |
124 | [class~=language-markdown]:before {
125 | content: "md"
126 | }
127 |
128 | [class~=language-json]:before {
129 | content: "json"
130 | }
131 |
132 | [class~=language-ruby]:before {
133 | content: "rb"
134 | }
135 |
136 | [class~=language-python]:before {
137 | content: "py"
138 | }
139 |
140 | [class~=language-bash]:before {
141 | content: "sh"
142 | }
143 | .hljs-comment,
144 | .hljs-quote {
145 | color: #5c6370;
146 | font-style: italic;
147 | }
148 |
149 | .hljs-doctag,
150 | .hljs-keyword,
151 | .hljs-formula {
152 | color: #c678dd;
153 | }
154 |
155 | .hljs-section,
156 | .hljs-name,
157 | .hljs-selector-tag,
158 | .hljs-deletion,
159 | .hljs-subst {
160 | color: #e06c75;
161 | }
162 |
163 | .hljs-literal {
164 | color: #56b6c2;
165 | }
166 |
167 | .hljs-string,
168 | .hljs-regexp,
169 | .hljs-addition,
170 | .hljs-attribute,
171 | .hljs-meta-string {
172 | color: #98c379;
173 | }
174 |
175 | .hljs-built_in,
176 | .hljs-class .hljs-title {
177 | color: #e6c07b;
178 | }
179 |
180 | .hljs-attr,
181 | .hljs-variable,
182 | .hljs-template-variable,
183 | .hljs-type,
184 | .hljs-selector-class,
185 | .hljs-selector-attr,
186 | .hljs-selector-pseudo,
187 | .hljs-number {
188 | color: #d19a66;
189 | }
190 |
191 | .hljs-symbol,
192 | .hljs-bullet,
193 | .hljs-link,
194 | .hljs-meta,
195 | .hljs-selector-id,
196 | .hljs-title {
197 | color: #61aeee;
198 | }
199 |
200 | .hljs-emphasis {
201 | font-style: italic;
202 | }
203 |
204 | .hljs-strong {
205 | font-weight: bold;
206 | }
207 |
208 | .hljs-link {
209 | text-decoration: underline;
210 | }
211 |
--------------------------------------------------------------------------------
/pages/postArticle/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {Layout, Menu, Breadcrumb, Row, Col, List, Avatar, Icon, Pagination, Alert, Input, Button, Select} from 'antd'
3 | import {connect} from 'react-redux'
4 | import Head from 'next/head';
5 |
6 | import EditArticle from '../../components/EditArticle';
7 | import {POST_ARTICLE_TXT,COMMON_TITLE,pageNum,ALL} from '../../config/constantsData';
8 | import MyLayout from '../../components/MyLayout';
9 | import {getBlogUrl} from '../../config';
10 |
11 | const {Content} = Layout;
12 |
13 |
14 |
15 | class PostArticle extends Component {
16 | render() {
17 | const {pageBlogData} = this.props;
18 | return (
19 |
20 |
21 |
{POST_ARTICLE_TXT}»{COMMON_TITLE}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 | }
34 | //得到文章最大id
35 | PostArticle.getInitialProps = async function (context) {
36 | try {
37 | let queryStringObj = {
38 | type: ALL,
39 | num: 1,
40 | pageNum
41 | }
42 | const pageBlog = await fetch(getBlogUrl(queryStringObj))
43 | const pageBlogData = await pageBlog.json()
44 |
45 | return {pageBlogData}
46 | } catch (error) {
47 | return {};
48 | }
49 | }
50 | export default connect()(PostArticle)
51 |
--------------------------------------------------------------------------------
/pages/versions/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import QueueAnim from 'rc-queue-anim';
3 | import Head from 'next/head';
4 | import moment from 'moment';
5 | import 'whatwg-fetch'
6 | import {
7 | Layout, Menu, Breadcrumb, Row, Col,
8 | List, Avatar, Icon, Pagination, Alert,
9 | Input, Button, Radio, Tooltip, Divider, Card, Timeline, Popover, Spin
10 | } from 'antd'
11 | import Loading from '../../components/loading'
12 | import MyLayout from '../../components/MyLayout';
13 | import './index.less'
14 | import {VERSIONS_TXT, COMMON_TITLE} from "../../config/constantsData";
15 | // import {obj, users} from './mockData';
16 | import {githubApiCommits, ALL_COMMITS, githubApiUser} from "../../config/githubApi";
17 |
18 | const {Content} = Layout;
19 |
20 |
21 | class Versions extends Component {
22 |
23 | constructor(props) {
24 | super(props);
25 | this.state = {
26 | versions: [],
27 | users: {},
28 | isMobile: false
29 | }
30 | }
31 |
32 | async componentWillMount() {
33 | const {availWidth} = window.screen;
34 | availWidth<768&&this.setState({
35 | isMobile:true
36 | })
37 | let data = [];
38 | let users = [];
39 | try {
40 | const blog = await fetch(githubApiCommits)
41 | const userData = await fetch(githubApiUser)
42 | data = await blog.json();
43 | users = await userData.json();
44 | } catch (error) {
45 | }
46 | const versions = Array.isArray(data) ? data.map(v => {
47 | const {html_url, commit, sha, author: outAuthor = {}} = v;
48 | const {avatar_url = '', login = '', html_url: github_html_url} = outAuthor || {};
49 | const {message, author} = commit;
50 | const {date} = author;
51 | return {html_url, message, date: +new Date(date), sha: sha.substring(0, 7), avatar_url, login, github_html_url};
52 | }) : data
53 | this.setState({
54 | versions,
55 | users
56 | })
57 | }
58 |
59 | getHoverTips() {
60 | const {users} = this.state;
61 | const {bio, company, location, name, login, avatar_url} = users;
62 | return
63 |
66 |
67 |
{name} {login}
68 |
{bio}
69 |
70 | {location}
71 | {company}
72 |
73 |
74 |
75 |
;
76 | }
77 |
78 | render() {
79 |
80 | const {versions,isMobile} = this.state;
81 | const mode = isMobile?'left':'alternate';
82 | console.log('mode',mode)
83 | return (
84 |
85 |
86 |
{VERSIONS_TXT}»{COMMON_TITLE}
87 |
88 |
89 |
90 |
91 |
本网站更新日志
92 |
93 | {
94 | Array.isArray(versions) && !versions.length &&
95 |
96 | }
97 |
102 | {
103 | Array.isArray(versions) ? versions.map((v, index) => {
104 | const {html_url, message, date, sha, avatar_url} = v;
105 | const momentDate = moment(date);
106 | return
107 | Commits on {momentDate.format('LLLL')}
108 |
109 |
110 |
111 | {message}
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | Weibozzz
120 |
121 | committed on {momentDate.fromNow()}
122 |
123 |
124 |
125 |
126 |
127 | ;
128 | })
129 | :
130 | {versions.message}
131 | }
132 |
133 |
134 |
139 |
140 |
141 |
142 | );
143 | }
144 | }
145 |
146 | export default Versions;
147 |
--------------------------------------------------------------------------------
/pages/versions/index.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/pages/versions/index.less
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const next = require('next')
3 |
4 | const compression = require('compression')
5 | const dev = process.env.NODE_ENV !== 'production'
6 | const app = next({dev})
7 | const handle = app.getRequestHandler()
8 | let port = dev ? 4324 : 7005
9 | console.log('Waiting ready on http://localhost ' + port + ' ……')
10 |
11 | // Pass in the absolute path to your robots.txt file
12 | app.prepare()
13 | .then(() => {
14 | const server = express()
15 |
16 | if (!dev) {
17 | server.use(compression()) //gzip
18 | }
19 | //文章二级页面
20 | server.get('/p/:id', (req, res) => {
21 | const actualPage = '/detail'
22 | const queryParams = {id: req.params.id}
23 | app.render(req, res, actualPage, queryParams)
24 | })
25 | //点击分页二级页面
26 | server.get('/blog/:id', (req, res) => {
27 | const actualPage = '/blog'
28 | const queryParams = {id: req.params.id}
29 | app.render(req, res, actualPage, queryParams)
30 | })
31 | //后台二级页面
32 | server.get('/adminDetail/:id', (req, res) => {
33 | const actualPage = '/adminDetail'
34 | const queryParams = {id: req.params.id}
35 | app.render(req, res, actualPage, queryParams)
36 | })
37 |
38 | const optionsPlain = {
39 | root: __dirname + '/static/',
40 | headers: {
41 | 'Content-Type': 'text/plain;charset=UTF-8',
42 | }
43 | };
44 | const optionsHtml = {
45 | root: __dirname + '/static/',
46 | headers: {
47 | 'Content-Type': 'text/html;charset=UTF-8',
48 | }
49 | };
50 | const optionsXml = {
51 | root: __dirname + '/static/',
52 | headers: {
53 | 'Content-Type': 'application/xml;charset=UTF-8',
54 | }
55 | };
56 | server.get('/robots.txt', (req, res) => (
57 | res.status(200).sendFile('robots.txt', optionsPlain)
58 | ));
59 | server.get('/sitemap.html', (req, res) => (
60 | res.status(200).sendFile('sitemap.html', optionsHtml)
61 | ));
62 | server.get('/sitemap.xml', (req, res) => (
63 | res.status(200).sendFile('sitemap.xml', optionsXml)
64 | ));
65 | server.get('*', (req, res) => {
66 | return handle(req, res)
67 | })
68 | server.listen(port, (err) => {
69 | if (err) throw err
70 | console.log('> Ready on http://localhost ' + port)
71 | })
72 |
73 | })
74 | .catch((ex) => {
75 | console.error(ex.stack)
76 | process.exit(1)
77 | })
78 |
--------------------------------------------------------------------------------
/static/420style.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/420style.jpg
--------------------------------------------------------------------------------
/static/ajax.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/ajax.png
--------------------------------------------------------------------------------
/static/all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/all.png
--------------------------------------------------------------------------------
/static/blogimg/677e4af2jw1f51htgf9ofj20qe0s0tj1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/677e4af2jw1f51htgf9ofj20qe0s0tj1.jpg
--------------------------------------------------------------------------------
/static/blogimg/IMG_20161119_160013.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/IMG_20161119_160013.jpg
--------------------------------------------------------------------------------
/static/blogimg/IMG_20170604_205917.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/IMG_20170604_205917.jpg
--------------------------------------------------------------------------------
/static/blogimg/life1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/life1.png
--------------------------------------------------------------------------------
/static/blogimg/life2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/life2.png
--------------------------------------------------------------------------------
/static/blogimg/life3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/life3.png
--------------------------------------------------------------------------------
/static/blogimg/lkasdfghjk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/lkasdfghjk.jpg
--------------------------------------------------------------------------------
/static/blogimg/loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/loader.gif
--------------------------------------------------------------------------------
/static/blogimg/time.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/time.png
--------------------------------------------------------------------------------
/static/blogimg/time2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/time2.png
--------------------------------------------------------------------------------
/static/blogimg/time3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/time3.png
--------------------------------------------------------------------------------
/static/blogimg/time4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/blogimg/time4.png
--------------------------------------------------------------------------------
/static/c_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/c_1.jpg
--------------------------------------------------------------------------------
/static/c_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/c_2.jpg
--------------------------------------------------------------------------------
/static/copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/copy.png
--------------------------------------------------------------------------------
/static/css.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/css.png
--------------------------------------------------------------------------------
/static/css/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "iconfont";
3 | src: url('iconfont.eot?t=1535096466956'); /* IE9*/
4 | src: url('iconfont.eot?t=1535096466956#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAvUAAsAAAAAEMgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8j1RRY21hcAAAAYAAAACEAAAB3oegg0BnbHlmAAACBAAAB7MAAAocbRcjO2hlYWQAAAm4AAAALwAAADYSavMhaGhlYQAACegAAAAcAAAAJAfeA4hobXR4AAAKBAAAAA4AAAAcHAAAAGxvY2EAAAoUAAAAEAAAABAHkgp6bWF4cAAACiQAAAAfAAAAIAEgAS1uYW1lAAAKRAAAAUUAAAJtPlT+fXBvc3QAAAuMAAAARgAAAFujkpbjeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeSX6KZ27438AQw9zA0AAUZgTJAQDnLwxneJztkUEKwkAMRV/sGKSIe3sGj+PCXZe9RldCz9D7BbqdC9RkIiiewQxv4P8hE8gHjkDn3JwC8kSImt2V5nf0zS/cXV84cUDtapOt21KHOu47/OqvEu/5nJhW/AfxyeqPyr/O7X68lcY2k0jGpiTSsTXx/bEtSaRUhyTSrGOCvgCyhiXXeJxdlltsHFcZx+fc53rmsnPZ2cusveudsZ31rr2X2biJbRzbVG1CkrZpGkrrgiABWhK1UhspASKqSlQgQFF4qARSxQvwFhEh2j4EyaIICZCAB3iBJx6KBKrUB15QRTZ8s+vkgd25nHPmfN/Z75v/7zurYEW5/wfyDdJXdpVXFAU1BZMocP2wgRI06OdjN81aXLgS9VA6cofjdh6xECYIxmFESARHA4X9cXsTwfM0a6cl8OCHUYJEyFvNNEuPgFseRv182MXjNB/nYRQeQ/086yKsePbkV7bn2U8lidVdRUjLNISyhWcYQxuzc/I+Y2c5cVybEeNFe8FGm4UBmO3snjq5fSojqNvf7BCkLqmI7F069eSjNxcJTkarKxyLZcIRPU3W1Pe0hje55TW0VcmHq+jLKkLq5K2o4vQpQ1c4eZfAwSe3GIXWGsaayhDnW5r2LhiiK2D4njr5GI1WB0tcRE6l3JgT2vJjeP3Skuc6Tg2hyrzgdD1X4FPk9W/kHXJCeV/5taK0UxtxkSBIShQ+vIy6RVanR76FNvEYBrIUslIcxfhhs5g2vUEWIdcNPHMkiz40xptoCw3HwdRn4bYFOZ9+waiYZKNWMLXfQnn2YIn00OfsdRR2g7AfPfCRkMNGsWC0jsgrn+zakF2KuauWSl7b9X2qcUKJV1lIQxXphm/XE6lKtTuU8za3qMqMaqha+srlU04WrAjJ6y+EetNyVa5JtWRSDXNGHCMSc1EJ3jQxTXvZN3wLcdWV7UQahFDhMJVwLCWTxDRUSi1mabpjMM1RXUEIE7ws5qpcqpZMZC89tj/PBNNCz7e0zNLKaqMe2eRtUmJYpTrTAm5bYG7ZRMcYBtxWHFkuQoQTIhhTS+rivCYFZlQ3y/3cbTonv9TRDEqIRYPacjKwQ8OcWwxSl6lCLxvDy5lVBxlEOiUoLkfLNiyBBXO9wSO1OjeJVjJL0SqiZWv+QjoX6JrJPT/OY8OMjACiMhHlxHKZhQl14vYXt3TfCDvLS55mMqIyP7A1HfREQE//ItdJqDDFVGwglRHWJm0yGA+iQdYaYwVdW58cTA7W0bUPL/R+27vwk2/vkeCbE+vOHfTvlXsfXb168GNFUQtxUgX/FbzMKWNlQzmjPKtcR3NT9tcG8LrX8lGWItDQmmgV7LdyWGJtPAjHXADP2Sgc9Mf5aNhDgygU+Zi1RoNgILK8GGwB6X5UzIiydDQc9MPABxu/MByHUE+mshsN834k0laTB9NCc4gHjhJcyG1tRkU2yqfSHhVaLvoPRT1jZsrDTKDTC+h8ygOwlI//n4XCXvCCheZDFmYo4Blomw/czDAAyh5eUIjvCoYoiI3QSZ5h/XhyHAm0QK3++nrPuR1njbZmC93VLf1I1tBxRceDl9PHv9vhrPbcSlyLwxVXrt+Qeb2a18512q15o9YpI1TtW3Jxu6qFUclHtb6U6GYzUAnCTKWa56klyqhmuZ5BdV1IIWUUS5tTJJilCzUcLjhBQAmXXYM6XOOMq4xTjAgCMRvE8aGFTOHrTOeIEFV4UnCEsKCEwiMIhwODFBTIIThDUynB8BQsLYiUC5tH3vqc6qsAq6MzJqWh4u9hDnTrnN770/7+3bv7+zeu3L79+s4bx7o8QJop9eTNi025d/b2h39sBYzPb71Wn/96vcrmz0SDT3f6z2ZJGvqy5AZ2TSu79Zbn654V2HU9+ifSMKIQKhGGyixKdNWxDA6FuvhZFLslR+WQHEAzdp2F/jRTBKqJb5dV3bB9T1cphnCMeFjiUhgBI2XDB/BJEb1WrkmIC2mUa0aEsMmdZc/RNUZV3YzLusEEkEsIZhoDx1iY7Y3ScuACaQQTTRdMKci7/wuqkJPAzp5ySnlKuahcVl5VritvKm8pP1VuK79Ufq/8GWhKs3EOugPJiaJyy6Lm82K/TGatQrqAzPiwgveKy7iLYBstdk4uCtXmBCbDbVyc4AFmADiHpb8JbllhzwFCkDI04ExFnuUgWT5zkZMZMFlhWswtFb8BhqY96INr2Lz51ON0SXgQFagXzXC2VjtBPm+mw5zsOg7zoqWlqycs0927sZxFDmG+98RFhF982pJQzsuLS1/b1Q1Lbr+2uBS7vHThMsaXJm+vPx9XbBuTE7tZz/yP1XB6XVkqWUdWnUT3Vyor+ytxN/C70OhUuuhpW5bsXs/pyMRZ7ci/z9WOH6v/rvqF43Y3292hRNqxac7vVOY4OrfnuX69/pf+ltt2y45F2M7uYk9fbCT1jaT2D+TJ+LmjptVN93aZXVG1IKn7jnz0GYSen3wrGw5PD4c4YSz8zBtxuLxumPbGSlT+6nnXZbz+RHf1fJ1Rzzt3rRwdeUQzLPPoYlR+/UKVJedXu0/+Zq0t7ZdOG5rNj/aNyc3KsML0zq2VIxqPB59aOpsSkp5ZWp7dN6rVzorO459XBzHXO+802xhnP7SXhrJ/lNu28/jnJwpheiMI09pmzNn25tbk426sgnY+95jjSlhCX2xmCKUveHOmbPUMcwCG5e3vQ1Y3N7YZi7dqyc5dVIR0ejj9DwIbxwFsHRyqvYJaIpvXEBtU0QAR/Qcvf3byI3SADv770Vc++OClyc/w/Tt37iF8/xOTV9F37h0o/wO0Lk7zAHicY2BkYGAA4vDPQdLx/DZfGbhZGEDg+tLrkxD0/60sDMzeQC4HAxNIFAA9UAtCAHicY2BkYGBu+N/AEMPCAAJAkpEBFbADAEcNAnB4nGNhYGBgwYEBAdwAHQAAAAAAAACsAdQB+AOYBO4FDnicY2BkYGBgZ1Rk4GcAASYg5gJCBob/YD4DAA2zAU8AeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicY2BigAAuBuyAnZGJkZmRhZGVkY2RnZGDgS0vPyU1q5g1t7K4MIc9MS+9NCexiA3MM2QtSk1MLmEtKwUqYGAAAE3LDqYAAA==') format('woff'),
6 | url('iconfont.ttf?t=1535096466956') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('iconfont.svg?t=1535096466956#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family:"iconfont" !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-nodejs:before { content: "\e989"; }
19 |
20 | .icon-mysql:before { content: "\e69b"; }
21 |
22 | .icon-angular:before { content: "\e619"; }
23 |
24 | .icon-mysql1:before { content: "\e667"; }
25 |
26 | .icon-react:before { content: "\f21a"; }
27 |
28 | .icon-vuejs:before { content: "\f25f"; }
29 |
30 |
--------------------------------------------------------------------------------
/static/css/iconfont/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/css/iconfont/iconfont.eot
--------------------------------------------------------------------------------
/static/css/iconfont/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/css/iconfont/iconfont.ttf
--------------------------------------------------------------------------------
/static/css/iconfont/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/css/iconfont/iconfont.woff
--------------------------------------------------------------------------------
/static/css/nprogress.css:
--------------------------------------------------------------------------------
1 | /* Make clicks pass-through */
2 | #nprogress {
3 | pointer-events: none;
4 | }
5 |
6 | #nprogress .bar {
7 | background: #29d;
8 |
9 | position: fixed;
10 | z-index: 1031;
11 | top: 0;
12 | left: 0;
13 |
14 | width: 100%;
15 | height: 2px;
16 | }
17 |
18 | /* Fancy blur effect */
19 | #nprogress .peg {
20 | display: block;
21 | position: absolute;
22 | right: 0px;
23 | width: 100px;
24 | height: 100%;
25 | box-shadow: 0 0 10px #29d, 0 0 5px #29d;
26 | opacity: 1.0;
27 |
28 | -webkit-transform: rotate(3deg) translate(0px, -4px);
29 | -ms-transform: rotate(3deg) translate(0px, -4px);
30 | transform: rotate(3deg) translate(0px, -4px);
31 | }
32 |
33 | /* Remove these to get rid of the spinner */
34 | #nprogress .spinner {
35 | display: block;
36 | position: fixed;
37 | z-index: 1031;
38 | top: 15px;
39 | right: 15px;
40 | }
41 |
42 | #nprogress .spinner-icon {
43 | width: 18px;
44 | height: 18px;
45 | box-sizing: border-box;
46 |
47 | border: solid 2px transparent;
48 | border-top-color: #29d;
49 | border-left-color: #29d;
50 | border-radius: 50%;
51 |
52 | -webkit-animation: nprogress-spinner 400ms linear infinite;
53 | animation: nprogress-spinner 400ms linear infinite;
54 | }
55 |
56 | .nprogress-custom-parent {
57 | overflow: hidden;
58 | position: relative;
59 | }
60 |
61 | .nprogress-custom-parent #nprogress .spinner,
62 | .nprogress-custom-parent #nprogress .bar {
63 | position: absolute;
64 | }
65 |
66 | @-webkit-keyframes nprogress-spinner {
67 | 0% { -webkit-transform: rotate(0deg); }
68 | 100% { -webkit-transform: rotate(360deg); }
69 | }
70 | @keyframes nprogress-spinner {
71 | 0% { transform: rotate(0deg); }
72 | 100% { transform: rotate(360deg); }
73 | }
--------------------------------------------------------------------------------
/static/cv/liuweibo_FrontEnd_CV.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/cv/liuweibo_FrontEnd_CV.doc
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/favicon.ico
--------------------------------------------------------------------------------
/static/fight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/fight.png
--------------------------------------------------------------------------------
/static/guitar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/guitar.png
--------------------------------------------------------------------------------
/static/h5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/h5.png
--------------------------------------------------------------------------------
/static/home/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/home/bg.png
--------------------------------------------------------------------------------
/static/home/borders1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/home/borders1.png
--------------------------------------------------------------------------------
/static/interesting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/interesting.png
--------------------------------------------------------------------------------
/static/jianli/1072586786_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/1072586786_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/353938127_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/353938127_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20140329_011043_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20140329_011043_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20140408_094952_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20140408_094952_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20140702_002912_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20140702_002912_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20150306_180108_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20150306_180108_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20150515_093309_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20150515_093309_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20150716_130022_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20150716_130022_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20150723_090614_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20150723_090614_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20160120_203926_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20160120_203926_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20160309_151645_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20160309_151645_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20160312_145558_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20160312_145558_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20160628_182845_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20160628_182845_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20161004_101037_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20161004_101037_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/IMG_20161103_210243_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/IMG_20161103_210243_compressed.jpg
--------------------------------------------------------------------------------
/static/jianli/mmexport1448350892762_compressed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/jianli/mmexport1448350892762_compressed.jpg
--------------------------------------------------------------------------------
/static/js.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/js.png
--------------------------------------------------------------------------------
/static/loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/loader.gif
--------------------------------------------------------------------------------
/static/musicControl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/musicControl.png
--------------------------------------------------------------------------------
/static/mv.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/mv.jpg
--------------------------------------------------------------------------------
/static/mysql.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/mysql.png
--------------------------------------------------------------------------------
/static/node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/node.png
--------------------------------------------------------------------------------
/static/php.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/php.png
--------------------------------------------------------------------------------
/static/robots.txt:
--------------------------------------------------------------------------------
1 | # robots.txt generated at http://tool.chinaz.com/robots/
2 | User-agent: *
3 | Disallow:
4 | Sitemap: http://www.liuweibo.cn/static/sitemap.xml
5 | Sitemap: http://www.liuweibo.cn/static/sitemap.html
6 |
--------------------------------------------------------------------------------
/static/server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/server.png
--------------------------------------------------------------------------------
/static/ttround.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/ttround.png
--------------------------------------------------------------------------------
/static/user/head-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/user/head-1.jpg
--------------------------------------------------------------------------------
/static/user/head-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/user/head-2.jpg
--------------------------------------------------------------------------------
/static/user/head-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/user/head-3.jpg
--------------------------------------------------------------------------------
/static/user/head-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/user/head-4.jpg
--------------------------------------------------------------------------------
/static/user/header-my.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weibozzz/next-blog/7019b5847c50d46858377ff92b7e8355d70aef04/static/user/header-my.jpg
--------------------------------------------------------------------------------
/store/action-types.js:
--------------------------------------------------------------------------------
1 | export const actionTypes = {
2 | //前台
3 | SEARCH_DATA: 'SEARCH_DATA',
4 | HOT_ARTICLE_DATA: 'HOT_ARTICLE_DATA',
5 | HOT_RECOMMEND_DATA: 'HOT_RECOMMEND_DATA',
6 | MODIFY_ARTICLE_DATA: 'MODIFY_ARTICLE_DATA',
7 | GET_SEARCH_TOTAL_DATA: 'GET_SEARCH_TOTAL_DATA',
8 | SEARCH_PAGE_DATA: 'SEARCH_PAGE_DATA',
9 | GET_LIFE:'GET_LIFE',
10 | GET_USER_COMMENT:'GET_USER_COMMENT',
11 | GET_COMMENTS:'GET_COMMENTS',
12 | GET_COMMENTS_USER:'GET_COMMENTS_USER',
13 | ADD_ZAN:'ADD_ZAN',
14 | SAVE_IP:'SAVE_IP',
15 | GET_IP:'GET_IP',
16 | GET_VIEW:'GET_VIEW',
17 | GET_CREATE_TIME:'GET_CREATE_TIME',
18 |
19 | SET_COMMENT_INDEX:'SET_COMMENT_INDEX',
20 | SET_ANSWER_ID:'SET_ANSWER_ID',
21 |
22 | GET_QINIU_TOKEN:'GET_QINIU_TOKEN',
23 |
24 | POST_COMMENTS: 'POST_COMMENTS',
25 | POST_USER_COMMENTS: 'POST_USER_COMMENTS',
26 | POST_ARTICLE: 'POST_ARTICLE',
27 |
28 | IS_COLLECT_ARTICLE:'IS_COLLECT_ARTICLE',
29 |
30 | //后台
31 | GET_ADMIN_DATA:'GET_ADMIN_DATA',
32 | POST_ADMIN_DETAIL:'POST_ADMIN_DETAIL',
33 | POST_ADMIN_PASSWORD:'POST_ADMIN_PASSWORD',
34 | }
35 |
--------------------------------------------------------------------------------
/store/actions.js:
--------------------------------------------------------------------------------
1 | import {actionTypes} from "./action-types";
2 | import fetch from 'isomorphic-unfetch'
3 | async function getJsonData(url){
4 | let jsonData = [];
5 | try {
6 | const res = await fetch(url)
7 | jsonData = await res.json()
8 | } catch (error) {
9 | }
10 | return jsonData;
11 | }
12 | //前台
13 | export const getSearchPageList = async (dispatch, url) => {
14 | //点击搜索分页搜索到的文章列表
15 | const jsonData = await getJsonData(url)
16 | return dispatch({type: actionTypes.SEARCH_PAGE_DATA, searchData: jsonData})
17 | }
18 | export const getSearchList = async (dispatch, url,myCollect) => {
19 | //第一次搜索到的文章列表
20 | if(url==='myCollect'){
21 | return dispatch({type: actionTypes.SEARCH_DATA, searchData: myCollect});
22 | }
23 | const jsonData = await getJsonData(url)
24 | return dispatch({type: actionTypes.SEARCH_DATA, searchData: jsonData})
25 |
26 | }
27 | export const getHotArticleList = async (dispatch, url) => {
28 | //获得热门文章
29 | const jsonData = await getJsonData(url)
30 | return dispatch({type: actionTypes.HOT_ARTICLE_DATA, hotArticleData: jsonData})
31 |
32 | }
33 | export const getModifyArticleList = async (dispatch, url) => {
34 | //获得最近修改文章
35 | const jsonData = await getJsonData(url)
36 | return dispatch({type: actionTypes.MODIFY_ARTICLE_DATA, modifyArticleData: jsonData})
37 |
38 | }
39 | export const getHotRecommendList = async (dispatch, url) => {
40 | //获得不同type热门文章
41 | const jsonData = await getJsonData(url)
42 | return dispatch({type: actionTypes.HOT_RECOMMEND_DATA, hotRecommendData: jsonData})
43 |
44 | }
45 | export const getSearchTotal = async (dispatch, url) => {
46 | //搜索的所有页数
47 | const jsonData = await getJsonData(url)
48 | return dispatch({type: actionTypes.GET_SEARCH_TOTAL_DATA, searchTotalData: jsonData})
49 |
50 | }
51 | export const addZan = async (dispatch, url) => {
52 | //搜索的所有页数
53 | const jsonData = await getJsonData(url)
54 | return dispatch({type: actionTypes.ADD_ZAN, addZanData: jsonData})
55 |
56 | }
57 | export const getLifeList = async (dispatch, url) => {
58 | //生活板块
59 | const jsonData = await getJsonData(url)
60 | return dispatch({type: actionTypes.GET_LIFE, lifeData: jsonData})
61 |
62 | }
63 | export const getQiniuToken = async (dispatch, url) => {
64 | //获得七牛云存储的token
65 | const jsonData = await getJsonData(url)
66 | return dispatch({type: actionTypes.GET_QINIU_TOKEN, qiniuToken: jsonData})
67 |
68 | }
69 | export const setCommentIndex = (dispatch, commentIndex) => {
70 | //设置评论回复index
71 | return dispatch({type: actionTypes.SET_COMMENT_INDEX, commentIndex})
72 |
73 | }
74 | export const setAnswerId = (dispatch, answerId) => {
75 | //设置回复评论answerid
76 | return dispatch({type: actionTypes.SET_ANSWER_ID, answerId})
77 |
78 | }
79 | export const collectArticleList = (dispatch, isCollectArticle) => {
80 | //设置是否展示收藏文章列表
81 | return dispatch({type: actionTypes.IS_COLLECT_ARTICLE, isCollectArticle})
82 |
83 | }
84 | export const getIpList = async (dispatch, url) => {
85 | //生活板块
86 | const jsonData = await getJsonData(url)
87 | return dispatch({type: actionTypes.GET_IP, ipListData: jsonData})
88 |
89 | }
90 | export const getViewList = async (dispatch, url) => {
91 | //生活板块
92 | const jsonData = await getJsonData(url)
93 | return dispatch({type: actionTypes.GET_VIEW, viewListData: jsonData})
94 |
95 | }
96 | export const getCreateTimeList = async (dispatch, url) => {
97 | //获得所有创建文章的时间
98 | const jsonData = await getJsonData(url)
99 | return dispatch({type: actionTypes.GET_CREATE_TIME, createTimeListData: jsonData})
100 |
101 | }
102 |
103 | export const postComments = async (dispatch, url,body) => {
104 | //发布评论,然后获得更新后的评论
105 | const res = await fetch(url,{
106 | method: 'POST',
107 | body:JSON.stringify(body)
108 | })
109 | const jsonData = await res.json()
110 | return dispatch({type: actionTypes.POST_COMMENTS, getCommentsData: jsonData})
111 |
112 | }
113 | export const postSaveIp = async (dispatch, url,body) => {
114 | //存储ip
115 | const res = await fetch(url,{
116 | method: 'POST',
117 | body:JSON.stringify(body)
118 | })
119 | const jsonData = await res.json()
120 | return dispatch({type: actionTypes.SAVE_IP, geSaveIpData: jsonData})
121 |
122 | }
123 | export const postUserComments = async (dispatch, url,body) => {
124 | //发布评论,然后获得更新后的评论
125 | const res = await fetch(url,{
126 | method: 'POST',
127 | body:JSON.stringify(body)
128 | })
129 | const jsonData = await res.json()
130 | return dispatch({type: actionTypes.POST_USER_COMMENTS, getUserCommentsData: jsonData})
131 |
132 | }
133 | export const postArticle = async (dispatch, url,body) => {
134 | //发布文章 修改文章
135 | const res = await fetch(url,{
136 | method: 'POST',
137 | body:JSON.stringify(body)
138 | })
139 | const jsonData = await res.json()
140 | return dispatch({type: actionTypes.POST_ARTICLE, postArticleData: jsonData})
141 |
142 | }
143 |
144 | //后台
145 |
146 | export const getAdminBlogList = async (dispatch, url) => {
147 | //点击搜索分页搜索到的文章列表
148 | const jsonData = await getJsonData(url)
149 | return dispatch({type: actionTypes.GET_ADMIN_DATA, adminBlogData: jsonData})
150 | }
151 | export const getCommentsUserList = async (dispatch, url) => {
152 | //上拉加载更多的评论
153 | const jsonData = await getJsonData(url)
154 | return dispatch({type: actionTypes.GET_COMMENTS_USER, getCommentsUserData: jsonData})
155 | }
156 |
157 | /*export const getCommentsList = async (dispatch, url) => {
158 | //得到所有用户评论
159 | const jsonData = await getJsonData(url)
160 | return dispatch({type: actionTypes.GET_COMMENTS, getAdminCommentsData: jsonData})
161 | }*/
162 |
163 | // export const postAdminDetail = async (dispatch, url) => {
164 | // //修改文章
165 | // const res = await fetch(url)
166 | // const jsonData = await res.json()
167 | // return dispatch({type: actionTypes.POST_ADMIN_DETAIL, postAdminDetailData: jsonData})
168 | // }
169 | export const postAdminPassword = async (dispatch, url,body) => {
170 | //检查密码是否正确为管理员
171 | const res = await fetch(url,{
172 | method: 'POST',
173 | body:JSON.stringify(body)
174 | })
175 | const jsonData = await res.json()
176 | return dispatch({type: actionTypes.POST_ADMIN_PASSWORD,postAdminPasswordData: jsonData})
177 | }
178 |
179 |
180 |
--------------------------------------------------------------------------------
/store/reducers.js:
--------------------------------------------------------------------------------
1 | import {actionTypes} from "./action-types";
2 |
3 | const exampleInitialState = {}
4 | export const reducer = (state = exampleInitialState, action) => {
5 |
6 | switch (action.type) {
7 | //前台
8 | case actionTypes.SEARCH_DATA:
9 | //第一次搜索到的文章列表
10 | return Object.assign({}, state, {
11 | searchData: action.searchData
12 | })
13 | case actionTypes.HOT_ARTICLE_DATA:
14 | //获得热门文章
15 | return Object.assign({}, state, {
16 | hotArticleData: action.hotArticleData
17 | })
18 | case actionTypes.MODIFY_ARTICLE_DATA:
19 | //获得修改文章
20 | return Object.assign({}, state, {
21 | modifyArticleData: action.modifyArticleData
22 | })
23 | case actionTypes.HOT_RECOMMEND_DATA:
24 | //获得不同type热门文章
25 | return Object.assign({}, state, {
26 | hotRecommendData: action.hotRecommendData
27 | })
28 | case actionTypes.SEARCH_PAGE_DATA:
29 | //点击搜索分页搜索到的文章列表
30 | return Object.assign({}, state, {
31 | searchData: action.searchData
32 | })
33 | case actionTypes.GET_SEARCH_TOTAL_DATA:
34 | //搜索的所有页数
35 | return Object.assign({}, state, {
36 | searchTotalData: action.searchTotalData
37 | })
38 | case actionTypes.ADD_ZAN:
39 | //搜索的所有页数
40 | return Object.assign({}, state, {
41 | addZanData: action.addZanData
42 | })
43 |
44 | case actionTypes.GET_LIFE:
45 | //生活板块
46 | return Object.assign({}, state, {
47 | lifeData: action.lifeData
48 | })
49 | case actionTypes.GET_CREATE_TIME:
50 | //生活板块
51 | return Object.assign({}, state, {
52 | createTimeListData: action.createTimeListData
53 | })
54 | case actionTypes.SET_COMMENT_INDEX:
55 | //设置评论回复index
56 | return Object.assign({}, state, {
57 | commentIndex: action.commentIndex
58 | })
59 | case actionTypes.SET_ANSWER_ID:
60 | //设置回复评论answerid
61 | return Object.assign({}, state, {
62 | answerId: action.answerId
63 | })
64 | case actionTypes.IS_COLLECT_ARTICLE:
65 | //设置是否展示收藏文章列表
66 | return Object.assign({}, state, {
67 | isCollectArticle: action.isCollectArticle
68 | })
69 |
70 | case actionTypes.GET_QINIU_TOKEN:
71 | //获得七牛云存储的token
72 | return Object.assign({}, state, {
73 | qiniuToken: action.qiniuToken
74 | })
75 |
76 |
77 | case actionTypes.POST_COMMENTS:
78 | //发布评论,然后获得更新后的评论
79 | return Object.assign({}, state, {
80 | getCommentsData: action.getCommentsData
81 | })
82 | case actionTypes.POST_USER_COMMENTS:
83 | //发布留言,然后获得更新后的评论
84 | return Object.assign({}, state, {
85 | getUserCommentsData: action.getUserCommentsData
86 | })
87 | case actionTypes.POST_ARTICLE:
88 | //发布文章后的返回
89 | return Object.assign({}, state, {
90 | postArticleData: action.postArticleData
91 | })
92 | case actionTypes.SAVE_IP:
93 | //存取ip地址返回
94 | return Object.assign({}, state, {
95 | geSaveIpData: action.geSaveIpData
96 | })
97 | case actionTypes.GET_IP:
98 | //得到ip地址返回
99 | return Object.assign({}, state, {
100 | ipListData: action.ipListData
101 | })
102 | case actionTypes.GET_VIEW:
103 | //得到view数量返回
104 | return Object.assign({}, state, {
105 | viewListData: action.viewListData
106 | })
107 |
108 | //后台
109 | case actionTypes.GET_COMMENTS:
110 | //获得博客用户评论数据
111 | return Object.assign({}, state, {
112 | getAdminCommentsData: action.getAdminCommentsData
113 | })
114 | case actionTypes.GET_COMMENTS_USER:
115 | //获得博客用户留言数据
116 | return Object.assign({}, state, {
117 | getCommentsUserData: action.getCommentsUserData
118 | })
119 | case actionTypes.GET_ADMIN_DATA:
120 | //获得博客数据
121 | return Object.assign({}, state, {
122 | adminBlogData: action.adminBlogData
123 | })
124 | case actionTypes.POST_ADMIN_DETAIL:
125 | //发布文章
126 | return Object.assign({}, state, {
127 | postAdminDetailData: action.postAdminDetailData
128 | })
129 | case actionTypes.POST_ADMIN_PASSWORD:
130 | //检查密码是否正确为管理员
131 | return Object.assign({}, state, {
132 | postAdminPasswordData: action.postAdminPasswordData
133 | })
134 | default:
135 | return state
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/store/store.js:
--------------------------------------------------------------------------------
1 | import {createStore, applyMiddleware} from 'redux'
2 | import {composeWithDevTools} from 'redux-devtools-extension'
3 | import thunkMiddleware from 'redux-thunk'
4 | import {reducer} from './reducers';
5 |
6 | const exampleInitialState = {commentIndex: -1, answerId: '', isCollectArticle: false}
7 |
8 |
9 | export function initializeStore(initialState = exampleInitialState) {
10 | return createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunkMiddleware)))
11 | }
12 |
--------------------------------------------------------------------------------
/until/cookie.js:
--------------------------------------------------------------------------------
1 | export function getCookie (name) {
2 | var arr,
3 | reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)')
4 | if (arr = document.cookie.match(reg)) {
5 | return decodeURIComponent(arr[2])
6 | } else {
7 | return null
8 | }
9 | }
10 | export function setCookie (c_name, value, expiredays) {
11 | var exdate = new Date()
12 | exdate.setDate(exdate.getDate() + expiredays)
13 | document.cookie = c_name + '='
14 | + encodeURIComponent(value) +
15 | ((expiredays == null) ? '' : ';expires=' +
16 | exdate.toGMTString())
17 | }
18 | export function clearAllCookie () {
19 | var keys = document.cookie.match(/[^ =;]+(?=\=)/g)
20 | if (keys) {
21 | for (var i = keys.length; i--;) {
22 | document.cookie = keys[i] + '=0;expires=' + new Date(0).toUTCString()
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/until/getiView.js:
--------------------------------------------------------------------------------
1 | export const getView = (rest = {}) => {
2 | const { date = '', history = '' } = rest;
3 | if (date) {
4 | try {
5 | const viewListData = JSON.parse(date);
6 | const his = JSON.parse(history)[0].history;
7 | let { t_view: curView } = viewListData[0];
8 | let { t_view: yesView } = viewListData[1];
9 | return { curView, yesView, his };
10 | } catch (err) {
11 | return {};
12 | }
13 | }
14 | return {};
15 | };
16 |
--------------------------------------------------------------------------------
/until/index.js:
--------------------------------------------------------------------------------
1 | import timeago from 'timeago.js';
2 | import {COLORS_ARR} from '../config/constantsData';
3 | import {getIPs} from 'real-ip';
4 | let format = require('date-format');
5 | export const updateHtml = str => {
6 | return str.replace(/'|"|:|\.|\[|\]|\\/g, function (str) {
7 |
8 | if (str === '"') {
9 | return '@quot;'
10 | } else if (str === "'") {
11 | return '@apos;'
12 | } else if (str === ':') {
13 | return ':'
14 | } else if (str === '[') {
15 | return '['
16 | } else if (str === '[') {
17 | return ']'
18 | } else if (str === '\\') {//自定义的 找不到
19 | return '+'
20 | } else {
21 | return '·'
22 | }
23 | });
24 | }
25 | export const spaceAdd = str => str && str.replace(/\+/g, ' ')
26 | export const getPathName = props => props.location && props.location.pathname && props.location.pathname.substring(1);
27 | export const NbspToSpace = str => str && str.replace(/ /g, ' ')
28 | export const formatTime = (time,type) => {
29 | let zh = ["日", "一", "二", "三", "四", "五", "六"];
30 | let date = new Date(time * 1000);
31 | let oneWeekTime = 7 * 24 * 60 * 60 * 1000;
32 | let result = type === 'mm-dd' ? format.asString('MM-dd', date) :format.asString('yyyy-MM-dd hh:mm', date) + ' 星期' + zh[date.getDay()]
33 | return Date.now() - date>=oneWeekTime ?
34 | result
35 | :
36 | timeago().format(date)
37 | }
38 | export const getArticleInfo = detailArr => {
39 | let {...rest} = detailArr && detailArr[0] ? detailArr[0] : {};
40 | /*{
41 | content,
42 | createTime,
43 | id,
44 | img,
45 | lastModify,
46 | like,
47 | modifyCount,
48 | recommend,
49 | short,
50 | title,
51 | type,
52 | url,
53 | user,
54 | visitor,
55 | week
56 | }*/
57 | return rest
58 | }
59 | export const OldTime = 1531094400; // 2018-07-09 00:00:00 星期一
60 | export const NewCdnTime = 1545723881; // 2018-12-25 15:44:41 星期二 切換了cdn域名
61 | /**
62 | * 七牛云cdn测试域名到期,切换新的域名
63 | */
64 | export const changeCdnUrl = (createTime,code)=> {
65 | let newCont = code.replace(/(pbw4yrlys\.bkt\.clouddn\.com)/gim,'images.static.liuweibo.cn')
66 | .replace(/(pd96wjt4m\.bkt\.clouddn\.com)/gim,'images.liuweibo.cn')
67 | // return createTime > NewCdnTime ? code : newCont;
68 | return newCont;
69 | };
70 | export const getHtml = (str, newTime) => {
71 | return str ? str.replace(/@quot;|@apos;/g, function (str) {
72 | if (str === '@quot;') {
73 | return '"'
74 | } else if (str === "@apos;") {
75 | return "'"
76 | }
77 | })
78 | : '';
79 | }
80 |
81 | export function toQueryStr(obj){
82 | return "?" + JSON.stringify(obj).replace(/{|}|\"|\'/g, "").replace(/,/g, "&").replace(/:/g, "=");
83 | }
84 | //对所有文章创建时间进行处理
85 | export function getYearAndMounth(time) {
86 | if(!time)return '';
87 | const tt=new Date(Number(time)*1000);
88 | return tt.getFullYear()+'年'+String(+tt.getMonth()+1).padStart(2,0)+'月';
89 | }
90 | //数组去重并且记住每个重复的个数
91 | export function cancelRepeat(arr) {
92 | let newArr=[];
93 | let obj={};
94 | for(let i in arr){
95 | if(!obj[arr[i]]){
96 | obj[arr[i]]=1;
97 | newArr.push(arr[i]);
98 | }else {
99 | obj[arr[i]]++;
100 | }
101 | }
102 | return obj;
103 | }
104 | export const regUrl = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/;
105 | /**
106 | * 取出指定数组长度的随机数组
107 | * @param arr
108 | * @param len
109 | * @returns {Array}
110 | */
111 | export const getRandomArr = (arr, len) => {
112 | let result = [];
113 | let resultArr = [];
114 | while (1) {
115 | let random = Math.random() * arr.length | 0;
116 | if (result.indexOf(random) === -1) {
117 | result.push(random)
118 | }
119 | if(result.length===Math.min(10,len)){
120 | resultArr=resultArr.concat(result)
121 | result=[]
122 | }
123 | if (resultArr.length>=len ) {
124 | break;
125 | }
126 | }
127 | return resultArr.slice(0,len)
128 | }
129 | /**
130 | * 获得图片前缀name
131 | * @param imageName
132 | * @returns {string}
133 | */
134 | export const getImageName = (imageName)=>{
135 | const index = imageName.lastIndexOf('.');
136 | return {
137 | font:index<0?imageName:imageName.substring(0,index),
138 | back:index<0?'.png':imageName.substring(index)
139 | }
140 |
141 | };
142 | //节流函数
143 | export const throttle = (fn,interval)=>{
144 | let timer;
145 | return ()=>{
146 | if(timer){
147 | return ;
148 | }
149 | timer=setTimeout(() => {
150 | clearTimeout(timer)
151 | fn()
152 | timer=null;
153 | }, interval||500);
154 | };
155 | }
156 | /**
157 | * 对象数组去重
158 | * @param arr
159 | * @param key
160 | * @returns {*}
161 | */
162 | export const removeSameArray = (arr, key) => {
163 | let obj = {}
164 | return arr.reduce((total, item) => {
165 | let val = item[key];
166 | if (!obj[val]) {
167 | total.push(item)
168 | obj[val] = val
169 | }
170 | return total;
171 | }, [])
172 | }
173 | export const reg_rule = {
174 | life: /^image\/life/,
175 | fight: /^image\/fight/,
176 | my: /^image\/my/,
177 | scenery: /^image\/scenery/,
178 | }
179 | export const fnTextPopup = function (arr, options) {
180 | // arr参数是必须的
181 | const len = COLORS_ARR.length;
182 | if (!arr || !arr.length) {
183 | return;
184 | }
185 | // 主逻辑
186 | let index = 0;
187 | let fn=function (event) {
188 | let color1 = COLORS_ARR[Math.random()*len | 0];
189 | let color2 = COLORS_ARR[Math.random()*len | 0];
190 | let x = event.pageX, y = event.pageY;
191 | let eleText = document.createElement('span');
192 | eleText.className = 'text-popup';
193 | eleText.style.backgroundImage=`linear-gradient(to right, ${color1}, ${color2}, ${color1})`;
194 | this.appendChild(eleText);
195 | if (arr[index]) {
196 | eleText.innerHTML = arr[index];
197 | } else {
198 | index = 0;
199 | eleText.innerHTML = arr[0];
200 | }
201 | // 动画结束后删除自己
202 | eleText.addEventListener('animationend', function () {
203 | eleText.parentNode.removeChild(eleText);
204 | });
205 | // 位置
206 | eleText.style.left = (x - eleText.clientWidth / 2) + 'px';
207 | eleText.style.top = (y - eleText.clientHeight) + 'px';
208 | // index递增
209 | index++;
210 | }
211 | document.documentElement.addEventListener('click', fn);
212 | return fn;
213 | };
214 | export const real_ip = ()=> new Promise((resolve, reject) => {
215 | getIPs(ip=>{
216 | resolve(ip)
217 | })
218 | setInterval(() => {
219 | reject('时间过长')
220 | }, 2000);
221 | });
222 |
--------------------------------------------------------------------------------
/until/upload-file.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 将图片转化为base64
4 | */
5 | export const imgBase64 = (file, callback) => {
6 | // 看支持不支持FileReader
7 | if (!file || !window.FileReader) return;
8 | // 创建一个 Image 对象
9 | let image = new Image();
10 | // 绑定 load 事件处理器,加载完成后执行
11 | image.onload = function () {
12 | // 创建 canvas DOM 对象
13 | let canvas = document.createElement('canvas');
14 | // 返回一个用于在画布上绘图的环境, '2d' 指定了您想要在画布上绘制的类型
15 | let ctx = canvas.getContext('2d');
16 | // 如果高度超标 // 参数,最大高度
17 | let MAX_HEIGHT = 3000;
18 | if (image.height > MAX_HEIGHT) {
19 | // 宽度等比例缩放 *=
20 | image.width *= MAX_HEIGHT / image.height;
21 | image.height = MAX_HEIGHT;
22 | }
23 | // 获取 canvas的 2d 环境对象,
24 | // 可以理解Context是管理员,canvas是房子
25 | ctx.clearRect(0, 0, canvas.width, canvas.height);
26 | // 重置canvas宽高
27 | canvas.width = image.width;
28 | canvas.height = image.height;
29 | // 将图像绘制到canvas上
30 | ctx.drawImage(image, 0, 0, image.width, image.height);
31 |
32 | callback(image, canvas);
33 |
34 | };
35 | if (/^image/.test(file.type)) {
36 | // 创建一个reader
37 | let reader = new FileReader();
38 | // 读取成功后的回调
39 | reader.onload = function () {
40 | // 设置src属性,浏览器会自动加载。
41 | // 记住必须先绑定事件,才能设置src属性,否则会出同步问题。
42 | image.src = this.result;
43 | };
44 | // 将图片将转成 base64 格式
45 | reader.readAsDataURL(file);
46 | }
47 | };
48 | /**
49 | * 把Base64转换成file文件
50 | */
51 | export const convertBase64UrlToFile = (dataurl, filename) => {
52 | let arr = dataurl.split(','),
53 | mime = arr[0].match(/:(.*?);/)[1],
54 | bstr = atob(arr[1]),
55 | n = bstr.length,
56 | u8arr = new Uint8Array(n);
57 | while (n--) {
58 | u8arr[n] = bstr.charCodeAt(n);
59 | }
60 | return new File([u8arr], filename, { type: mime });
61 | };
62 |
63 | /**
64 | * 检查并压缩图片大小
65 | */
66 | export const checkAndHandleCompression = (file, maxSize = 1) => {
67 | return new Promise((resolve, reject) => {
68 |
69 | imgBase64(file, (image, canvas) => {
70 | maxSize = maxSize * 1024; // 1M (单位KB)
71 | let fileSize = file.size / 1024; // 、图片大小 (单位KB)
72 |
73 | let uploadSrc,
74 | uploadFile;
75 | // 如果图片大小大于maxSize,进行压缩
76 | console.log({ fileSize, maxSize });
77 | if (fileSize > maxSize) {
78 | uploadSrc = canvas.toDataURL(file.type, maxSize / fileSize); // 转换成DataURL
79 | uploadFile = convertBase64UrlToFile(uploadSrc, file.name); // 转成file文件
80 | } else {
81 | uploadSrc = image.src;
82 | uploadFile = file;
83 | }
84 |
85 | let compressedSize = uploadFile.size / 1024;// 压缩后图片大小 (单位KB)
86 | console.log('压缩后图片大小kb', compressedSize);
87 | // 判断图片大小是否小于maxSize,如果大于则继续压缩至小于为止
88 | if (compressedSize.toFixed(2) > maxSize) {
89 | checkAndHandleCompression(uploadFile);
90 | } else {
91 | let fileOptions = { uploadSrc, uploadFile };
92 | resolve(fileOptions);
93 | }
94 | });
95 |
96 | });
97 | };
98 |
--------------------------------------------------------------------------------