├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .vscode └── settings.json ├── README.md ├── _config.yml ├── babel.config.js ├── conf.json ├── gulpfile.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── index.html └── static │ ├── TweenMax.min.js │ └── iconfont │ ├── iconfont.css │ ├── iconfont.eot │ ├── iconfont.js │ ├── iconfont.svg │ ├── iconfont.ttf │ └── iconfont.woff ├── src ├── App.vue ├── api │ ├── app.js │ ├── blog.convert.js │ ├── blog.js │ ├── news.convert.js │ ├── news.js │ └── user.js ├── assets │ ├── icon │ │ └── 404.png │ ├── logo.png │ └── styles │ │ ├── base.scss │ │ └── variable.scss ├── components │ ├── 404 │ │ └── blogger404.vue │ ├── index.js │ ├── item │ │ ├── blogItem.vue │ │ ├── bloggerItem.vue │ │ ├── commentItem.vue │ │ └── newsItem.vue │ ├── layout │ │ ├── back.vue │ │ └── home.vue │ ├── loading │ │ └── loading.vue │ ├── markdown │ │ └── index.vue │ ├── notice │ │ └── updateApp.vue │ ├── share │ │ └── index.vue │ └── waves │ │ └── index.vue ├── config │ └── conf.js ├── filters │ └── index.js ├── language │ ├── cn.js │ ├── en.js │ └── index.js ├── main.js ├── registerServiceWorker.js ├── router │ ├── index.js │ └── router.js ├── store │ ├── app.js │ ├── index.js │ └── user.js ├── utils │ ├── $cookie.js │ ├── $http.js │ ├── $news.http.js │ ├── $official.http.js │ ├── $storage.js │ ├── native.js │ └── user.js └── views │ ├── About.vue │ ├── BlogApp.vue │ ├── BlogCollection.vue │ ├── BlogDetail.vue │ ├── BloggerRank.vue │ ├── BloggerSearch.vue │ ├── Home.vue │ ├── News.vue │ ├── NewsDetail.vue │ ├── Theme.vue │ └── User.vue └── vue.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | 6 | parserOptions: { 7 | parser: 'babel-eslint' 8 | }, 9 | 10 | env: { 11 | browser: true, 12 | node: true 13 | }, 14 | 15 | extends: [ 16 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 17 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 18 | 'plugin:vue/essential', 19 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 20 | 'standard' 21 | ], 22 | 23 | // required to lint *.vue files 24 | plugins: [ 25 | 'vue' 26 | ], 27 | 28 | // add your custom rules here 29 | rules: { 30 | rules: { 31 | 'no-var': 'error', 32 | 'init-declarations': 2, 33 | quotes: [ 34 | 'error', 35 | 'single' 36 | ], 37 | semi: [ 38 | 'error', 39 | 'never' 40 | ], 41 | 'no-extra-semi': 'error', 42 | 'linebreak-style': [ 43 | 'error', 44 | 'unix' 45 | ], 46 | indent: [ 47 | 'error', 48 | 2, 49 | { 50 | SwitchCase: 1 51 | } 52 | ], 53 | 'array-bracket-spacing': [ 54 | 2, 55 | 'never' 56 | ], 57 | 'block-scoped-var': 0, 58 | 'brace-style': [ 59 | 2, 60 | '1tbs', 61 | { 62 | allowSingleLine: true 63 | } 64 | ], 65 | camelcase: 2, 66 | 'comma-dangle': [ 67 | 2, 68 | 'never' 69 | ], 70 | 'comma-spacing': [ 71 | 2, 72 | { 73 | before: false, 74 | after: true 75 | } 76 | ], 77 | 'comma-style': [ 78 | 2, 79 | 'last' 80 | ], 81 | complexity: [ 82 | 2, 83 | 9 84 | ], 85 | 'computed-property-spacing': [ 86 | 2, 87 | 'never' 88 | ] 89 | }, 90 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 91 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 92 | }, 93 | 94 | 'extends': [ 95 | 'plugin:vue/essential', 96 | '@vue/standard' 97 | ] 98 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw* 21 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CnBlogApp 2 | 3 | 此项目,本地运行能够使用,本地运行所有接口都做了代理,图片也做了中转处理,此项目引用了部分cordova功能, 4 | 譬如原生复制,返回事件,Webview的cookie写入(Cordova中document.cookie不生效). 5 | 6 | 命令 7 | 8 | npm install 9 | npm run start 本地运行 10 | npm run build 本地编译 11 | 12 | 基于 vue-cli@3.0: https://cli.vuejs.org/ 13 | 14 | UI 库使用 vant: https://youzan.github.io/vant/#/zh-CN/intro/ 15 | 16 | /src/utils 下提供了 http 请求,localstorage 存储,cookie 操作 17 | 18 | 提供了this.push和this.goBack方法,调用这两个方法才能实现切换动画效果 19 | 20 | 监控了原始backbutton事件,让其调用提供的goBack方法 21 | 22 | 我已经将vue.config 里面的跨域配置去掉的,可以使用chrome插件crods domain来处理跨域问题。 23 | 24 | Cordova打包的时候需要引入如下Plugin,如果觉得运行慢可以自行引入inter的crosswalk或者腾讯的x5引擎提升性能 25 | 26 | 28 | 29 | 30 | 31 | 登录: 32 | 33 | ![Image text](https://images2018.cnblogs.com/blog/657942/201808/657942-20180823092205507-1862396053.png) 34 | 35 | 1.个人中心有登录按钮,登录功能里面有一个博客昵称和cookie输入框, 36 | 其中博客昵称只是用来获取用户信息跟用户登录相关操作没直接联系, 37 | 用于昵称请输入准确的不然可能无法匹配,到时获取错误的用户信息。【用户昵称请看我的截图箭头】 38 | 39 | 2.只有.CNBlogsCookie才是真正评论推荐功能需要使用的。.CNBlogsCookie请用Chrome登陆后在cookie里面查看。 40 | 41 | 3.登录Cookie会过期,如果过期了请重新更新 42 | 43 | 4.博客ID请输入完整准确的,不然接口可能匹配不到。 44 | 45 | 接口: 46 | 47 | 1.非登录的接口来源于: http://wcf.open.cnblogs.com/news/help / http://wcf.open.cnblogs.com/blog/help 48 | 49 | 2.登录后操作的接口来源于博客园PC断原有接口 50 | 51 | 实现功能: 52 | 53 | 1.新闻列表 54 | 55 | 2.博客列表 56 | 57 | 3.博客详情[评论功能,推荐功能] 58 | 59 | 4.新闻详情[新闻详情不提供评论和推荐功能,原因是新闻的接口,居然需要把所有的cookie都加进去, 60 | 61 | 而博客的操作只需要加入.CNBlogsCookie这个cookie,个人觉得没太大必要了。基本上也就只有博客评论和推荐的人多, 62 | 63 | 所以登录界面我也就只提供了.CNBlogsCookie的设置输入框。 64 | 65 | 66 | 下载链接: 67 | 68 | https://fir.im/cnblog 69 | 70 | 更新历史:【app发布历史,请前往下载链接自动更新】 71 | 72 | 2018/09/14 ---- 新增博客/新闻图片预览 73 | 74 | 2018/09/02 ---- [1]登录界面新增帮助提示 [2] 解决如果自己用户名被其他人的用户名包含了导致无法登录。譬如我叫做FrankZC如果还有一个叫做FrankZC92,我将无法登录 【此次版本更新不修改版本号,不对用户进行提醒,未有用户反馈】 75 | 76 | 2018/08/22 ---- 新增皮肤切换功能,部分图片修改,改用阿里iconfont维护 77 | 78 | 2018/08/18 ---- 新增了App版本更新检查,配置读取的是项目里面的conf.json文件。 79 | 80 | 2018/08/11 ---- 新增本地博客收藏和回复评论功能,本地收藏存储手机缓存里面。 81 | 82 | 2018/08/09 ---- 隐藏Mathjax.js加载界面提示,个人中心优化 83 | 84 | 2018/08/02 ---- 解决数学表达式不支持左右滑动展示 85 | 86 | 反馈: 87 | 88 | 1.有任何问题可以在Issue里面反馈 89 | 90 | 2.或者去我的博客下面评论. https://www.cnblogs.com/FourLeafCloverZc/p/9380895.html 91 | 92 | 打赏: 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": { 3 | "id": "2018.09.14", 4 | "message": "新增博客/新闻内容点击图片预览", 5 | "forceUpdate": false 6 | } 7 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var gulp = require('gulp') 3 | var GulpSSH = require('gulp-ssh') 4 | 5 | let config = { 6 | version: '1.0.0', 7 | ssh: { 8 | host: 'xxxx', // 服务器ip地址 9 | port: 22, // 端口号 10 | username: 'root', // 用户名 11 | password: 'xxxx' // 密码 12 | }, 13 | remoteDir: `/usr/local/xxx`, // 部署路径 14 | commands: [ 15 | `rm -rf ` // 部署前需要执行的脚本。譬如重新发布后需要清空发布包之前的js和css 16 | ] 17 | } 18 | 19 | var gulpSSH = new GulpSSH({ 20 | ignoreErrors: false, 21 | sshConfig: config.ssh 22 | }) 23 | 24 | gulp.task('default', ['deployFile'], function () {}) 25 | 26 | /** 27 | * 上传文件 28 | */ 29 | gulp.task('deployFile', ['execSSH'], () => { 30 | return gulp 31 | .src(['./dist/**']) 32 | .pipe(gulpSSH.dest(config.remoteDir)) 33 | }) 34 | 35 | /** 36 | * 执行命令 37 | */ 38 | gulp.task('execSSH', () => { 39 | console.log('删除服务器上现有文件...') 40 | return gulpSSH.shell(config.commands, { 41 | filePath: 'commands.log' 42 | }) 43 | .pipe(gulp.dest('logs')) 44 | }) 45 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | //"module": "amd", 6 | //"target": "ES6", 7 | "baseUrl": "./", 8 | "paths": { 9 | "@/*": ["src/*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-template", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "vue-cli-service build", 7 | "lint": "vue-cli-service lint", 8 | "deploy": "gulp", 9 | "start": "vue-cli-service serve" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.18.0", 13 | "register-service-worker": "^1.0.0", 14 | "vant": "^1.1.9", 15 | "vue": "^2.5.16", 16 | "vue-i18n": "^8.0.0", 17 | "vue-router": "^3.0.1", 18 | "vuex": "^3.0.1", 19 | "xmltojson": "git+https://github.com/metatribal/xmlToJSON.git" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "^3.0.0-beta.15", 23 | "@vue/cli-plugin-eslint": "^3.0.0-rc.3", 24 | "@vue/cli-plugin-pwa": "^3.0.0-beta.15", 25 | "@vue/cli-service": "^3.0.0-beta.15", 26 | "@vue/eslint-config-standard": "^3.0.0-rc.3", 27 | "gulp": "^3.9.1", 28 | "gulp-ssh": "^0.7.0", 29 | "node-sass": "^4.9.0", 30 | "sass-loader": "^7.0.1", 31 | "vue-template-compiler": "^2.5.16" 32 | }, 33 | "browserslist": [ 34 | "> 1%", 35 | "last 2 versions", 36 | "not ie <= 8" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Cnblog 9 | 14 | 15 | 16 | 17 | 18 | 72 | 73 | 74 | 75 | 78 |
79 | 80 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /public/static/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('iconfont.eot?t=1534902662584'); /* IE9*/ 4 | src: url('iconfont.eot?t=1534902662584#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'), 6 | url('iconfont.ttf?t=1534902662584') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont.svg?t=1534902662584#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-like:before { content: "\e799"; } 19 | 20 | .icon-emaxcitygerenxinxitubiaoji02:before { content: "\e61d"; } 21 | 22 | .icon-update:before { content: "\ed7d"; } 23 | 24 | .icon-setting:before { content: "\e608"; } 25 | 26 | .icon-blogger:before { content: "\e6a4"; } 27 | 28 | .icon-comment:before { content: "\e634"; } 29 | 30 | .icon-exit:before { content: "\e7cb"; } 31 | 32 | .icon-about:before { content: "\e602"; } 33 | 34 | .icon-share:before { content: "\e615"; } 35 | 36 | .icon-ego-blog:before { content: "\e632"; } 37 | 38 | .icon-account:before { content: "\e603"; } 39 | 40 | .icon-news_hot:before { content: "\e622"; } 41 | 42 | .icon-qzone:before { content: "\e604"; } 43 | 44 | .icon-paihangbang:before { content: "\e66e"; } 45 | 46 | .icon-account1:before { content: "\e649"; } 47 | 48 | .icon-theme:before { content: "\e600"; } 49 | 50 | -------------------------------------------------------------------------------- /public/static/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frank-C-Zhang/CNBlogApp/74e17454688284145a31d5f512acc6fee9101f36/public/static/iconfont/iconfont.eot -------------------------------------------------------------------------------- /public/static/iconfont/iconfont.js: -------------------------------------------------------------------------------- 1 | (function(window){var svgSprite='';var script=function(){var scripts=document.getElementsByTagName("script");return scripts[scripts.length-1]}();var shouldInjectCss=script.getAttribute("data-injectcss");var ready=function(fn){if(document.addEventListener){if(~["complete","loaded","interactive"].indexOf(document.readyState)){setTimeout(fn,0)}else{var loadFn=function(){document.removeEventListener("DOMContentLoaded",loadFn,false);fn()};document.addEventListener("DOMContentLoaded",loadFn,false)}}else if(document.attachEvent){IEContentLoaded(window,fn)}function IEContentLoaded(w,fn){var d=w.document,done=false,init=function(){if(!done){done=true;fn()}};var polling=function(){try{d.documentElement.doScroll("left")}catch(e){setTimeout(polling,50);return}init()};polling();d.onreadystatechange=function(){if(d.readyState=="complete"){d.onreadystatechange=null;init()}}}};var before=function(el,target){target.parentNode.insertBefore(el,target)};var prepend=function(el,target){if(target.firstChild){before(el,target.firstChild)}else{target.appendChild(el)}};function appendSvg(){var div,svg;div=document.createElement("div");div.innerHTML=svgSprite;svgSprite=null;svg=div.getElementsByTagName("svg")[0];if(svg){svg.setAttribute("aria-hidden","true");svg.style.position="absolute";svg.style.width=0;svg.style.height=0;svg.style.overflow="hidden";prepend(svg,document.body)}}if(shouldInjectCss&&!window.__iconfont__svg__cssinject__){window.__iconfont__svg__cssinject__=true;try{document.write("")}catch(e){console&&console.log(e)}}ready(appendSvg)})(window) -------------------------------------------------------------------------------- /public/static/iconfont/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /public/static/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frank-C-Zhang/CNBlogApp/74e17454688284145a31d5f512acc6fee9101f36/public/static/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /public/static/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frank-C-Zhang/CNBlogApp/74e17454688284145a31d5f512acc6fee9101f36/public/static/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 50 | 51 | 95 | -------------------------------------------------------------------------------- /src/api/app.js: -------------------------------------------------------------------------------- 1 | import $http from '../utils/$http' 2 | export function getConf () { 3 | return $http.get('https://raw.githubusercontent.com/FourLeafClover/CNBlogApp/master/conf.json') 4 | } 5 | -------------------------------------------------------------------------------- /src/api/blog.convert.js: -------------------------------------------------------------------------------- 1 | 2 | let getText = function (obj) { 3 | return (obj && obj._text) ? obj._text : '' 4 | } 5 | 6 | let getBlogApp = function (item) { 7 | let blogApp = getText(item.blogapp) 8 | if (blogApp.length === 0) { 9 | let link = item.link._attr.href._value 10 | if (link) { 11 | blogApp = link.split('/p/')[0].replace('http://www.cnblogs.com/', '') 12 | } 13 | } 14 | return blogApp 15 | } 16 | 17 | export function getHomePageConvert (data) { 18 | let entry = data.feed.entry 19 | let result = [] 20 | entry.map(item => { 21 | try { 22 | let convertItem = { 23 | id: getText(item.id), 24 | author: { 25 | name: getText(item.author.name), 26 | avatar: getText(item.author.avatar), 27 | uri: getText(item.author.uri), 28 | blogapp: getBlogApp(item) 29 | }, 30 | blogapp: getBlogApp(item), 31 | summary: getText(item.summary), 32 | views: getText(item.views), 33 | title: getText(item.title), 34 | published: getText(item.published), 35 | comments: getText(item.comments), 36 | link: item.link._attr.href._value, 37 | diggs: getText(item.diggs) ? getText(item.diggs) : 0 38 | } 39 | result.push(convertItem) 40 | } catch (error) { 41 | } 42 | }) 43 | return result 44 | } 45 | 46 | export function getCommentConvert (data) { 47 | let entry = data.feed.entry 48 | if (entry) { 49 | if (!Array.isArray(entry)) { 50 | entry = [entry] 51 | } 52 | return entry.map(item => { 53 | return { 54 | id: getText(item.id), 55 | author: { 56 | name: getText(item.author.name), 57 | avatar: getText(item.author.uri) 58 | }, 59 | content: getText(item.content), 60 | published: getText(item.published), 61 | updated: getText(item.updated) 62 | } 63 | }) 64 | } else { 65 | return null 66 | } 67 | } 68 | 69 | export function bloggerConvert (data) { 70 | const entry = data.feed.entry 71 | if (entry) { 72 | if (Array.isArray(entry)) { 73 | return entry.map(item => { 74 | return { 75 | blogapp: getText(item.blogapp), 76 | postcount: getText(item.postcount), 77 | avatar: getText(item.avatar), 78 | name: getText(item.title), 79 | updated: getText(item.updated) 80 | } 81 | }) 82 | } else { 83 | return [ 84 | { 85 | blogapp: getText(entry.blogapp), 86 | postcount: getText(entry.postcount), 87 | avatar: getText(entry.avatar), 88 | name: getText(entry.title), 89 | updated: getText(entry.updated) 90 | } 91 | ] 92 | } 93 | } else { 94 | return null 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/api/blog.js: -------------------------------------------------------------------------------- 1 | import $http from '../utils/$http' 2 | import { getHomePageConvert, getCommentConvert, userConvert, bloggerConvert } from '@/api/blog.convert' 3 | import Axios from '../../node_modules/axios' 4 | const xmltojson = require('xmltojson') 5 | const options = { 6 | mergeCDATA: true, 7 | grokAttr: true, 8 | grokText: true, 9 | normalize: true, 10 | xmlns: true, 11 | namespaceKey: '_ns', 12 | textKey: '_text', 13 | valueKey: '_value', 14 | attrKey: '_attr', 15 | cdataKey: '_cdata', 16 | attrsAsObject: true, 17 | stripAttrPrefix: true, 18 | stripElemPrefix: true, 19 | childrenAsArray: false 20 | } 21 | export function getHomePage (page, pageSize = 50) { 22 | return $http.get(`/blog/sitehome/paged/${page}/${pageSize}`).then((res) => { 23 | const data = getHomePageConvert(xmltojson.parseString(res, options)) 24 | return Promise.resolve(data) 25 | }).catch((err) => { 26 | return Promise.reject(err) 27 | }) 28 | } 29 | 30 | export function get48Top () { 31 | return $http.get('/blog/48HoursTopViewPosts/1000').then(res => { 32 | const data = getHomePageConvert(xmltojson.parseString(res, options)) 33 | return Promise.resolve(data) 34 | }).catch(err => { 35 | return Promise.reject(err) 36 | }) 37 | } 38 | 39 | export function get10TopDigg () { 40 | return $http.get('blog/TenDaysTopDiggPosts/1000').then(res => { 41 | const data = getHomePageConvert(xmltojson.parseString(res, options)) 42 | return Promise.resolve(data) 43 | }).catch(err => { 44 | return Promise.reject(err) 45 | }) 46 | } 47 | 48 | export function loadBlogBody (id) { 49 | return $http.get(`/blog/post/body/${id}`).then(res => { 50 | const data = res 51 | return Promise.resolve(data) 52 | }).catch(err => { 53 | return Promise.reject(err) 54 | }) 55 | } 56 | 57 | export function getBlogComment (id, page, pageSize) { 58 | return $http.get(`/blog/post/${id}/comments/${page}/${pageSize}`).then(res => { 59 | const data = getCommentConvert(xmltojson.parseString(res, options)) 60 | return Promise.resolve(data == null ? [] : data) 61 | }).catch(err => { 62 | return Promise.reject(err) 63 | }) 64 | } 65 | 66 | export function loadUser (name) { 67 | return $http.get(`/blog/bloggers/search?t=${name}`).then(res => { 68 | const data = bloggerConvert(xmltojson.parseString(res, options)) 69 | let user = data.find(item => item.name.toLocaleLowerCase() === name.toLocaleLowerCase()) 70 | return Promise.resolve(user) 71 | }).catch(err => { 72 | return Promise.reject(err) 73 | }) 74 | } 75 | 76 | export function loadBlogApp (blogapp, page, pageSize) { 77 | return $http.get(`/blog/u/${blogapp}/posts/${page}/${pageSize}`).then(res => { 78 | const data = getHomePageConvert(xmltojson.parseString(res, options)) 79 | return Promise.resolve(data) 80 | }).catch(err => { 81 | return Promise.reject(err) 82 | }) 83 | } 84 | 85 | export function loadBloggerRank (count) { 86 | return $http.get(`/blog/bloggers/recommend/1/${count}`).then(res => { 87 | const data = bloggerConvert(xmltojson.parseString(res, options)) 88 | return Promise.resolve(data) 89 | }).catch(err => { 90 | return Promise.reject(err) 91 | }) 92 | } 93 | 94 | export function searchBloggers (keyword) { 95 | return $http.get(`/blog/bloggers/search?t=${keyword}`).then(res => { 96 | const data = bloggerConvert(xmltojson.parseString(res, options)) 97 | return Promise.resolve(data) 98 | }).catch(err => { 99 | return Promise.reject(err) 100 | }) 101 | } 102 | 103 | // WCF获取body接口不稳定.直接从博客园PC端通过连接获取博文body 104 | export function loadBlogBodyFromPC (url) { 105 | return Axios.get(url).then(res => { 106 | let body = '' 107 | if (res.data) { 108 | let element = document.createElement('div') 109 | element.innerHTML = res.data 110 | body = element.querySelector('#cnblogs_post_body').innerHTML 111 | /* diggCount = Number(element.querySelector('#digg_count').innerHTML) 112 | buryCount = Number(element.querySelector('#bury_count').innerHTML) 113 | let diggElem = element.querySelector('#digg_tips') 114 | if (diggElem) { 115 | isDigg = diggElem.html().indexOf('您已推荐') >= 0 116 | isBury = diggElem.html().indexOf('您已反对') >= 0 117 | } */ 118 | } 119 | return Promise.resolve(body) 120 | }).catch(err => { 121 | return Promise.reject(err) 122 | }) 123 | } 124 | -------------------------------------------------------------------------------- /src/api/news.convert.js: -------------------------------------------------------------------------------- 1 | let getText = function (obj) { 2 | return (obj && obj._text) ? obj._text : '' 3 | } 4 | 5 | export function newsItemConvert (data) { 6 | let entry = data.feed.entry 7 | if (entry) { 8 | return entry.map(item => { 9 | return { 10 | id: getText(item.id), 11 | sourceName: getText(item.sourceName), 12 | summary: getText(item.summary), 13 | title: getText(item.title), 14 | updated: getText(item.updated), 15 | views: getText(item.views), 16 | published: getText(item.published), 17 | diggs: getText(item.diggs), 18 | comments: getText(item.comments) 19 | } 20 | }) 21 | } else { 22 | return [] 23 | } 24 | } 25 | 26 | export function newsCommentConvert (data) { 27 | let entry = data.feed.entry 28 | if (entry) { 29 | return entry.map(item => { 30 | return { 31 | id: getText(item.id), 32 | author: { 33 | name: getText(item.author.name), 34 | avatar: getText(item.author.uri) 35 | }, 36 | content: getText(item.content), 37 | published: getText(item.published), 38 | updated: getText(item.updated) 39 | } 40 | }) 41 | } else { 42 | return [] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/api/news.js: -------------------------------------------------------------------------------- 1 | import { newsItemConvert, newsCommentConvert } from '@/api/news.convert' 2 | import $http from '../utils/$http' 3 | const xmltojson = require('xmltojson') 4 | const options = { 5 | mergeCDATA: true, 6 | grokAttr: true, 7 | grokText: true, 8 | normalize: true, 9 | xmlns: true, 10 | namespaceKey: '_ns', 11 | textKey: '_text', 12 | valueKey: '_value', 13 | attrKey: '_attr', 14 | cdataKey: '_cdata', 15 | attrsAsObject: true, 16 | stripAttrPrefix: true, 17 | stripElemPrefix: true, 18 | childrenAsArray: false 19 | } 20 | export function getLastNews (page, pageSize = 50) { 21 | return $http.get(`/news/recent/paged/${page}/${pageSize}`).then((res) => { 22 | const data = newsItemConvert(xmltojson.parseString(res, options)) 23 | return Promise.resolve(data) 24 | }).catch((err) => { 25 | return Promise.reject(err) 26 | }) 27 | } 28 | 29 | export function getRecommendNews (page, pageSize) { 30 | return $http.get(`/news/recommend/paged/${page}/${pageSize}`).then((res) => { 31 | const data = newsItemConvert(xmltojson.parseString(res, options)) 32 | return Promise.resolve(data) 33 | }).catch((err) => { 34 | return Promise.reject(err) 35 | }) 36 | } 37 | 38 | export function getHotNews () { 39 | return $http.get(`/news/hot/100`).then((res) => { 40 | const data = newsItemConvert(xmltojson.parseString(res, options)) 41 | return Promise.resolve(data) 42 | }).catch((err) => { 43 | return Promise.reject(err) 44 | }) 45 | } 46 | 47 | export function getNewsDetail (id) { 48 | return $http.get(`/news/item/${id}`).then((res) => { 49 | return Promise.resolve(res) 50 | }).catch((err) => { 51 | return Promise.reject(err) 52 | }) 53 | } 54 | 55 | export function getNewsComment (id, page, pageSize) { 56 | return $http.get(`/news/item/${id}/comments/${page}/${pageSize}`).then((res) => { 57 | const data = newsCommentConvert(xmltojson.parseString(res, options)) 58 | return Promise.resolve(data) 59 | }).catch((err) => { 60 | return Promise.reject(err) 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import officalHttp from '@/utils/$official.http' 2 | import newsHttp from '@/utils/$news.http' 3 | 4 | export function addComment (blogapp, postid, content, parentCommentId = 0) { 5 | // {"blogApp":"FourLeafCloverZc","postId":6118617,"body":"mark1","parentCommentId":0} 6 | return officalHttp({ 7 | method: 'post', 8 | url: '/mvc/PostComment/Add.aspx', 9 | data: { 10 | blogApp: blogapp, 11 | postid: postid, 12 | body: content, 13 | parentCommentId: parentCommentId 14 | } 15 | }) 16 | } 17 | 18 | export function voteBlog (blogapp, postid, isDigg) { 19 | return officalHttp({ 20 | method: 'post', 21 | url: '/mvc/vote/VoteBlogPost.aspx', 22 | data: { 23 | blogApp: blogapp, 24 | postid: postid, 25 | voteType: isDigg ? 'Digg' : 'Bury', 26 | isAbandoned: false 27 | } 28 | }) 29 | } 30 | 31 | export function addNewsComment (content, contentid) { 32 | return newsHttp({ 33 | url: '/Comment/InsertComment', 34 | method: 'post', 35 | data: { 36 | content: content, 37 | contentId: contentid, 38 | parentCommentId: '' 39 | } 40 | }) 41 | } 42 | 43 | export function voteNews (contentId) { 44 | return newsHttp({ 45 | url: '/News/VoteNews', 46 | method: 'post', 47 | data: { 48 | contentId: contentId, 49 | action: 'agree' 50 | } 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /src/assets/icon/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frank-C-Zhang/CNBlogApp/74e17454688284145a31d5f512acc6fee9101f36/src/assets/icon/404.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frank-C-Zhang/CNBlogApp/74e17454688284145a31d5f512acc6fee9101f36/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/styles/base.scss: -------------------------------------------------------------------------------- 1 | @import './variable.scss'; 2 | * { 3 | -webkit-text-size-adjust: none !important; 4 | -webkit-touch-callout: none !important; 5 | -webkit-user-select: none !important; 6 | user-select: none !important; 7 | } 8 | 9 | html, 10 | body, 11 | div, 12 | ul, 13 | li, 14 | h1, 15 | h2, 16 | h3, 17 | h4, 18 | h5, 19 | h6, 20 | p, 21 | dl, 22 | dt, 23 | dd, 24 | ol, 25 | form, 26 | input, 27 | textarea, 28 | th, 29 | td, 30 | select { 31 | margin: 0; 32 | padding: 0; 33 | } 34 | 35 | html,body{ 36 | height: 100%; 37 | width: 100%; 38 | overflow-x: hidden; 39 | } 40 | 41 | div { 42 | -webkit-overflow-scrolling: touch; 43 | box-sizing: border-box; 44 | } 45 | div, 46 | video, 47 | img { 48 | outline-width: 0px; 49 | vertical-align: top; 50 | } 51 | 52 | .van-tab--active { 53 | @include themify{ 54 | color:themed('color') 55 | }; 56 | font-size: 18px; 57 | } 58 | .van-tab:active{ 59 | background-color: white 60 | } 61 | .van-tabs__line{ 62 | display: none; 63 | } 64 | 65 | .markdown{ 66 | width: 100%; 67 | padding: 0 10px; 68 | box-sizing: border-box; 69 | display: block; 70 | word-break: break-all; 71 | letter-spacing: 0.1em; 72 | h1,h2,h3,h4,h5,h6{ 73 | margin: 10px 0px; 74 | font-size: 18px; 75 | padding: 0px; 76 | } 77 | p{ 78 | color: gray; 79 | margin: 10px 0px; 80 | line-height: 22px; 81 | font-size: 16px; 82 | margin-left: 0px !important; 83 | } 84 | img{ 85 | width: 100%; 86 | padding: 0 important; 87 | margin: 10px 0px !important; 88 | height: auto !important; 89 | } 90 | .cnblogs_code{ 91 | box-shadow: 0px 0px 5px #c9c9c9; 92 | padding: 10px; 93 | box-sizing: border-box; 94 | } 95 | pre{ 96 | width: 100%; 97 | box-sizing: border-box; 98 | overflow-x: auto; 99 | box-shadow: 1 1 2px gray; 100 | margin: 0 !important; 101 | padding: 0 !important; 102 | } 103 | li{ 104 | margin-left: 10px !important; 105 | padding: 0px !important; 106 | } 107 | } 108 | 109 | 110 | .van-switch--on{ 111 | @include themify{ 112 | background-color:themed('color') 113 | }; 114 | } 115 | 116 | .van-button--primary{ 117 | @include themify{ 118 | background-color:themed('color'); 119 | border:1px solid themed('color') 120 | } 121 | } -------------------------------------------------------------------------------- /src/assets/styles/variable.scss: -------------------------------------------------------------------------------- 1 | $themes: ( 2 | dodgerblue: ( 3 | color:dodgerblue 4 | ), 5 | green:( 6 | color:green 7 | ), 8 | red:( 9 | color:red 10 | ), 11 | gray:( 12 | color:gray 13 | ), 14 | purple:( 15 | color:purple 16 | ), 17 | orange:( 18 | color:orange 19 | ) 20 | ); 21 | 22 | @mixin themify($themes: $themes) { 23 | @each $theme, $map in $themes { 24 | .theme-#{$theme} & { 25 | $theme-map: () !global; 26 | @each $key, $submap in $map { 27 | $value: map-get(map-get($themes, $theme), '#{$key}'); 28 | $theme-map: map-merge($theme-map, ($key: $value)) !global; 29 | } 30 | @content; 31 | $theme-map: null !global; 32 | } 33 | } 34 | } 35 | 36 | @function themed($key) { 37 | @return map-get($theme-map, $key); 38 | } 39 | 40 | .van-switch--on{ 41 | background-color: !important; 42 | } 43 | 44 | .van-button--primary{ 45 | @include themify{ 46 | background-color:themed('color') !important; 47 | border: 1px solid themed('color') !important; 48 | } 49 | } 50 | 51 | .van-icon{ 52 | @include themify{ 53 | color:themed('color') !important; 54 | } 55 | } 56 | 57 | .van-nav-bar__title{ 58 | @include themify{ 59 | color:themed('color') !important; 60 | } 61 | } -------------------------------------------------------------------------------- /src/components/404/blogger404.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 32 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import home from './layout/home.vue' 3 | import blogItem from './item/blogItem' 4 | import newsItem from './item/newsItem' 5 | import markdown from './markdown' 6 | import commentItem from './item/commentItem' 7 | import loading from './loading/loading' 8 | import back from './layout/back' 9 | import share from './share' 10 | import bloggerItem from './item/bloggerItem' 11 | import empty from './404/blogger404.vue' 12 | import waves from './waves' 13 | import updateApp from './notice/updateApp' 14 | Vue.component(waves.name, waves) 15 | Vue.component(home.name, home) 16 | Vue.component(blogItem.name, blogItem) 17 | Vue.component(newsItem.name, newsItem) 18 | Vue.component(markdown.name, markdown) 19 | Vue.component(commentItem.name, commentItem) 20 | Vue.component(loading.name, loading) 21 | Vue.component(back.name, back) 22 | Vue.component(share.name, share) 23 | Vue.component(bloggerItem.name, bloggerItem) 24 | Vue.component(empty.name, empty) 25 | Vue.component(waves.name, waves) 26 | Vue.component(updateApp.name, updateApp) 27 | -------------------------------------------------------------------------------- /src/components/item/blogItem.vue: -------------------------------------------------------------------------------- 1 | 15 | 58 | 89 | -------------------------------------------------------------------------------- /src/components/item/bloggerItem.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 31 | 32 | 77 | -------------------------------------------------------------------------------- /src/components/item/commentItem.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 33 | 34 | 77 | -------------------------------------------------------------------------------- /src/components/item/newsItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | 28 | 59 | -------------------------------------------------------------------------------- /src/components/layout/back.vue: -------------------------------------------------------------------------------- 1 | 13 | 28 | 43 | -------------------------------------------------------------------------------- /src/components/layout/home.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 50 | 51 | 59 | 71 | -------------------------------------------------------------------------------- /src/components/loading/loading.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 73 | -------------------------------------------------------------------------------- /src/components/markdown/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 69 | 70 | 278 | -------------------------------------------------------------------------------- /src/components/notice/updateApp.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 45 | 46 | 55 | -------------------------------------------------------------------------------- /src/components/share/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 54 | 67 | -------------------------------------------------------------------------------- /src/components/waves/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 117 | 118 | 124 | -------------------------------------------------------------------------------- /src/config/conf.js: -------------------------------------------------------------------------------- 1 | // export const WCF_API = process.env.NODE_ENV === 'deveploment' ? '' : '' 2 | // export const OFFICAL_API = process.env.NODE_ENV === 'deveploment' ? '' : '' 3 | export const ENV = 'prod' // process.env.NODE_ENV 4 | export const WCF_API = ENV === 'development' ? '' : 'http://wcf.open.cnblogs.com' 5 | export const OFFICAL_API = ENV === 'development' ? '' : 'https://www.cnblogs.com' 6 | export const NEWS_API = ENV === 'development' ? '' : 'https://news.cnblogs.com' 7 | export const Version = '2018.09.14' 8 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { ENV } from '@/config/conf' 3 | const dateFormat = (str) => { 4 | var date = new Date(str) 5 | var dateGetTime = date.getTime() 6 | var nowGetTime = new Date().getTime() 7 | if ((nowGetTime - dateGetTime) < (60 * 1000)) { 8 | return '1分钟前' 9 | } else if ((nowGetTime - dateGetTime) < (60 * 60 * 1000)) { 10 | return '1小时内发布' 11 | } else { 12 | return date.toLocaleDateString() 13 | } 14 | } 15 | 16 | const imgConvert = (str) => { 17 | // 开发模式图片要做中转,不然没有权限访问 18 | if (ENV === 'development') { 19 | str = str.replace('http://', '') 20 | return `https://images.weserv.nl/?url=${str}` 21 | } else { 22 | return str 23 | } 24 | } 25 | 26 | Vue.filter('dateFormat', dateFormat) 27 | Vue.filter('imgConvert', imgConvert) 28 | -------------------------------------------------------------------------------- /src/language/cn.js: -------------------------------------------------------------------------------- 1 | export default { 2 | App: { 3 | Home: '首页', 4 | About: '关于', 5 | Back: '返回' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/language/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | App: { 3 | Home: 'Home', 4 | About: 'About', 5 | Back: 'Back' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/language/index.js: -------------------------------------------------------------------------------- 1 | import en from './en' 2 | import cn from './cn' 3 | import VueI18n from 'vue-i18n' 4 | import Vue from 'vue' 5 | import { getItem } from '@/utils/$storage' 6 | Vue.use(VueI18n) 7 | 8 | const messages = { 9 | en: en, 10 | cn: cn 11 | } 12 | let storeageLocale = getItem('vue.locale') 13 | const i18n = new VueI18n({ 14 | locale: storeageLocale || 'cn', 15 | messages 16 | }) 17 | 18 | export default i18n 19 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import './registerServiceWorker' 6 | import vant from 'vant' 7 | import 'vant/lib/vant-css/index.css' 8 | import i18n from './language' 9 | require('./filters') 10 | require('./components') 11 | require('./assets/styles/base.scss') 12 | Vue.prototype.push = function (location) { 13 | this.$store.dispatch('app/UPDATE_PAGEANIMATION', 1) 14 | this.$router.push(location) 15 | } 16 | Vue.prototype.goBack = function (index) { 17 | this.$store.dispatch('app/UPDATE_PAGEANIMATION', -1) 18 | this.$router.go(index) 19 | } 20 | Vue.use(vant) 21 | Vue.config.productionTip = false 22 | let $vm = new Vue({ 23 | router, 24 | i18n, 25 | store, 26 | render: h => h(App) 27 | }).$mount('#app') 28 | window.$vm = $vm 29 | document.addEventListener('deviceready', function () { 30 | document.addEventListener('backbutton', function () { 31 | if (window.$vm.$router.currentRoute.name === 'page-home') { 32 | window.navigator.app.exitApp() 33 | } else { 34 | window.$vm.goBack(-1) 35 | } 36 | }, false) 37 | }, false) 38 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { register } from 'register-service-worker' 3 | 4 | if (process.env.NODE_ENV === 'production') { 5 | register(`${process.env.BASE_URL}service-worker.js`, { 6 | ready () { 7 | console.log( 8 | 'App is being served from cache by a service worker.\n' + 9 | 'For more details, visit https://goo.gl/AFskqB' 10 | ) 11 | }, 12 | cached () { 13 | console.log('Content has been cached for offline use.') 14 | }, 15 | updated () { 16 | console.log('New content is available; please refresh.') 17 | }, 18 | offline () { 19 | console.log('No internet connection found. App is running in offline mode.') 20 | }, 21 | error (error) { 22 | console.error('Error during service worker registration:', error) 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import config from './router' 4 | Vue.use(Router) 5 | // import store from '@/store' 6 | let routers = new Router({ 7 | routes: config 8 | }) 9 | 10 | routers.beforeEach((to, from, next) => { 11 | next() 12 | }) 13 | 14 | export default routers 15 | -------------------------------------------------------------------------------- /src/router/router.js: -------------------------------------------------------------------------------- 1 | let configs = [{ 2 | path: '/', 3 | name: 'page-home', 4 | component: () => 5 | import('@/views/Home.vue'), 6 | meta: { 7 | needAuth: false, 8 | cache: true 9 | } 10 | }, 11 | { 12 | path: '/user', 13 | name: 'page-user', 14 | component: () => 15 | import('@/views/User.vue'), 16 | meta: { 17 | needAuth: false, 18 | cache: true 19 | } 20 | }, { 21 | path: '/news', 22 | name: 'page-news', 23 | component: () => 24 | import('@/views/News.vue'), 25 | meta: { 26 | needAuth: false, 27 | cache: true 28 | } 29 | }, { 30 | path: '/blogdetail', 31 | name: 'page-blogdetail', 32 | component: () => import('@/views/BlogDetail.vue'), 33 | meta: { 34 | needAuth: false, 35 | cache: false 36 | } 37 | }, { 38 | path: '/newsdetail', 39 | name: 'page-newsdetail', 40 | component: () => import('@/views/NewsDetail.vue'), 41 | meta: { 42 | needAuth: false, 43 | cache: false 44 | } 45 | }, { 46 | path: '/blogapp', 47 | name: 'page-blogapp', 48 | component: () => import('@/views/BlogApp.vue'), 49 | meta: { 50 | needAuth: false, 51 | cache: true 52 | } 53 | }, { 54 | path: '/about', 55 | name: 'page-about', 56 | component: () => import('@/views/About.vue'), 57 | meta: { 58 | needAuth: false, 59 | cache: true 60 | } 61 | }, { 62 | path: '/bloggerrank', 63 | name: 'page-bloggerrank', 64 | component: () => import('@/views/BloggerRank.vue'), 65 | meta: { 66 | needAuth: false, 67 | cache: true 68 | } 69 | }, { 70 | path: '/bloggersearch', 71 | name: 'page-bloggersearch', 72 | component: () => import('@/views/BloggerSearch.vue'), 73 | meta: { 74 | needAuth: false, 75 | cache: true 76 | } 77 | }, { 78 | path: '/collection', 79 | name: 'page-blogCollection', 80 | component: () => import('@/views/BlogCollection'), 81 | meta: { 82 | needAuth: false, 83 | cache: true 84 | } 85 | }, { 86 | path: '/theme', 87 | name: 'page-theme', 88 | component: () => import('@/views/Theme'), 89 | meta: { 90 | needAuth: false, 91 | cache: true 92 | } 93 | } 94 | ] 95 | 96 | export default configs 97 | -------------------------------------------------------------------------------- /src/store/app.js: -------------------------------------------------------------------------------- 1 | import { getItem, setItem } from '@/utils/$storage' 2 | import { Version } from '@/config/conf' 3 | import { getConf } from '@/api/app' 4 | let theme = getItem('cnblog.theme') 5 | const state = { 6 | pageAnimation: 0, 7 | openPageAnimation: getItem('cnblog.openPageAnimation') !== null ? getItem('cnblog.openPageAnimation') : true, // 是否开启页面过渡动画,默认开启 8 | version: Version, 9 | serveConf: null, // 服务端配置 10 | openUpdateAppNotice: false, 11 | readVersion: getItem('cnblog.readVersion'), // 记录是否已经关闭某个版本更新的弹窗, 12 | isHaveUpdate: false, 13 | theme: theme || 'dodgerblue', 14 | themeList: ['dodgerblue', 'green', 'red', 'gray', 'purple', 'orange'] 15 | } 16 | const mutations = { 17 | UPDATE_PAGEANIMATION (state, animation) { 18 | state.pageAnimation = animation 19 | }, 20 | OPEN_PAGEANIMATION (state, isOpen) { 21 | state.openPageAnimation = isOpen 22 | }, 23 | GET_SERVECONF (state, conf) { 24 | state.serveConf = conf 25 | if (state.serveConf.version.id !== state.version) { 26 | state.isHaveUpdate = true 27 | if (state.serveConf.version.forceUpdate) { 28 | state.openUpdateAppNotice = true 29 | } else { 30 | if (state.readVersion !== state.serveConf.version.id) { 31 | state.openUpdateAppNotice = true 32 | } 33 | } 34 | } 35 | }, 36 | CLOSE_UPDATEAPP_NOTICE (state) { 37 | setItem('cnblog.readVersion', state.serveConf.version.id) 38 | state.openUpdateAppNotice = false 39 | }, 40 | CHANGE_THEME (state, theme) { 41 | state.theme = theme 42 | } 43 | } 44 | const actions = { 45 | UPDATE_PAGEANIMATION ({ commit }, animation) { 46 | commit('UPDATE_PAGEANIMATION', animation) 47 | }, 48 | OPEN_PAGEANIMATION ({ commit }, isOpen) { 49 | setItem('cnblog.openPageAnimation', isOpen) 50 | commit('OPEN_PAGEANIMATION', isOpen) 51 | }, 52 | GET_SERVECONF ({ commit }) { 53 | getConf().then(res => { 54 | if (res) { 55 | commit('GET_SERVECONF', res) 56 | } 57 | }) 58 | }, 59 | CLOSE_UPDATEAPP_NOTICE ({ commit }) { 60 | commit('CLOSE_UPDATEAPP_NOTICE') 61 | }, 62 | CHANGE_THEME ({ commit }, theme) { 63 | setItem('cnblog.theme', theme) 64 | commit('CHANGE_THEME', theme) 65 | } 66 | } 67 | 68 | export const app = { namespaced: true, state: state, mutations: mutations, actions: actions } 69 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { app } from './app' 4 | import { user } from './user' 5 | Vue.use(Vuex) 6 | export default new Vuex.Store({ 7 | modules: { 8 | app, 9 | user 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /src/store/user.js: -------------------------------------------------------------------------------- 1 | import { getUser, removeUser, setUser, setBlogColl, unCollectBlog, getBlogColl } from '@/utils/user' 2 | 3 | const state = { 4 | user: getUser(), 5 | blog_coll: getBlogColl() 6 | } 7 | 8 | const mutations = { 9 | SET_USER (state, user) { 10 | state.user = user 11 | }, 12 | REMOVE_USER (state) { 13 | state.user = null 14 | }, 15 | COLLECT_BLOG (state, blog) { 16 | state.blog_coll.unshift(blog) 17 | setBlogColl(blog) 18 | }, 19 | UNCOLLECT_BLOG (state, blog) { 20 | const index = state.blog_coll.findIndex(x => x.id === blog.id) 21 | if (index >= 0) { 22 | state.blog_coll.splice(index, 1) 23 | unCollectBlog(blog) 24 | } 25 | } 26 | } 27 | 28 | const actions = { 29 | SET_USER ({ commit }, user) { 30 | setUser(user) 31 | commit('SET_USER', user) 32 | }, 33 | REMOVE_USER ({ commit }) { 34 | removeUser() 35 | commit('REMOVE_USER') 36 | }, 37 | COLLECT_BLOG ({ commit }, blog) { 38 | commit('COLLECT_BLOG', blog) 39 | }, 40 | UNCOLLECT_BLOG ({ commit }, blog) { 41 | commit('UNCOLLECT_BLOG', blog) 42 | } 43 | } 44 | 45 | export const user = { namespaced: true, state: state, mutations: mutations, actions: actions } 46 | -------------------------------------------------------------------------------- /src/utils/$cookie.js: -------------------------------------------------------------------------------- 1 | export function setCookie (name, value) { 2 | var Days = 30 3 | var exp = new Date() 4 | exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000) 5 | document.cookie = name + '=' + escape(value) + ';expires=' + exp.toGMTString() 6 | } 7 | 8 | export function getCookie (name) { 9 | let reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)') 10 | let arr = document.cookie.match(reg) 11 | if (arr) { 12 | return unescape(arr[2]) 13 | } else { return null } 14 | } 15 | 16 | export function delCookie (name) { 17 | var exp = new Date() 18 | exp.setTime(exp.getTime() - 1) 19 | var cval = getCookie(name) 20 | if (cval != null) { document.cookie = name + '=' + cval + ';expires=' + exp.toGMTString() } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/$http.js: -------------------------------------------------------------------------------- 1 | import { WCF_API } from '../config/conf' 2 | import axios from 'axios' 3 | 4 | const instance = axios.create({ 5 | baseURL: WCF_API, 6 | headers: { 7 | 'Content-Type': 'application/json' 8 | }, 9 | timeout: 1000 * 60 10 | }) 11 | 12 | instance.interceptors.request.use(function (config) { 13 | return config 14 | }, function (error) { 15 | return Promise.reject(error) 16 | }) 17 | 18 | instance.interceptors.response.use(function (response) { 19 | return response.data 20 | }, function (error) { 21 | window.$vm.$toast({ 22 | message: error.message 23 | }) 24 | return Promise.reject(error) 25 | }) 26 | 27 | export default instance 28 | -------------------------------------------------------------------------------- /src/utils/$news.http.js: -------------------------------------------------------------------------------- 1 | import { NEWS_API } from '../config/conf' 2 | import axios from 'axios' 3 | 4 | const instance = axios.create({ 5 | baseURL: NEWS_API, 6 | timeout: 1000 * 10, 7 | withCredentials: true 8 | }) 9 | 10 | instance.interceptors.request.use(function (config) { 11 | config.headers['Accept'] = 'application/json' 12 | config.headers['Content-Type'] = 'application/json' 13 | return config 14 | }, function (error) { 15 | return Promise.reject(error) 16 | }) 17 | 18 | instance.interceptors.response.use(function (response) { 19 | if (response.data.Message.indexOf('登录') >= 0) { 20 | response.data.Message = '请重新设置登录Cookie' 21 | } 22 | return response.data 23 | }, function (error) { 24 | window.$vm.$toast({ 25 | message: error 26 | }) 27 | return Promise.reject(error) 28 | }) 29 | 30 | export default instance 31 | -------------------------------------------------------------------------------- /src/utils/$official.http.js: -------------------------------------------------------------------------------- 1 | import { OFFICAL_API } from '../config/conf' 2 | import axios from 'axios' 3 | 4 | const instance = axios.create({ 5 | baseURL: OFFICAL_API, 6 | timeout: 1000 * 10, 7 | withCredentials: true 8 | }) 9 | 10 | instance.interceptors.request.use(function (config) { 11 | config.headers['Accept'] = 'application/json' 12 | config.headers['Content-Type'] = 'application/json' 13 | 14 | // loading 1.5s后太出现,请求快的话就不需要出现 15 | config.loadingTimeOut = setTimeout(() => { 16 | config.loading = window.$vm.$toast.loading({ 17 | duration: 10000, 18 | forbidClick: true, // 禁用背景点击 19 | loadingType: 'spinner' 20 | }) 21 | }, 500) 22 | 23 | return config 24 | }, function (error) { 25 | return Promise.reject(error) 26 | }) 27 | 28 | instance.interceptors.response.use(function (response) { 29 | if (response.data.Message.indexOf('登录') >= 0) { 30 | response.data.Message = '请重新设置登录Cookie' 31 | } 32 | if (response.config.loading) { 33 | response.config.loading.clear() 34 | } else { 35 | clearTimeout(response.config.loadingTimeOut) 36 | } 37 | return response.data 38 | }, function (error) { 39 | window.$vm.$toast({ 40 | message: error.message 41 | }) 42 | if (error.config.loading) { 43 | error.config.loading.clear() 44 | } else { 45 | clearTimeout(error.config.loadingTimeOut) 46 | } 47 | return Promise.reject(error) 48 | }) 49 | 50 | export default instance 51 | -------------------------------------------------------------------------------- /src/utils/$storage.js: -------------------------------------------------------------------------------- 1 | export function getItem (key) { 2 | var data = window.localStorage.getItem(key) 3 | if (data) { 4 | var dataObj = JSON.parse(data) 5 | if (dataObj.type === 'string') { 6 | return dataObj.data 7 | } else if (dataObj.type === 'number') { 8 | return Number(dataObj.data) 9 | } else if (dataObj.type === 'object') { 10 | return JSON.parse(dataObj.data) 11 | } else if (dataObj.type === 'boolean') { 12 | return Boolean(dataObj.data) 13 | } else { 14 | return dataObj.data 15 | } 16 | } else { 17 | return null 18 | } 19 | } 20 | 21 | export function setItem (key, value) { 22 | let data = { 23 | type: '', 24 | data: null 25 | } 26 | if (typeof (value) === 'string') { 27 | data.type = 'string' 28 | data.data = value 29 | } else if (typeof (value) === 'number') { 30 | data.type = 'number' 31 | data.data = value 32 | } else if (typeof (value) === 'object') { 33 | data.type = 'object' 34 | data.data = JSON.stringify(value) 35 | } else if (typeof (value) === 'boolean') { 36 | data.type = 'boolean' 37 | data.data = value ? 1 : 0 38 | } else { 39 | data.type = 'unknow' 40 | data.data = value 41 | } 42 | window.localStorage.setItem(key, JSON.stringify(data)) 43 | } 44 | 45 | export function clearItem (key) { 46 | window.localStorage.removeItem(key) 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/native.js: -------------------------------------------------------------------------------- 1 | export function copy (text, cb) { 2 | if (window.cordova && window.cordova.plugins.clipboard) { 3 | window.cordova.plugins.clipboard.copy(text) 4 | if (cb) { 5 | cb() 6 | } 7 | window.$vm.$toast({ 8 | message: '复制成功' 9 | }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/user.js: -------------------------------------------------------------------------------- 1 | import { setItem, getItem, clearItem } from '@/utils/$storage' 2 | import { setCookie } from '@/utils/$cookie' 3 | 4 | export function setAuthCookie (cnblogsCookie) { 5 | // 存储到缓存 6 | setItem('cnblog.cookie', cnblogsCookie) 7 | // Cookie中添加 8 | if (window.cookieMaster) { 9 | window.cookieMaster.setCookieValue( 10 | 'www.cnblogs.com', 11 | '.CNBlogsCookie', 12 | cnblogsCookie 13 | ) 14 | window.cookieMaster.setCookieValue('news.cnblogs.com', '.CNBlogsCookie', cnblogsCookie) 15 | } else { 16 | setCookie('.CNBlogsCookie', cnblogsCookie) 17 | } 18 | } 19 | 20 | export function initAuthCookie () { 21 | const cnblogsCookie = getItem('cnblog.cookie') 22 | if (cnblogsCookie) { 23 | if (window.cookieMaster) { 24 | window.cookieMaster.setCookieValue( 25 | 'www.cnblogs.com', 26 | '.CNBlogsCookie', 27 | cnblogsCookie 28 | ) 29 | window.cookieMaster.setCookieValue('news.cnblogs.com', '.CNBlogsCookie', cnblogsCookie) 30 | } else { 31 | setCookie('.CNBlogsCookie', cnblogsCookie) 32 | } 33 | } 34 | } 35 | 36 | export function setUser (user) { 37 | setItem('cnblog.user', user) 38 | } 39 | 40 | export function removeUser (cookie) { 41 | clearItem('cnblog.user') 42 | } 43 | 44 | export function removeAuthCookie (cookie) { 45 | setAuthCookie('') 46 | } 47 | 48 | export function getUser () { 49 | return getItem('cnblog.user') 50 | } 51 | 52 | export function getBlogColl () { 53 | let coll = getItem('cnblog.blog.coll') 54 | return coll === null ? [] : coll 55 | } 56 | 57 | export function setBlogColl (blog) { 58 | let coll = getBlogColl() 59 | coll.unshift(blog) 60 | setItem('cnblog.blog.coll', coll) 61 | } 62 | 63 | export function unCollectBlog (blog) { 64 | let coll = getBlogColl() 65 | let index = coll.findIndex(x => x.id === blog.id) 66 | if (index >= 0) { 67 | coll.splice(index, 1) 68 | setItem('cnblog.blog.coll', coll) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 67 | 68 | 88 | -------------------------------------------------------------------------------- /src/views/BlogApp.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 66 | 67 | 75 | -------------------------------------------------------------------------------- /src/views/BlogCollection.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 26 | 27 | 50 | -------------------------------------------------------------------------------- /src/views/BlogDetail.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 233 | 234 | 310 | -------------------------------------------------------------------------------- /src/views/BloggerRank.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 50 | 62 | -------------------------------------------------------------------------------- /src/views/BloggerSearch.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 55 | 56 | 78 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 110 | 111 | 161 | -------------------------------------------------------------------------------- /src/views/News.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 138 | 139 | 188 | -------------------------------------------------------------------------------- /src/views/NewsDetail.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 114 | 115 | 174 | -------------------------------------------------------------------------------- /src/views/Theme.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | 21 | 30 | -------------------------------------------------------------------------------- /src/views/User.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 174 | 175 | 272 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | baseUrl: './', 3 | productionSourceMap: false, 4 | devServer: { 5 | port: 7878 6 | // 不再使用代理,在chrome中安装跨域插件 7 | /* proxy: { 8 | '/blog': { 9 | target: 'http://wcf.open.cnblogs.com', 10 | changeOrigin: true, 11 | secure: false 12 | }, 13 | '/news': { 14 | target: 'http://wcf.open.cnblogs.com/', 15 | changeOrigin: true, 16 | secure: false 17 | }, 18 | '/mvc': { 19 | target: 'https://www.cnblogs.com/mvc', 20 | changeOrigin: true, 21 | secure: false 22 | }, 23 | '/Comment/InsertComment': { 24 | target: ' https://news.cnblogs.com', 25 | changeOrigin: true, 26 | secure: false 27 | }, 28 | '/News/VoteNews': { 29 | target: ' https://news.cnblogs.com', 30 | changeOrigin: true, 31 | secure: false 32 | } 33 | } */ 34 | }, 35 | lintOnSave: false 36 | } 37 | --------------------------------------------------------------------------------