├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── components
├── Header.js
├── Header.scss
├── PostItem.js
├── PostItem.scss
├── PostList.js
└── PostList.scss
├── config
├── githubConfig.js
└── webpack
│ ├── webpack.base.config.js
│ ├── webpack.config.js
│ ├── webpack.dev.config.js
│ └── webpack.prod.config.js
├── package.json
├── public
├── assets
│ ├── css
│ │ ├── global.scss
│ │ └── reset.scss
│ ├── image
│ │ └── logo.png
│ └── js
│ │ └── rem.js
└── index.html
├── src
├── actions
│ ├── github.js
│ └── index.js
├── main.js
├── page
│ ├── blog
│ │ ├── article.js
│ │ ├── article.scss
│ │ ├── index.js
│ │ ├── index.scss
│ │ ├── search.js
│ │ └── search.scss
│ └── index
│ │ ├── index.js
│ │ └── index.scss
└── router.js
├── tools
├── deploy.js
├── httpServer.js
├── ip.js
├── release.sh
└── utils.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env", "react"]
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | dist
4 | config/githubConfig.js
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 simbawu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # 个人博客 http://simbawu.com
6 | - 结合GitHub GraphQL API,免费开源、开箱即用;
7 | - 响应式布局,完美适配不同屏幕尺寸;
8 | - 全新技术栈,持续更新升级;
9 | - 面向人群:工程师、设计师、产品经理等。
10 |
11 | ## 开箱即用
12 |
13 | #### 安装
14 |
15 | ```shell
16 | git clone git@github.com:simbawus/blog.git
17 | cd blog
18 | yarn
19 | yarn start
20 | ```
21 |
22 | 执行完上面四个步骤,打开浏览器访问 http://0.0.0.0:8097 即可预览我的博客。
23 |
24 | #### 修改
25 |
26 | 1. 进入**config/githubConfig.js** 修改github access token,如何获取token请查看我这篇文章:[如何利用GitHub GraphQL API开发个人博客?](https://github.com/simbawus/blog/issues/11)。
27 | 2. 进入**tools/release.sh**修改你的服务器配置,主要是修改下面这段,别忘了要在你电脑上配置你的服务器的ssh登录哦!
28 |
29 | ```shell
30 | root@115.28.222.218:/alidata/www/simbawu/simba/blog
31 | ```
32 |
33 | 两步修改即可上线你的个人博客啦!当然,可以进行个性化定制。
34 |
35 | #### 上线
36 |
37 | ```shell
38 | yarn deploy
39 | ```
40 |
41 | 执行上述命令,即可完成项目发布。是不是So easy ?
42 |
43 | 之后,只需要将写好的Markdown格式文章,新建一个Issue,就可以在博客里查看了。
44 |
45 | ## 一起搞事情
46 |
47 | - 如果你是产品经理,对博客的用户需求、用户体验有独到的见解,欢迎贡献你的想法;
48 | - 如果你是设计师,有前沿的设计审美、极致的设计追求,欢迎展现你的美;
49 | - 如果你是工程师,技术过硬、热衷开源,欢迎跟牛逼的产品和设计一起打造一流的个人博客。
50 |
51 | 让我们跟大牛们一起,做点不一样的事情。欢迎加我微信:**simbawu610**。
52 |
53 | ## Thanks
54 |
55 | 谢谢大家,欢迎Star & Fork 。◕‿◕。
--------------------------------------------------------------------------------
/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import s from './Header.scss';
3 | import { Link, withRouter } from 'react-router-dom';
4 |
5 | class Header extends React.Component {
6 | constructor(){
7 | super();
8 | this.state = {
9 | menus:[{
10 | name: '首页',
11 | url: '/index'
12 | },{
13 | name: 'Blog',
14 | url: '/blog'
15 | }],
16 | keyword: '',
17 | searchBoxFull: false
18 | };
19 |
20 | this._renderMenus = this._renderMenus.bind(this);
21 | this.handleSearchBox = this.handleSearchBox.bind(this);
22 | this.handleSearch = this.handleSearch.bind(this);
23 | this.handleInput = this.handleInput.bind(this);
24 | }
25 |
26 | componentDidMount(){
27 |
28 | }
29 |
30 | _renderMenus(){
31 | return this.state.menus.map((menu, index) => {
32 | let currentMenu = new RegExp(menu.url).test(location.pathname) ? menu.url : '';
33 | return (
34 | {menu.name}
35 | )
36 | })
37 | }
38 |
39 | handleSearchBox(state){
40 | this.setState({
41 | searchBoxFull: state
42 | })
43 | }
44 |
45 | handleInput(e){
46 | let keyword = e.target.value;
47 | this.setState({
48 | keyword: keyword
49 | })
50 | }
51 |
52 | handleSearch(e){
53 | e.preventDefault();
54 | let keyWords = this.state.keyword;
55 | this.props.history.push(`/search?keyWords=${keyWords}`);
56 | this.props.handleSearch && this.props.handleSearch();
57 | }
58 |
59 | render() {
60 | return (
61 |
62 |
63 |
64 | {this._renderMenus()}
65 |
66 |
70 |
71 |
72 | );
73 | }
74 | }
75 |
76 | export default withRouter(Header);
--------------------------------------------------------------------------------
/components/Header.scss:
--------------------------------------------------------------------------------
1 | .container{
2 | border-bottom: 1px solid #e6e3e3;
3 | background-color: #fff;
4 | }
5 |
6 | .header{
7 | position: relative;
8 | display: flex;
9 | align-items: center;
10 | justify-content: space-between;
11 | margin: 0 .1em;
12 | height: .5em;
13 | }
14 |
15 | .menuList{
16 | display: flex;
17 | align-items: center;
18 | height: .5em;
19 | }
20 |
21 | .menu{
22 | padding: 0 .5em;
23 | white-space: nowrap;
24 | font-size: .16em;
25 | color: #999;
26 | }
27 |
28 | .currentMenu{
29 | color: #00b0ab;
30 | }
31 |
32 | .searchBox{
33 | display: flex;
34 | align-items: center;
35 | box-sizing: border-box;
36 | padding: 0 .16em;
37 | width: .92rem;
38 | height: .3em;
39 | border: 1px solid #e6e3e3;
40 | border-radius: .3em;
41 | background-color: #fff;
42 | transition: width ease .2s;
43 |
44 | input{
45 | flex: 1;
46 | width: .4em;
47 | font-size: .14em;
48 | border: 0;
49 | }
50 |
51 | i{
52 | width: 1em;
53 | cursor: pointer;
54 | font-size: .18em;
55 | color: #e6e3e3;
56 | }
57 | }
58 |
59 | .searchBoxFull{
60 | .searchBox{
61 | position: absolute;
62 | right:0;
63 | width: 50%;
64 | }
65 | }
66 |
67 | @media only screen and (min-width: 768px) {
68 | .header{
69 | margin: 0 auto;
70 | max-width: 768px;
71 | font-size: 100px;
72 | }
73 | }
--------------------------------------------------------------------------------
/components/PostItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import s from './PostItem.scss';
3 | import { Link } from 'react-router-dom';
4 | import utils from "../tools/utils";
5 |
6 | class PostItem extends React.Component {
7 | constructor(){
8 | super();
9 | }
10 |
11 | render() {
12 | let item = this.props.item;
13 | let text = utils.autoAddEllipsis(item.bodyText, 90);
14 | let date = new Date(item.updatedAt).format('yyyy-MM-dd');
15 |
16 | return (
17 |
18 | {item.title}
19 | {text}
20 | {date}
21 |
22 | );
23 | }
24 | }
25 |
26 | export default PostItem;
--------------------------------------------------------------------------------
/components/PostItem.scss:
--------------------------------------------------------------------------------
1 | .item{
2 | display: block;
3 | background-color: #fff;
4 | padding: .16em .2em;
5 | box-shadow: 0 1px 3px rgba(26,26,26,.1);
6 |
7 | & + .item{
8 | margin-top: .1em;
9 | }
10 | }
11 |
12 | .title{
13 | font-size: .16em;
14 | color: #1a1a1a;
15 | }
16 |
17 | .summary{
18 | margin: .04em 0;
19 | font-size: .14em;
20 | color: #666;
21 | }
22 |
23 | .date{
24 | font-size: .12em;
25 | color: #999;
26 | }
--------------------------------------------------------------------------------
/components/PostList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import s from './PostList.scss';
3 | import PostItem from 'components/PostItem';
4 |
5 | class PostList extends React.Component {
6 | constructor(){
7 | super();
8 | }
9 |
10 | render() {
11 | let list = this.props.list.map((item, index)=>{
12 | return
13 | });
14 |
15 | return (
16 |
17 | {list}
18 |
19 | );
20 | }
21 | }
22 |
23 | export default PostList;
--------------------------------------------------------------------------------
/components/PostList.scss:
--------------------------------------------------------------------------------
1 | .list{
2 | margin: 0 auto;
3 | max-width: 768px;
4 | background-color: #f6f6f6;
5 | }
6 |
7 | @media only screen and (min-width: 768px) {
8 | .list{
9 | height: calc(100vh - .5em);
10 | font-size: 100px;
11 | }
12 | }
--------------------------------------------------------------------------------
/config/githubConfig.js:
--------------------------------------------------------------------------------
1 | //上传到github的access token,一经检测即会失效,所以大家在上传代码到github的时候应该过滤这个文件。
2 | const githubToken = '11f1779c52f5b929e0627b429b8c7a3bf8806ce1';
3 |
4 | export default githubToken
--------------------------------------------------------------------------------
/config/webpack/webpack.base.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = {
6 | entry: {
7 | app: ['./src/main.js']
8 | },
9 | output: {
10 | path: path.resolve(__dirname, '../../dist'),
11 | filename: '[name].js',
12 | publicPath: "/",
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.jsx?$/,
18 | loader: 'babel-loader',
19 | exclude: /(node_modules|build)/,
20 | query: {
21 | presets: ['env']
22 | }
23 | }, {
24 | test: /\.scss$/,
25 | use: [{
26 | loader: "style-loader"
27 | }, {
28 | loader: "css-loader",
29 | options: {
30 | modules: true,
31 | localIdentName: '[name]_[local]_[hash:base64:3]'
32 | },
33 | }, {
34 | loader: "sass-loader"
35 | }]
36 | }, {
37 | test: /\.css$/,
38 | use: [{
39 | loader: "style-loader"
40 | }, {
41 | loader: "css-loader"
42 | }, {
43 | loader: "sass-loader"
44 | }]
45 | }
46 | ]
47 | },
48 | plugins: [
49 | new webpack.DefinePlugin({ // 定义环境变量
50 | "process.env": JSON.stringify(process.env.NODE_ENV)
51 | }),
52 | new HtmlWebpackPlugin({
53 | filename: './index.html',
54 | template: './public/index.html',
55 | inject: "body",
56 | minify: {
57 | caseSensitive: false,
58 | collapseBooleanAttributes: true,
59 | collapseWhitespace: true
60 | },
61 | hash: true,
62 | cache: true,
63 | showErrors: true,
64 | chunks: "app",
65 | chunksSortMode: "auto",
66 | excludeChunks: "",
67 | xhtml: false
68 | })
69 | ],
70 | resolve: {
71 | alias: {
72 | components: path.join(__dirname, '../../components'),
73 | actions: path.join(__dirname, '../../src/actions')
74 | }
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/config/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | const base = require('./webpack.base.config');
2 | const merge = require('webpack-merge');
3 |
4 | let config;
5 | if (process.env.NODE_ENV === 'production') {
6 | config = require('./webpack.prod.config');
7 | } else {
8 | config = require('./webpack.dev.config');
9 | }
10 |
11 | module.exports = merge(base, config);
--------------------------------------------------------------------------------
/config/webpack/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | plugins: [
6 | new webpack.HotModuleReplacementPlugin()
7 | ],
8 | devServer: {
9 | inline: true,
10 | hot: true,
11 | host: '0.0.0.0',
12 | historyApiFallback: true,
13 | port: process.env.PORT || 8097
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/config/webpack/webpack.prod.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 |
3 | module.exports = {
4 |
5 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog",
3 | "version": "1.0.0",
4 | "main": "main.js",
5 | "repository": "https://github.com/simbawus/blog.git",
6 | "author": "simbawu ",
7 | "license": "MIT",
8 | "dependencies": {
9 | "react": "^16.3.2",
10 | "react-dom": "^16.3.2",
11 | "react-router-dom": "^4.2.2"
12 | },
13 | "devDependencies": {
14 | "axios": "^0.18.0",
15 | "babel-core": "^6.26.0",
16 | "babel-loader": "^7.1.2",
17 | "babel-polyfill": "^6.26.0",
18 | "babel-preset-env": "^1.6.1",
19 | "babel-preset-react": "^6.24.1",
20 | "cross-env": "^5.1.5",
21 | "css-loader": "^0.28.9",
22 | "github-markdown-css": "^2.10.0",
23 | "html-webpack-plugin": "^3.2.0",
24 | "node-sass": "^4.7.2",
25 | "sass-loader": "^6.0.6",
26 | "style-loader": "^0.20.1",
27 | "webpack": "^4.8.1",
28 | "webpack-cli": "^2.1.3",
29 | "webpack-dev-server": "^3.1.4",
30 | "webpack-merge": "^4.1.2"
31 | },
32 | "scripts": {
33 | "start": "cross-env NODE_ENV=development webpack-dev-server --mode development --open 'Google Chrome' --config ./config/webpack/webpack.config.js",
34 | "deploy": "node ./tools/deploy.js"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/public/assets/css/global.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'iconfont'; /* project id 568828 */
3 | src: url('//at.alicdn.com/t/font_568828_30v1qg8rf5uyp66r.eot');
4 | src: url('//at.alicdn.com/t/font_568828_30v1qg8rf5uyp66r.eot?#iefix') format('embedded-opentype'),
5 | url('//at.alicdn.com/t/font_568828_30v1qg8rf5uyp66r.woff') format('woff'),
6 | url('//at.alicdn.com/t/font_568828_30v1qg8rf5uyp66r.ttf') format('truetype'),
7 | url('//at.alicdn.com/t/font_568828_30v1qg8rf5uyp66r.svg#iconfont') format('svg');
8 | }
9 |
10 | //:global内的className不会被转义
11 |
12 | :global{
13 | .iconfont{
14 | font-family: "iconfont";
15 | font-size: .16rem;;
16 | font-style: normal;
17 | -webkit-font-smoothing: antialiased;
18 | -webkit-text-stroke-width: 0.2px;
19 | -moz-osx-font-smoothing: grayscale;
20 | }
21 |
22 | #app{
23 | min-height: 100%;
24 | height: 100%;
25 | }
26 | }
27 |
28 |
29 |
--------------------------------------------------------------------------------
/public/assets/css/reset.scss:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, button, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
2 | margin: 0;
3 | padding: 0;
4 | font-family: PingFang-SC, "San Francisco", "Source Sans", Rotobo, "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", "Hiragino Sans GB", "Heiti SC", sans-serif;
5 | font-style: normal;
6 | font-weight: normal;
7 | list-style: none;
8 | -webkit-tap-highlight-color: rgba(0,0,0,0);
9 | }
10 |
11 |
12 | html,body{
13 | min-height: 100%;
14 | height: 100%;
15 | -webkit-overflow-scrolling: touch;
16 | }
17 |
18 | body {
19 | -webkit-font-smoothing: antialiased;
20 | -webkit-touch-callout: none;
21 | -moz-osx-font-smoothing: grayscale;
22 | }
23 |
24 | a {
25 | text-decoration: none;
26 | }
27 |
28 | img {
29 | max-width: 100%;
30 | display: block;
31 | }
32 |
33 | a:focus,
34 | input:focus,
35 | button:focus {
36 | outline: 0;
37 | }
38 |
--------------------------------------------------------------------------------
/public/assets/image/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wusb/blog/7c2cb7f2bc5d5fc33472cdedcb25883da3d76a50/public/assets/image/logo.png
--------------------------------------------------------------------------------
/public/assets/js/rem.js:
--------------------------------------------------------------------------------
1 | !function(x){function w(){var a=Math.min(r.getBoundingClientRect().width,r.getBoundingClientRect().height);x.rem=a/3.75,r.style.fontSize=x.rem+"px"}var v,u,t,s=x.document,r=s.documentElement,q=s.querySelector('meta[name="viewport"]'),p=s.querySelector('meta[name="flexible"]');if(q){var o=q.getAttribute("content").match(/initial\-scale=(["']?)([\d\.]+)\1?/);o&&(u=parseFloat(o[2]),v=parseInt(1/u))}else{if(p){var o=p.getAttribute("content").match(/initial\-dpr=(["']?)([\d\.]+)\1?/);o&&(v=parseFloat(o[2]),u=parseFloat((1/v).toFixed(2)))}}if(!v&&!u){var n=(x.navigator.appVersion.match(/android/gi),x.navigator.appVersion.match(/iphone/gi)),v=x.devicePixelRatio;v=n?v>=3?3:v>=2?2:1:1,u=1/v}if(r.setAttribute("data-dpr",v),!q){if(q=s.createElement("meta"),q.setAttribute("name","viewport"),q.setAttribute("content","initial-scale="+u+", maximum-scale="+u+", minimum-scale="+u+", user-scalable=no"),r.firstElementChild){r.firstElementChild.appendChild(q)}else{var m=s.createElement("div");m.appendChild(q),s.write(m.innerHTML)}}x.dpr=v,x.addEventListener("resize",function(){(document.body.clientHeight==document.body.scrollHeight)&&(clearTimeout(t),t=setTimeout(w,300))},!1),x.addEventListener("pageshow",function(b){b.persisted&&(clearTimeout(t),t=setTimeout(w,300))},!1),w()}(window);
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
22 | 吴胜斌 | simbawu - 个人主页
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/actions/github.js:
--------------------------------------------------------------------------------
1 | export default {
2 | getIssues(data){
3 | return this.http('github', 'post', '/graphql', data)
4 | }
5 | }
--------------------------------------------------------------------------------
/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import httpServer from '../../tools/httpServer';
2 | import GitHub from './github';
3 | import githubToken from '../../config/githubConfig.js'
4 |
5 | const project = {
6 | github:{
7 | baseURL: 'https://api.github.com',
8 | token: githubToken
9 | }
10 | };
11 |
12 | const actions = {
13 | http(pName, method, url, data){
14 | return httpServer({method: method,url: url}, data, ...Object.values(project[pName])).then((res) => {
15 | return res;
16 | })
17 | }
18 | };
19 |
20 |
21 | export default Object.assign(
22 | actions, GitHub
23 | );
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import router from './router';
4 |
5 | import '../public/assets/js/rem';
6 |
7 | import '../public/assets/css/reset.scss';
8 | import '../public/assets/css/global.scss';
9 |
10 | ReactDOM.render(router,document.getElementById('app'));
--------------------------------------------------------------------------------
/src/page/blog/article.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import s from './article.scss';
3 | import Actions from 'actions';
4 | import { Link } from 'react-router-dom';
5 | import utils from '../../../tools/utils';
6 | import 'github-markdown-css';
7 | import Header from 'components/Header';
8 |
9 | class ArticlePage extends React.Component {
10 | constructor(){
11 | super();
12 | this.state = {
13 | title: '',
14 | updatedAt: '',
15 | bodyHTML: ''
16 | }
17 | }
18 |
19 | componentDidMount(){
20 | let articleId = this.props.match.params.articleId;
21 | this.getIssue(articleId)
22 | }
23 |
24 | getIssue(articleId){
25 | let data = {
26 | query: `query {
27 | repository(owner:"simbawus", name: "blog") {
28 | issue(number: ${articleId}) {
29 | title
30 | updatedAt
31 | bodyHTML
32 | }
33 | }
34 | }`
35 | };
36 |
37 | Actions.getIssues(data).then((res) => {
38 | let issue = res.data.data.repository.issue;
39 |
40 | document.title = `${issue.title} - 个人博客 - 吴胜斌 | simbawu`;
41 |
42 | this.setState({
43 | title: issue.title,
44 | updatedAt: new Date(issue.updatedAt).format('yyyy-MM-dd'),
45 | bodyHTML: issue.bodyHTML
46 | })
47 |
48 | })
49 | }
50 |
51 | _renderHTML(){
52 | return { __html: this.state.bodyHTML };
53 | }
54 |
55 | render() {
56 | return (
57 |
58 |
59 |
60 |
{this.state.title}
61 |
{this.state.updatedAt}
62 |
63 |
64 |
65 | );
66 | }
67 | }
68 |
69 | export default ArticlePage;
--------------------------------------------------------------------------------
/src/page/blog/article.scss:
--------------------------------------------------------------------------------
1 | .markdown{
2 | margin: 0 auto;
3 | max-width: 768px;
4 | padding: 15px;
5 | font-size: 12px;
6 |
7 | ol{
8 | list-style: decimal;
9 | }
10 |
11 | ul{
12 | list-style: disc;
13 | }
14 |
15 | ol ol, ul ol {
16 | list-style: lower-roman;
17 | }
18 |
19 | li{
20 | list-style: inherit;
21 | }
22 |
23 | em{
24 | font-style: italic;
25 | }
26 | }
27 |
28 | .date{
29 | margin: 4px 0 6px;
30 | font-size: 15px;
31 | color: #8a8a8a;
32 | }
--------------------------------------------------------------------------------
/src/page/blog/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import s from './index.scss';
3 | import Actions from 'actions';
4 | import { Link, browserHistory } from 'react-router-dom';
5 | import Header from 'components/Header';
6 | import PostList from 'components/PostList';
7 |
8 | import utils from '../../../tools/utils';
9 |
10 | class IndexPage extends React.Component {
11 | constructor(){
12 | super();
13 | this.state = {
14 | labels: [],
15 | list: [],
16 | currentLabel: 'New',
17 | opacity: 1,
18 | headerFixed: false
19 | };
20 |
21 | this.localState = {
22 | startX: 0,
23 | startY: 0,
24 | startT: 0,
25 | isMove: false,
26 | isTouchEnd: true,
27 | initTab: 0,
28 | currentTab: 0,
29 | direction: 'left'
30 | };
31 |
32 | this.chooseLabel = this.chooseLabel.bind(this);
33 | this.touchStart = this.touchStart.bind(this);
34 | this.touchMove = this.touchMove.bind(this);
35 | this.touchEnd = this.touchEnd.bind(this);
36 | this.handleLabelIndex = this.handleLabelIndex.bind(this);
37 | this.handleOpacity = this.handleOpacity.bind(this);
38 | this.handleOnScroll = this.handleOnScroll.bind(this);
39 | }
40 |
41 | componentDidMount(){
42 | document.title = '个人博客 - 吴胜斌 | simbawu';
43 | window.addEventListener('scroll', this.handleOnScroll);
44 | let currentLabel = this.props.match.params.type;
45 | this.getIssues(currentLabel || 'New');
46 | }
47 |
48 | componentWillUnmount(){
49 | window.removeEventListener('scroll', this.handleOnScroll);
50 | }
51 |
52 | getIssues(label = 'New', page = null){
53 | let data = {};
54 | this.localState.initLabel = label;
55 | if(label == 'New' || !this.state.labels.includes(label)){
56 | label = 'New';
57 | data = {
58 | query: `query {
59 | repository(owner:"simbawus", name: "blog") {
60 | issues(orderBy:{field: UPDATED_AT, direction: DESC} , labels: null, first: 10, after: ${page}) {
61 | edges{
62 | cursor
63 | node{
64 | title
65 | updatedAt
66 | bodyText
67 | number
68 | }
69 | }
70 | }
71 | labels(first: 100){
72 | nodes{
73 | name
74 | }
75 | }
76 | }
77 | }`
78 | };
79 | }else {
80 | data = {
81 | query: `query {
82 | repository(owner:"simbawus", name: "blog") {
83 | issues(orderBy:{field: UPDATED_AT, direction: DESC} , labels: "${label}", first: 10, after: ${page}) {
84 | edges{
85 | cursor
86 | node{
87 | title
88 | updatedAt
89 | bodyText
90 | number
91 | }
92 | }
93 | }
94 | labels(first: 100){
95 | nodes{
96 | name
97 | }
98 | }
99 | }
100 | }`
101 | };
102 | }
103 |
104 | Actions.getIssues(data).then((res) => {
105 | let labels = res.data.data.repository.labels.nodes.map((item, index) => {
106 | return item.name
107 | });
108 |
109 | labels.unshift('New');
110 |
111 | let list = res.data.data.repository.issues.edges;
112 | page = list[(list.length - 1)].cursor;
113 |
114 | this.setState({
115 | labels: labels,
116 | list: list,
117 | currentLabel: label,
118 | opacity: 1
119 | })
120 |
121 | })
122 | }
123 |
124 | _renderLabels(){
125 | return this.state.labels.map((item, index) => {
126 | return this.chooseLabel(item)}>
127 |
130 |
131 | })
132 | }
133 |
134 | chooseLabel(currentLabel){
135 | this.props.history.push(`/blog/${currentLabel}`);
136 | this.getIssues(currentLabel);
137 | }
138 |
139 | touchStart(e){
140 | // e.preventDefault();
141 | if(e.touches.length == 1 || this.localState.isTouchEnd){
142 | let touch = e.touches[0];
143 | this.localState.startX = touch.pageX;
144 | this.localState.startY = touch.pageY;
145 | this.localState.initTab = this.state.currentTab; //本次滑动前的初始位置
146 | this.localState.startT = new Date().getTime(); //记录手指按下的开始时间
147 | this.localState.isMove = false; //是否产生滑动
148 | this.localState.isTouchEnd = false; //当前开始滑动
149 | }
150 | }
151 |
152 | touchMove(e){
153 | // e.preventDefault();
154 | let touch = e.touches[0];
155 | let deltaX = touch.pageX - this.localState.startX;
156 | let deltaY = touch.pageY - this.localState.startY;
157 | //如果X方向上的位移大于Y方向,则认为是左右滑动
158 | if (Math.abs(deltaX) > Math.abs(deltaY)){
159 | if ((this.handleLabelIndex(this.state.currentLabel) > 0 && deltaX > 0) || (this.handleLabelIndex(this.state.currentLabel) <= (this.state.labels.length - 2) && deltaX < 0)){
160 | //移动页面
161 | this.handleOpacity(0.2);
162 | this.localState.isMove = true;
163 | }
164 | this.localState.direction = deltaX > 0 ? "right" : "left"; //判断手指滑动的方向
165 | }
166 | }
167 |
168 | touchEnd(e){
169 | // e.preventDefault();
170 | //计算手指在屏幕上停留的时间
171 | let deltaT = new Date().getTime() - this.localState.startT;
172 | let index = 0;
173 | if (this.localState.isMove){ //发生了左右滑动
174 | //使用动画过渡让页面滑动到最终的位置
175 | if(deltaT < 300){ //如果停留时间小于300ms,则认为是快速滑动,无论滑动距离是多少,都停留到下一页
176 | index = this.localState.direction == 'left'? 1 : -1;
177 |
178 | }else {
179 | //如果滑动距离小于屏幕的50%,则退回到上一页
180 | if (Math.abs(this.localState.moveLength)/this.localState.pageWidth < 0.5){
181 | translate = this.localState.currentPosition-this.localState.moveLength;
182 |
183 | index = 0
184 |
185 | }else{
186 | //如果滑动距离大于屏幕的50%,则滑动到下一页
187 | index = this.localState.direction == 'left'? 1 : -1;
188 | }
189 | }
190 |
191 | this.chooseLabel(this.state.labels[this.handleLabelIndex(this.state.currentLabel) + index]);
192 |
193 | }
194 | }
195 |
196 | handleOpacity(opacity){
197 | this.setState({
198 | opacity: opacity
199 | })
200 | }
201 |
202 | handleLabelIndex(label){
203 | return this.state.labels.indexOf(label)
204 | }
205 |
206 | handleOnScroll(){
207 | let scrollY = window.scrollY;
208 | if(scrollY > 50){
209 | this.setState({
210 | headerFixed: true
211 | })
212 | }else {
213 | this.setState({
214 | headerFixed: false
215 | })
216 | }
217 | }
218 |
219 | render() {
220 | return (
221 |
222 |
223 |
224 |
225 | {this._renderLabels()}
226 |
227 |
230 |
231 |
232 | );
233 | }
234 | }
235 |
236 | export default IndexPage;
--------------------------------------------------------------------------------
/src/page/blog/index.scss:
--------------------------------------------------------------------------------
1 | .box{
2 | margin: 0 auto;
3 | max-width: 768px;
4 | min-height: 100%;
5 | overflow: hidden;
6 | background-color: #f6f6f6;
7 | }
8 |
9 | .labels{
10 | max-width: 768px;
11 | width: 100%;
12 | height: .48em;
13 | line-height: 0;
14 | overflow-x: auto;
15 | overflow-y: hidden;
16 | white-space: nowrap;
17 | background-color: #fff;
18 | box-shadow: 0 1px 3px rgba(26, 26, 26, 0.1);
19 |
20 | li{
21 | display: inline-block;
22 | padding: 0 .1em;
23 | vertical-align: middle;
24 |
25 | > div{
26 | display: block;
27 | padding-top: .02em;
28 | height: .44em;
29 | line-height: .44em;
30 | color: #666;
31 | cursor: pointer;
32 | border-bottom: .02em solid #fff;
33 | transition: color .3s ease-in;
34 |
35 | &.current{
36 | color: #00b0ab;
37 | border-color: #00b0ab;
38 | }
39 |
40 | > p{
41 | font-size: .16em;
42 | }
43 | }
44 | }
45 | }
46 |
47 | .list{
48 | margin-top: .1em;
49 | transition: opacity .6s ease;
50 | }
51 |
52 | .headerFixed{
53 | .labels{
54 | position: fixed;
55 | top: 0;
56 | left: 50%;
57 | transform: translateX(-50%);
58 | }
59 |
60 | .list{
61 | margin-top: .58em;
62 | }
63 | }
64 |
65 | @media only screen and (min-width: 768px) {
66 | .box{
67 | height: calc(100vh - .5em);
68 | font-size: 100px;
69 | border-left: 1px solid #e6e3e3;
70 | border-right: 1px solid #e6e3e3;
71 | }
72 | }
--------------------------------------------------------------------------------
/src/page/blog/search.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import s from './search.scss';
3 | import Actions from 'actions';
4 | import { Link, browserHistory } from 'react-router-dom';
5 | import Header from 'components/Header';
6 | import PostList from 'components/PostList';
7 |
8 | import utils from '../../../tools/utils';
9 |
10 | class SearchPage extends React.Component {
11 | constructor(){
12 | super();
13 | this.state = {
14 | list: []
15 | };
16 |
17 | this.getIssues = this.getIssues.bind(this);
18 | this.handleSearch = this.handleSearch.bind(this);
19 | }
20 |
21 | componentDidMount(){
22 | this.getIssues();
23 | }
24 |
25 | getIssues(){
26 | let keyWords = utils.getParameterByName('keyWords');
27 | document.title = `${keyWords} - 个人博客 - 吴胜斌 | simbawu`;
28 |
29 | let data = {
30 | query: `query {
31 | search(query:"${keyWords} repo:simbawus/blog", type: ISSUE, first: 10) {
32 | issueCount
33 | edges{
34 | cursor
35 | node{
36 | ... on Issue {
37 | title
38 | number
39 | bodyText
40 | updatedAt
41 | }
42 | }
43 | }
44 | }
45 | }`
46 | };
47 |
48 | Actions.getIssues(data).then((res) => {
49 | let list = res.data.data.search.edges;
50 | this.setState({
51 | list: list
52 | })
53 | })
54 | }
55 |
56 | handleSearch(){
57 | this.getIssues();
58 | }
59 |
60 | render() {
61 | return (
62 |
63 |
64 | { this.state.list.length > 0 ?
:
65 |
没搜到哦 换个词试试~
}
66 |
67 | );
68 | }
69 | }
70 |
71 | export default SearchPage;
--------------------------------------------------------------------------------
/src/page/blog/search.scss:
--------------------------------------------------------------------------------
1 | .noPostTips{
2 | margin-top: 3em;
3 | text-align: center;
4 | font-size: .14em;
5 | color: #666;
6 | }
--------------------------------------------------------------------------------
/src/page/index/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import s from './index.scss';
3 | import { Link } from 'react-router-dom';
4 |
5 | class IndexPage extends React.Component {
6 | constructor(){
7 | super();
8 | this.state = {
9 | chinese: true,
10 | introduce: {
11 | welcome: '朋友,你好!',
12 | name: '我是吴胜斌',
13 | work: '目前在上海担任前端工程师',
14 | more: '更多关于我的信息,请见下方。',
15 | blog: '博客'
16 | }
17 | };
18 |
19 | this.getLanguage = this.getLanguage.bind(this);
20 | this.changeLanguage = this.changeLanguage.bind(this);
21 | this.gotoUrl = this.gotoUrl.bind(this);
22 | }
23 |
24 | componentDidMount(){
25 | this.getLanguage();
26 | }
27 |
28 | getLanguage(){
29 | if(!(/zh/.test(navigator.language))){
30 | this.changeLanguage({}, false)
31 | }
32 | }
33 |
34 | changeLanguage(e, chinese){
35 | e.stopPropagation && e.stopPropagation();
36 | if(chinese){
37 | this.setState({
38 | chinese: chinese,
39 | introduce: {
40 | welcome: '朋友,你好!',
41 | name: '我是吴胜斌,',
42 | work: '目前在上海从事Web前端开发工作,',
43 | more: '更多关于我的信息,请见下方。',
44 | blog: '博客'
45 | }
46 | })
47 | }else {
48 | this.setState({
49 | chinese: chinese,
50 | introduce: {
51 | welcome: 'Hi Friend,',
52 | name: 'I’m Simba Wu.',
53 | work: 'A Front End Developer in Shanghai.',
54 | more: 'More about me at the bottom.',
55 | blog: 'Blog'
56 | }
57 | })
58 | }
59 | }
60 |
61 |
62 | gotoUrl(){
63 | this.props.history.push('/blog')
64 | }
65 |
66 |
67 | render() {
68 | return (
69 |
70 |
71 | {this.state.introduce.blog}
72 | this.changeLanguage(e, !this.state.chinese)}>中 / EN
73 |
74 |
75 |
76 |
77 |
{this.state.introduce.welcome}
78 |
79 | {this.state.introduce.name} {this.state.introduce.work} {this.state.introduce.more}
80 |
81 |
82 |
98 |
99 |
100 | );
101 | }
102 | }
103 |
104 | export default IndexPage;
--------------------------------------------------------------------------------
/src/page/index/index.scss:
--------------------------------------------------------------------------------
1 | .container{
2 | height: 100%;
3 | background: #78BBE6; /* fallback for old browsers */
4 | background: -webkit-linear-gradient(to bottom, #1B435D, #78BBE6); /* Chrome 10-25, Safari 5.1-6 */
5 | background: linear-gradient(to bottom, #1B435D, #78BBE6); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
6 | }
7 |
8 | .photo{
9 | position: relative;
10 | width: 100%;
11 | height: 2.4rem;
12 | background-image: url("http://images.simbawu.com/%E4%B8%AA%E4%BA%BA%E9%BB%91%E7%99%BD%E5%9B%BE.jpeg");
13 | background-size: cover;
14 | background-repeat: no-repeat;
15 | background-position: center;
16 | }
17 |
18 | .blogBtn{
19 | left: .1rem;
20 | }
21 |
22 | .languageBtn{
23 | right: .1rem;
24 | }
25 |
26 | .blogBtn, .languageBtn{
27 | position: absolute;
28 | top: .1rem;
29 | width: .6rem;
30 | height: .3rem;
31 | line-height: .3rem;
32 | text-align: center;
33 | font-size: .14rem;
34 | color: #fff;
35 | background-color: rgba(0, 0, 0, .2);
36 | border-radius: .08rem;
37 | border: 0;
38 | transition: all .3s ease;
39 |
40 | &:active{
41 | background-color: rgba(0, 0, 0, .6);
42 | }
43 | }
44 |
45 | .slogan {
46 | position: relative;
47 | width: .3rem;
48 | height: 100%;
49 | font-size: .14rem;
50 | color: #fff;
51 | background-color: #FF895D;
52 |
53 | &:after {
54 | position: absolute;
55 | content: 'Stay hungry, Stay foolish.';
56 | top: 50%;
57 | left: 50%;
58 | white-space: nowrap;
59 | }
60 | }
61 |
62 | .chineseSlogan{
63 | &:after{
64 | content: '成功不息,痴心不改';
65 | }
66 | }
67 |
68 | .introduce{
69 | padding: .3rem .2rem;
70 |
71 | p{
72 | font-size: .2rem;
73 | color: #FF895D;
74 | }
75 |
76 | div{
77 | margin-top: .2rem;
78 | line-height: .36rem;
79 | font-family: San Francisco;
80 | font-size: .21rem;
81 | color: #fff;
82 | }
83 | }
84 |
85 | .link{
86 | position: absolute;
87 | bottom: .2rem;
88 | left: 0;
89 | width: 100%;
90 |
91 | a{
92 | padding: 0 .04rem;
93 | }
94 | }
95 |
96 | .icon_link{
97 | display: flex;
98 | justify-content: center;
99 | align-items: center;
100 |
101 | a{
102 | height: .28rem;
103 | line-height: .28rem;
104 | font-size: 0;
105 |
106 | + a{
107 | margin-left: .06rem;
108 | }
109 | }
110 |
111 | i{
112 | display: inline-block;
113 | width: .28rem;
114 | height: .28rem;
115 | text-align: center;
116 | border-radius: 50%;
117 | }
118 |
119 | .icon_github{
120 | font-size: .28rem;
121 | color: #fff;
122 | }
123 |
124 | .icon_zhihu{
125 | font-size: .17rem;
126 | background-color: #fff;
127 | color: #80b4dc;
128 | }
129 | }
130 |
131 | .word_link{
132 | display: flex;
133 | justify-content: center;
134 | align-items: center;
135 | margin-top: .12rem;
136 |
137 | a{
138 | height: .12rem;
139 | line-height: .12rem;
140 | font-size: .14rem;
141 | color: #3cefe9;
142 |
143 | & + a{
144 | border-left: 1px solid #fff;
145 | }
146 |
147 | &:hover{
148 | text-decoration: underline;
149 | }
150 | }
151 | }
152 |
153 | @media (min-aspect-ratio: 1/1) {
154 | .container {
155 | display: flex;
156 | align-items: center;
157 | }
158 |
159 | .photo{
160 | flex: 1;
161 | height: 100%;
162 | }
163 |
164 | .slogan{
165 | position: relative;
166 | width: .3rem;
167 | height: 100%;
168 |
169 | &:after{
170 | transform: translate(-50%,-50%) rotate(-90deg);
171 | }
172 | }
173 |
174 | .about{
175 | position: relative;
176 | flex: 1;
177 | height: 100%;
178 | }
179 | }
180 |
181 | @media (max-aspect-ratio: 1/1) {
182 | .container {
183 | display: block;
184 | }
185 |
186 | .slogan {
187 | width: 100%;
188 | height: .3rem;
189 |
190 | &:after {
191 | transform: translate(-50%, -50%);
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | BrowserRouter as Router,
4 | Route,
5 | Link,
6 | Redirect,
7 | Switch
8 | } from 'react-router-dom';
9 |
10 | import Index from "./page/index/index";
11 | import Blog from "./page/blog/index";
12 | import Article from './page/blog/article';
13 | import Search from "./page/blog/search";
14 |
15 | const router = (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 |
28 | export default router;
--------------------------------------------------------------------------------
/tools/deploy.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const spawn = require('child_process').spawn;
3 | const webpackConfig = require('../config/webpack/webpack.config');
4 |
5 | new Promise((resolve, reject) => {
6 | webpack({ mode: 'production',
7 | ...webpackConfig
8 | }).run((err, stats) => {
9 | if (err) {
10 | reject(err);
11 | } else {
12 | console.log(stats.toString(webpackConfig.stats));
13 | resolve();
14 | }
15 | });
16 | }).then(() => {
17 | rumCommand('sh', [__dirname + '/release.sh'], (state) => {
18 | console.log('state:',state);
19 | });
20 | });
21 |
22 | function rumCommand(command, args, callBack) {
23 | let child = spawn(command, args);
24 | let response = '';
25 | child.stdout.on('data', function(buffer){
26 | response += buffer.toString();
27 | });
28 | child.stdout.on('end', function(){
29 | callBack(response);
30 | });
31 | }
--------------------------------------------------------------------------------
/tools/httpServer.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | axios.interceptors.request.use(config => {
4 | return config
5 | }, error => {
6 | return Promise.reject(error)
7 | });
8 |
9 |
10 | axios.interceptors.response.use(response => {
11 | return response
12 | }, error => {
13 | return Promise.resolve(error.response)
14 | });
15 |
16 | function errorState(response) {
17 | //错误处理
18 | }
19 |
20 | function successState(res) {
21 | //正确处理
22 | }
23 | const httpServer = (opts, data, baseURL, token) => {
24 |
25 | let Public = { //公共参数
26 |
27 | };
28 |
29 | let httpDefaultOpts = { //http默认配置
30 | method:opts.method,
31 | baseURL: baseURL,
32 | url: opts.url,
33 | timeout: 10000,
34 | params: Object.assign(Public, data),
35 | data: Object.assign(Public, data),
36 | headers: opts.method=='get' ? {
37 | "Authorization" : `Bearer ${token}`,
38 | "X-Requested-With": 'XMLHttpRequest',
39 | "Accept": "application/json",
40 | "Content-Type": "application/json; charset=UTF-8"
41 | } : {
42 | "Authorization" : `Bearer ${token}`,
43 | "X-Requested-With": "XMLHttpRequest",
44 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
45 | }
46 | };
47 |
48 | if(opts.method=='get'){
49 | delete httpDefaultOpts.data
50 | }else{
51 | delete httpDefaultOpts.params
52 | }
53 |
54 | let promise = new Promise(function(resolve, reject) {
55 | axios(httpDefaultOpts).then(
56 | (res) => {
57 | successState(res);
58 | resolve(res)
59 | }
60 | ).catch(
61 | (response) => {
62 | errorState(response);
63 | reject(response)
64 | }
65 | )
66 |
67 | });
68 |
69 | return promise
70 | };
71 |
72 | export default httpServer
--------------------------------------------------------------------------------
/tools/ip.js:
--------------------------------------------------------------------------------
1 | var os=require('os'),ip='',ifaces=os.networkInterfaces();
2 | for (var dev in ifaces) {
3 | ifaces[dev].forEach(function(details,alias){
4 | if (details.family=='IPv4') {
5 | ip = details.address;
6 | }
7 | });
8 | }
9 |
10 | module.exports = ip;
--------------------------------------------------------------------------------
/tools/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | /usr/bin/rsync -av \
4 | --delete \
5 | --exclude '.git' \
6 | --exclude '.DS_Store' \
7 | --exclude 'Runtime' \
8 | -e 'ssh -p 22' \
9 | ./dist/ root@115.28.222.218:/alidata/www/simbawu/simba/blog
10 |
--------------------------------------------------------------------------------
/tools/utils.js:
--------------------------------------------------------------------------------
1 | Date.prototype.format = function(fmt) {
2 | var o = {
3 | "M+" : this.getMonth()+1, //月份
4 | "d+" : this.getDate(), //日
5 | "h+" : this.getHours(), //小时
6 | "m+" : this.getMinutes(), //分
7 | "s+" : this.getSeconds(), //秒
8 | "q+" : Math.floor((this.getMonth()+3)/3), //季度
9 | "S" : this.getMilliseconds(), //毫秒
10 | // "W" : this.weekday()
11 | };
12 | if(/(y+)/.test(fmt))
13 | fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
14 | for(var k in o)
15 | if(new RegExp("("+ k +")").test(fmt))
16 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
17 | return fmt;
18 | };
19 |
20 | export default {
21 | autoAddEllipsis(pStr, pLen){
22 | let _ret = cutString(pStr, pLen);
23 | let _cutFlag = _ret.cutflag;
24 | let _cutStringn = _ret.cutstring;
25 |
26 | if ("1" == _cutFlag) {
27 | return _cutStringn + "...";
28 | } else {
29 | return _cutStringn;
30 | }
31 |
32 | function cutString(pStr, pLen) {
33 |
34 | // 原字符串长度
35 | var _strLen = pStr.length;
36 |
37 | var _tmpCode;
38 |
39 | var _cutString;
40 |
41 | // 默认情况下,返回的字符串是原字符串的一部分
42 | var _cutFlag = "1";
43 |
44 | var _lenCount = 0;
45 |
46 | var _ret = false;
47 |
48 | if (_strLen <= pLen/2) {
49 | _cutString = pStr;
50 | _ret = true;
51 | }
52 |
53 | if (!_ret) {
54 | for (var i = 0; i < _strLen ; i++ ) {
55 | if (isFull(pStr.charAt(i))) {
56 | _lenCount += 2;
57 | } else {
58 | _lenCount += 1;
59 | }
60 |
61 | if (_lenCount > pLen) {
62 | _cutString = pStr.substring(0, i);
63 | _ret = true;
64 | break;
65 | } else if (_lenCount == pLen) {
66 | _cutString = pStr.substring(0, i + 1);
67 | _ret = true;
68 | break;
69 | }
70 | }
71 | }
72 |
73 | if (!_ret) {
74 | _cutString = pStr;
75 | _ret = true;
76 | }
77 |
78 | if (_cutString.length == _strLen) {
79 | _cutFlag = "0";
80 | }
81 |
82 | return {"cutstring":_cutString, "cutflag":_cutFlag};
83 | }
84 |
85 | function isFull (pChar) {
86 | if ((pChar.charCodeAt(0) > 128)) {
87 | return true;
88 | } else {
89 | return false;
90 | }
91 | }
92 | },
93 | getParameterByName(name, url) {
94 | if (!url) url = window.location.href;
95 | name = name.replace(/[\[\]]/g, "\\$&");
96 | var regex = new RegExp("[?&]" + name + "(=([^]*)|&|#|$)"),
97 | results = regex.exec(url);
98 | if (!results) return null;
99 | if (!results[2]) return '';
100 | return decodeURIComponent(results[2].replace(/\+/g, " "));
101 | }
102 | }
--------------------------------------------------------------------------------