├── .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 | logo 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 |
67 | this.handleSearchBox(true)} onBlur={() => this.handleSearchBox(false)}/> 68 | 69 |
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 |
    128 |

    {item}

    129 |
    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 |
    228 | 229 |
    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 | 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 |
    83 | 91 |
    92 | Blog 93 | 掘金 94 | 简书 95 | segmentfault 96 |
    97 |
    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 | } --------------------------------------------------------------------------------