├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
├── CNAME
├── favicon.ico
└── index.html
├── src
├── App.vue
├── ajax
│ └── request.js
├── assets
│ ├── images
│ │ ├── author.jpg
│ │ ├── avatar.jpg
│ │ └── weixin.jpg
│ └── styles
│ │ ├── icon
│ │ └── myIcon
│ │ │ ├── iconfont.css
│ │ │ ├── iconfont.eot
│ │ │ ├── iconfont.js
│ │ │ ├── iconfont.svg
│ │ │ ├── iconfont.ttf
│ │ │ ├── iconfont.woff
│ │ │ └── iconfont.woff2
│ │ ├── main.scss
│ │ └── reset.scss
├── components
│ ├── banner.vue
│ ├── bread-crumb.vue
│ ├── comment.vue
│ ├── lang-dropdown.vue
│ ├── loading.vue
│ └── redirect.vue
├── filters
│ └── index.js
├── layout
│ ├── admin
│ │ ├── components
│ │ │ ├── app-header.vue
│ │ │ ├── app-main.vue
│ │ │ └── app-menu.vue
│ │ └── index.vue
│ └── home
│ │ ├── components
│ │ ├── app-footer.vue
│ │ ├── app-header.vue
│ │ └── app-main.vue
│ │ └── index.vue
├── main.js
├── router
│ ├── index.js
│ └── oauth.js
├── settings.js
├── utils
│ ├── common.js
│ ├── cookie-util.js
│ ├── gitee-api.js
│ ├── loading.js
│ ├── oauth-util.js
│ └── page-title-util.js
└── views
│ ├── admin
│ ├── category
│ │ └── index.vue
│ └── post-list
│ │ ├── detail.vue
│ │ └── index.vue
│ ├── error-page
│ └── 404.vue
│ └── home
│ ├── about
│ └── index.vue
│ ├── article
│ ├── components
│ │ ├── index-primary.vue
│ │ ├── index-secondary.vue
│ │ └── post-list.vue
│ ├── index.vue
│ └── post-detail.vue
│ └── works
│ └── index.vue
└── vue.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [奇思笔记](https://github.com/zclzone/blog)
2 |
3 | > 基于 Gitee-api + Vue + Markdown 的个人博客网站,完全免费,无需个人服务器即可在线维护博客
4 |
5 | ### 预览
6 |
7 | github: [blog.zclzone.com](http://blog.zclzone.com)
8 |
9 | gitee: [zclzone.gitee.io/blog](https://zclzone.gitee.io/blog)
10 |
11 | ### 亮点
12 |
13 | 1. 无需服务器即可实现在后台网站在线维护博客文章(增删改查),增减文章类型,后续计划将更多的网站配置放到后台在线维护。
14 | 2. 使用 Gitee Api,在国内访问速度快,体验好,当然如果有朋友愿意开发一个 github 版的也是相当不错的,本人由于访问 github 速度太慢,即使开发出来也没多少实用性,所以暂不折腾了。
15 | 3. 网站内容配置文件化,后续计划增加更多可配置化的设置,再慢慢将网站配置转移到后台网站维护,如主题色配置,多模板等等。
16 | 4. 后台网站使用 Gitee Oauth 登录验证,其他人可访问后台网站但无法更改数据。
17 | 5. 使用 Vue 开发,国内会使用 Vue 开发的程序员还是挺多的,考虑到 SEO 后期可能会使用 nuxtjs 重构。
18 |
19 | ### 使用方式
20 |
21 | 1. Fork & Clone 此仓库并安装依赖
22 |
23 | 当然了,Fork 是可选的,不过对此项目感兴趣的朋友可以点点 Star,或者 Fork 此项目,如果您对此项目有足够兴趣,欢迎提交您宝贵的 Pr 让此项目变得更加完备和好用
24 |
25 | ```
26 | git clone https://github.com/zclzone/blog.git
27 | cd blog
28 | npm install
29 | ```
30 |
31 |
32 |
33 | 2. 修改配置 src 文件夹下的配置文件 settings.js
34 |
35 | 需确保将 giteeApiOptions 的配置信息修改成自己的,至于其他配置信息我计划写一篇博客单独介绍,请留意我的[博客网站](http://blog.qszone.com)
36 | 
37 |
38 |
39 |
40 | 3. 构建项目并发布
41 |
42 | 1. 如果您没有 gitee 账号,您需要先创建一个 [gitee](https://gitee.com/) 账号,并确保账号下没有名为**gitee-blog-db**的仓库
43 | 2. Fork gitee 仓库 [gitee-db](https://gitee.com/qszone/gitee-db),此仓库将作为博客的“数据库”,或者您也可以 Clone 此仓库再推送到 gitee,仓库名可以自定义,但必须跟 settings.js 下 giteeApiOptions 的 repo 保持一致
44 | 3. 执行语句构建项目(当然您可以不急着构建,可以直接在本地运行看下效果,执行 npm run serve 即可)
45 | 4. 创建一个空的 gitee 或者 github 仓库,将构建好的代码(dist 文件夹下)推送到此仓库并发布 pages,理论上您可以发布到任何外网能访问的地方,如码云、github 等,具体如何发布这里不作详细介绍,我会在我的[博客](http://blog.qszone.com)专门写一篇博客介绍如何发布 pages
46 | 5. 发布之后就可以访问了,默认页面是博客首页,后台地址是[您的发布地址/#/admin],如:[https://blog.zclzone.com/#/admin](http://blog.qszone.com)
47 |
48 | 以下指令假设您 gitee “数据库” 已经存在并且创建了一个空仓库用于发布 pages
49 |
50 | ```
51 | npm run build
52 | cd dist
53 | git init
54 | git add .
55 | git commit -m 'init blog'
56 | git remote add origin [您创建的空仓库地址]
57 | git push -u origin master
58 | ```
59 |
60 |
61 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "@vssue/api-github-v3": "^1.4.4",
11 | "axios": "^0.20.0",
12 | "core-js": "^3.6.5",
13 | "element-ui": "^2.13.2",
14 | "github-markdown-css": "^4.0.0",
15 | "js-cookie": "^2.2.1",
16 | "mavon-editor": "^2.9.0",
17 | "vssue": "^1.4.6",
18 | "vue": "^2.6.11",
19 | "vue-router": "^3.2.0"
20 | },
21 | "devDependencies": {
22 | "@vue/cli-plugin-babel": "~4.4.0",
23 | "@vue/cli-plugin-router": "~4.4.0",
24 | "@vue/cli-service": "~4.4.0",
25 | "node-sass": "^4.12.0",
26 | "sass-loader": "^8.0.2",
27 | "vue-template-compiler": "^2.6.11"
28 | },
29 | "browserslist": [
30 | "> 1%",
31 | "last 2 versions",
32 | "not dead"
33 | ]
34 | }
--------------------------------------------------------------------------------
/public/CNAME:
--------------------------------------------------------------------------------
1 | blog.zclzone.com
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zclzone/gitee-blog/6fb881e9097a3310482cc2309a80ae0ae8335ec1/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/src/ajax/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | const service = axios.create({
4 | // baseURL: 'https://gitee.com/',
5 | timeout: 10000,
6 | withCredentials: false
7 | })
8 |
9 | service.interceptors.request.use(
10 | config => {
11 | if (config.method === 'get') {
12 | const url = config.url
13 | url.indexOf('?') === -1 ? config.url = url + '?_=' + (new Date().getTime()) : config.url = url + '&_=' + (new Date().getTime())
14 | }
15 | return config
16 | },
17 | err => Promise.reject(err)
18 | )
19 |
20 | service.interceptors.response.use(
21 | response => response,
22 | err => Promise.reject(err)
23 | )
24 |
25 | export default service
--------------------------------------------------------------------------------
/src/assets/images/author.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zclzone/gitee-blog/6fb881e9097a3310482cc2309a80ae0ae8335ec1/src/assets/images/author.jpg
--------------------------------------------------------------------------------
/src/assets/images/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zclzone/gitee-blog/6fb881e9097a3310482cc2309a80ae0ae8335ec1/src/assets/images/avatar.jpg
--------------------------------------------------------------------------------
/src/assets/images/weixin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zclzone/gitee-blog/6fb881e9097a3310482cc2309a80ae0ae8335ec1/src/assets/images/weixin.jpg
--------------------------------------------------------------------------------
/src/assets/styles/icon/myIcon/iconfont.css:
--------------------------------------------------------------------------------
1 | @font-face {font-family: "myIcon";
2 | src: url('iconfont.eot?t=1568436289437'); /* IE9 */
3 | src: url('iconfont.eot?t=1568436289437#iefix') format('embedded-opentype'), /* IE6-IE8 */
4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
5 | url('iconfont.woff?t=1568436289437') format('woff'),
6 | url('iconfont.ttf?t=1568436289437') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
7 | url('iconfont.svg?t=1568436289437#myIcon') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .myIcon {
11 | font-family: "myIcon" !important;
12 | font-size: 16px;
13 | font-style: normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .myIcon-icon-search:before {
19 | content: "\e615";
20 | }
21 |
22 | .myIcon-check-fail:before {
23 | content: "\e631";
24 | }
25 |
26 | .myIcon-nice:before {
27 | content: "\e639";
28 | }
29 |
30 | .myIcon-icon-location:before {
31 | content: "\e61b";
32 | }
33 |
34 | .myIcon-weixin:before {
35 | content: "\e614";
36 | }
37 |
38 | .myIcon-github:before {
39 | content: "\e632";
40 | }
41 |
42 | .myIcon-tag:before {
43 | content: "\e63a";
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/src/assets/styles/icon/myIcon/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zclzone/gitee-blog/6fb881e9097a3310482cc2309a80ae0ae8335ec1/src/assets/styles/icon/myIcon/iconfont.eot
--------------------------------------------------------------------------------
/src/assets/styles/icon/myIcon/iconfont.js:
--------------------------------------------------------------------------------
1 | !function (a) { var t, e = '', c = (t = document.getElementsByTagName("script"))[t.length - 1].getAttribute("data-injectcss"); if (c && !a.__iconfont__svg__cssinject__) { a.__iconfont__svg__cssinject__ = !0; try { document.write("") } catch (t) { console && console.log(t) } } !function (t) { if (document.addEventListener) if (~["complete", "loaded", "interactive"].indexOf(document.readyState)) setTimeout(t, 0); else { var c = function () { document.removeEventListener("DOMContentLoaded", c, !1), t() }; document.addEventListener("DOMContentLoaded", c, !1) } else document.attachEvent && (n = t, o = a.document, l = !1, (i = function () { try { o.documentElement.doScroll("left") } catch (t) { return void setTimeout(i, 50) } e() })(), o.onreadystatechange = function () { "complete" == o.readyState && (o.onreadystatechange = null, e()) }); function e() { l || (l = !0, n()) } var n, o, l, i }(function () { var t, c; (t = document.createElement("div")).innerHTML = e, e = null, (c = t.getElementsByTagName("svg")[0]) && (c.setAttribute("aria-hidden", "true"), c.style.position = "absolute", c.style.width = 0, c.style.height = 0, c.style.overflow = "hidden", function (t, c) { c.firstChild ? function (t, c) { c.parentNode.insertBefore(t, c) }(t, c.firstChild) : c.appendChild(t) }(c, document.body)) }) }(window);
--------------------------------------------------------------------------------
/src/assets/styles/icon/myIcon/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
48 |
--------------------------------------------------------------------------------
/src/assets/styles/icon/myIcon/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zclzone/gitee-blog/6fb881e9097a3310482cc2309a80ae0ae8335ec1/src/assets/styles/icon/myIcon/iconfont.ttf
--------------------------------------------------------------------------------
/src/assets/styles/icon/myIcon/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zclzone/gitee-blog/6fb881e9097a3310482cc2309a80ae0ae8335ec1/src/assets/styles/icon/myIcon/iconfont.woff
--------------------------------------------------------------------------------
/src/assets/styles/icon/myIcon/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zclzone/gitee-blog/6fb881e9097a3310482cc2309a80ae0ae8335ec1/src/assets/styles/icon/myIcon/iconfont.woff2
--------------------------------------------------------------------------------
/src/assets/styles/main.scss:
--------------------------------------------------------------------------------
1 | @import url('./reset.scss');
2 | @import url('./icon/myIcon/iconfont.css');
3 |
4 | :root {
5 | --main-color: #2b4c59; //主色,最深
6 | --secondary-color: #316c72; //次色,稍浅色
7 | --light-color: #84a59d; //浅色
8 | --front-color: #fff; //前景色
9 | --bg-color: #f3f3f3; //背景色
10 | }
11 |
12 | html,
13 | body {
14 | height: 100%;
15 | // scrollbar-width: none;
16 | // -ms-overflow-style: none;
17 | // overflow-x: hidden;
18 | // overflow-y: auto;
19 | // &::-webkit-scrollbar {
20 | // display: none;
21 | // }
22 | }
23 |
24 | html[theme='dark-mode'] {
25 | filter: invert(1) hue-rotate(180deg);
26 | }
27 |
28 | .el-row {
29 | padding: 0 !important;
30 | margin: 0 !important;
31 | }
32 |
33 | .el-tag {
34 | color: var(--front-color) !important;
35 | font-weight: 600;
36 | margin: 10px 5px 0;
37 | .el-tag__close {
38 | color: var(--front-color) !important;
39 | }
40 | }
41 |
42 | .el-tabs__item {
43 | color: var(--main-color) !important;
44 | &.is-active {
45 | color: var(--secondary-color) !important;
46 | }
47 | &:hover {
48 | color: var(--secondary-color) !important;
49 | }
50 | }
51 |
52 | .el-tabs__active-bar {
53 | background: var(--secondary-color) !important;
54 | }
55 |
56 | @media screen and (max-width: 768px) {
57 | .sm-hide {
58 | display: none;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/assets/styles/reset.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | *,
4 | ::before,
5 | ::after {
6 | margin: 0;
7 | padding: 0;
8 | box-sizing: border-box;
9 | -webkit-box-sizing: border-box;
10 | -webkit-tap-highlight-color: transparent; /* 清除点击高亮效果 */
11 | }
12 |
13 | a {
14 | text-decoration: none;
15 | color: #333;
16 | }
17 |
18 | a:hover,
19 | a:link,
20 | a:visited,
21 | a:active {
22 | text-decoration: none;
23 | }
24 |
25 | input,
26 | textarea {
27 | outline: none;
28 | border: none;
29 | resize: none;
30 | -webkit-appearance: none; /* 清除移动端输入框特有的样式 */
31 | }
32 |
33 | body {
34 | font-size: 14px;
35 | font-weight: 400;
36 | color: #000;
37 | -webkit-font-smoothing: antialiased;
38 | font-family: 'Arial', 'Microsoft YaHei', '黑体', '宋体', sans-serif !important;
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/banner.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{bannerData.title}}
6 | {{bannerData.description}}
7 |
8 |
9 |
10 |
11 |
12 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/bread-crumb.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{item.name}}
5 |
6 |
7 |
8 |
9 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/comment.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/lang-dropdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{title}}
4 |
5 |
6 |
7 | {{item.title}}
8 |
9 |
10 |
11 |
12 |
13 |
35 |
36 |
--------------------------------------------------------------------------------
/src/components/loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
23 |
--------------------------------------------------------------------------------
/src/components/redirect.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/filters/index.js:
--------------------------------------------------------------------------------
1 | function formatNumber(n) {
2 | const str = n.toString();
3 | return str[1] ? str : `0${str}`;
4 | }
5 |
6 | export function dateFormat(time) {
7 | const date = new Date(time)
8 |
9 | const year = date.getFullYear()
10 | const month = date.getMonth() + 1
11 | const day = date.getDate()
12 | const hour = date.getHours()
13 | const minute = date.getMinutes()
14 | const second = date.getSeconds()
15 |
16 | const t1 = [year, month, day].map(formatNumber).join('-')
17 | const t2 = [hour, minute, second].map(formatNumber).join(':')
18 | return `${t1} ${t2}`
19 | }
20 |
--------------------------------------------------------------------------------
/src/layout/admin/components/app-header.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
62 |
63 |
--------------------------------------------------------------------------------
/src/layout/admin/components/app-main.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
48 |
49 |
--------------------------------------------------------------------------------
/src/layout/admin/components/app-menu.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
63 |
64 |
--------------------------------------------------------------------------------
/src/layout/admin/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
69 |
70 |
--------------------------------------------------------------------------------
/src/layout/home/components/app-footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/layout/home/components/app-header.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/layout/home/components/app-main.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/layout/home/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 |
5 | // 引入Element UI
6 | import Element from 'element-ui'
7 | import 'element-ui/lib/theme-chalk/index.css'
8 | import 'element-ui/lib/theme-chalk/display.css'
9 | Vue.use(Element)
10 |
11 | // 引入mavonEditor
12 | import mavonEditor from 'mavon-editor'
13 | import 'mavon-editor/dist/css/index.css'
14 | Vue.use(mavonEditor)
15 |
16 | import loading from '@/utils/loading'
17 | Vue.use(loading)
18 |
19 |
20 | // 引入样式
21 | import '@/assets/styles/main.scss'
22 | import 'github-markdown-css/github-markdown.css'
23 |
24 |
25 | // 扩充Vue的全局prototype属性
26 | import axios from '@/ajax/request'
27 | import { to } from '@/utils/common'
28 | Vue.prototype.$axios = axios
29 | Vue.prototype.$to = to
30 | Vue.prototype.guid = () => {
31 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
32 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
33 | return v.toString(16)
34 | })
35 | }
36 |
37 | //全局过滤器
38 | import * as filters from './filters'
39 | Object.keys(filters).forEach(key => {
40 | Vue.filter(key, filters[key])
41 | })
42 |
43 | Vue.config.productionTip = false
44 |
45 | new Vue({
46 | router,
47 | render: h => h(App)
48 | }).$mount('#app')
49 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 |
4 | import { setPageTitle } from '@/utils/page-title-util'
5 |
6 | Vue.use(VueRouter)
7 |
8 | const routes = [
9 | {
10 | path: '/',
11 | component: _ => import('@/layout/home'),
12 | redirect: '/article',
13 | hidden: true,
14 | children: [
15 | {
16 | path: 'index',
17 | component: _ => import('@/views/home/article'),
18 | meta: {
19 | title: '首页'
20 | }
21 | },
22 | {
23 | path: 'article',
24 | component: _ => import('@/views/home/article'),
25 | meta: {
26 | title: '文章'
27 | }
28 | },
29 | {
30 | path: 'article/:name*',
31 | component: _ => import('@/views/home/article/post-detail'),
32 | },
33 | {
34 | path: 'works',
35 | component: _ => import('@/views/home/article'),
36 | meta: {
37 | title: '作品'
38 | }
39 | },
40 | {
41 | path: 'about',
42 | component: _ => import('@/views/home/about'),
43 | },
44 | ]
45 | },
46 | {
47 | path: '/admin',
48 | component: _ => import('@/layout/admin'),
49 | redirect: '/admin/list',
50 | meta: {
51 | title: '博客管理'
52 | },
53 | children: [
54 | {
55 | path: 'list',
56 | component: _ => import('@/views/admin/post-list'),
57 | meta: {
58 | title: '文章列表'
59 | }
60 | },
61 | {
62 | path: 'list/:action',
63 | component: _ => import('@/views/admin/post-list/detail'),
64 | hidden: true,
65 | meta: {
66 | title: '文章详情'
67 | }
68 | },
69 | {
70 | path: 'category',
71 | component: _ => import('@/views/admin/category'),
72 | meta: {
73 | title: '文章分类'
74 | }
75 | }
76 |
77 | ]
78 | },
79 | {
80 | path: '/redirect',
81 | hidden: true,
82 | component: _ => import('@/components/redirect'),
83 | },
84 | {
85 | path: '*',
86 | component: () => import('@/views/error-page/404'),
87 | hidden: true,
88 | meta: {
89 | title: '404-错误页'
90 | }
91 | }
92 | ]
93 |
94 |
95 | const router = new VueRouter({
96 | scrollBehavior: () => ({ y: 0 }),
97 | routes,
98 | })
99 |
100 | // 路由钩子修改页面title
101 | router.beforeEach((to, from, next) => {
102 | if (to.meta && to.meta.title) {
103 | setPageTitle(to.meta.title)
104 | }
105 | next()
106 | })
107 |
108 | // 取消重复打开相同路由时控制台报错误
109 | const VueRouterPush = VueRouter.prototype.push
110 | VueRouter.prototype.push = function push(to) {
111 | return VueRouterPush.call(this, to).catch(err => err)
112 | }
113 |
114 | export default router
115 |
--------------------------------------------------------------------------------
/src/router/oauth.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 此文件已停用,main.js并未引用此文件
3 | */
4 |
5 | import router from './index'
6 | import { getToken, setToken } from '@/utils/cookie-util'
7 | import { getAccessToken, getOauthUrl } from '@/utils/oauth-util'
8 |
9 | const whiteList = ['/', '/index', '/index/article', '/index/works', '/index/about']
10 |
11 | router.beforeEach(async (to, from, next) => {
12 | if (whiteList.indexOf(to.path) !== -1) {
13 | next()
14 | return
15 | }
16 | if (to.path === '/auth') {
17 | if (to.query && to.query.code) {
18 | const access_token = await getAccessToken(to.query.code)
19 | if (access_token) {
20 | setToken(access_token)
21 | next({ path: '/admin' })
22 | }
23 | }
24 | }
25 | const token = getToken()
26 | if (token) {
27 | //每次路由改变延长Cookie过期时间
28 | setToken(token)
29 | next()
30 | } else {
31 | location.hash = ''
32 | window.name = location.href
33 | location.href = getOauthUrl()
34 | }
35 | })
36 |
37 | router.afterEach(() => {
38 | })
39 |
--------------------------------------------------------------------------------
/src/settings.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // 页面title
3 | title: '奇思笔记',
4 | // 发布pages的仓库名(博客部署文件存放的仓库名)
5 | pagesRepo: 'blog',
6 | // 网站配置信息
7 | siteOptions: {
8 | title: '奇思笔记',
9 | subtitle: '孤光自照,肝胆皆冰雪',
10 | author: '张传龙',
11 | description: '前端全栈开发者',
12 | github: 'https://github.com/zclzone/blog',
13 | navMenu: {
14 | 'http://www.qszone.com': '首页', //外链
15 | '/article': '文章',
16 | 'https://coolshell.cn': '友情链接',
17 | // '/works': '作品',
18 | '/article/关于本站': '关于本站'
19 | },
20 | skills: ['Vue', 'React', 'JavaScript', 'Css', 'Java', 'C#', 'MySql', 'Sql Server', 'MongoDB'],
21 | },
22 | // 评论配置信息
23 | commentOptions: {
24 | owner: 'zclzone',
25 | repo: 'blog',
26 | clientId: '13f12c4d3203ceee7d22',
27 | clientSecret: '4de1abe7fa4d21bc1c76b6e03a86f0cc2d4afc83'
28 | },
29 | // gitee oauth
30 | giteeOauthOptions: {
31 | baseOauthUrl: 'https://gitee.com/oauth',
32 | clientId: '5469618e0b5d3fb058336b82c907bc9459f3c727ad1eb060325a803d91209a52',
33 | clientSecret: 'cb90ca23bd37be8d8a1aa53551db8cef4a5b148babef6aa78f3ad58e778c0abd',
34 | redirectUri: 'https://zclzone.gitee.io/redirect'
35 | },
36 | // gitee api
37 | giteeApiOptions: {
38 | baseApiURL: 'https://gitee.com/api/v5',
39 | owner: 'zclzone',
40 | repo: 'gitee-db'
41 | }
42 | }
--------------------------------------------------------------------------------
/src/utils/common.js:
--------------------------------------------------------------------------------
1 | import { Message, Notification } from 'element-ui'
2 |
3 | const to = promise => {
4 | return promise.then(res => [null, res]).catch(err => [err])
5 | }
6 |
7 | const guid = () => {
8 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
9 | let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
10 | return v.toString(16)
11 | })
12 | }
13 |
14 | const utf8ToBase64 = (str) => {
15 | return btoa(unescape(encodeURIComponent(str)))
16 | }
17 |
18 | const base64ToUtf8 = (str) => {
19 | return decodeURIComponent(escape(atob(str)))
20 | }
21 |
22 | const eleNotification = (message, title = '提醒') => {
23 | Notification({
24 | title,
25 | dangerouslyUseHTMLString: true, //是否将 message 属性作为 HTML 片段处理
26 | duration: 0,
27 | message
28 | })
29 | }
30 |
31 | const eleMessage = (message, type = 'info') => {
32 | Message({
33 | type,
34 | message
35 | })
36 | }
37 |
38 |
39 |
40 | export {
41 | to,
42 | guid,
43 | utf8ToBase64,
44 | base64ToUtf8,
45 | eleNotification,
46 | eleMessage
47 | }
--------------------------------------------------------------------------------
/src/utils/cookie-util.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const TOKEN_CODE = 'access_token'
4 |
5 | export function getToken() {
6 | return Cookies.get(TOKEN_CODE)
7 | }
8 |
9 | export function setToken(token) {
10 | return Cookies.set(TOKEN_CODE, token, { expires: new Date(new Date().getTime() + 2 * 60 * 60 * 1000) })
11 | }
12 |
13 | export function removeToken() {
14 | Cookies.remove(TOKEN_CODE)
15 | Cookies.remove('User')
16 | }
17 |
18 | export function getUser() {
19 | return Cookies.get('User')
20 | }
21 |
22 | export function setUser(userInfo) {
23 | return Cookies.set('User', userInfo)
24 | }
--------------------------------------------------------------------------------
/src/utils/gitee-api.js:
--------------------------------------------------------------------------------
1 | import axios from '@/ajax/request'
2 | import { getToken } from '@/utils/cookie-util'
3 | import { to, utf8ToBase64, base64ToUtf8 } from '@/utils/common'
4 |
5 | const { giteeApiOptions } = require('@/settings')
6 | const { baseApiURL, owner, repo } = giteeApiOptions
7 |
8 | const baseRepoURL = `${baseApiURL}/repos/${owner}/${repo}`
9 |
10 | const giteeApi = {
11 | getFileSha: async (fileName) => {
12 | const [err, res] = await to(axios.get(`${baseRepoURL}/contents/${fileName}`))
13 | if (err) {
14 | return ''
15 | }
16 | if (!res.data.sha) {
17 | return ''
18 | }
19 | return res.data.sha
20 | },
21 |
22 | getFile: async (fileName) => {
23 | const [err, res] = await to(axios.get(`${baseRepoURL}/contents/${fileName}`))
24 | if (err) {
25 | return null
26 | }
27 | if (res.data.length) {
28 | return res.data
29 | }
30 | if (res.data.sha) {
31 | return {
32 | sha: res.data.sha,
33 | name: res.data.name,
34 | path: res.data.path,
35 | content: base64ToUtf8(res.data.content)
36 | }
37 | }
38 | return null
39 | },
40 |
41 | addFile: async (fileName, content) => {
42 | const sha = await giteeApi.getFileSha(fileName)
43 | if (sha) {
44 | return {
45 | status: 'Fail',
46 | msg: `已经存在 ${fileName}`
47 | }
48 | }
49 | content = utf8ToBase64(content)
50 | const data = { access_token: getToken(), message: `add ${fileName}`, content }
51 | const [err, res] = await to(axios.post(`${baseRepoURL}/contents/${fileName}`, data))
52 | if (err) {
53 | return {
54 | status: 'Fail',
55 | msg: `添加失败: ${err}`
56 | }
57 | }
58 | return {
59 | status: 'OK',
60 | msg: `添加成功: ${fileName}`,
61 | }
62 | },
63 |
64 | removeFile: async (fileName) => {
65 | const sha = await giteeApi.getFileSha(fileName)
66 | if (!sha) {
67 | return {
68 | status: 'Fail',
69 | msg: `不存在 ${fileName}`
70 | }
71 | }
72 | let params = { access_token: getToken(), message: `remove ${fileName}`, sha }
73 | const [err, res] = await to(axios.delete(`${baseRepoURL}/contents/${fileName}`, { params }))
74 |
75 | if (err) {
76 | return {
77 | status: 'Fail',
78 | msg: `删除失败: ${err}`
79 | }
80 | }
81 | return {
82 | status: 'OK',
83 | msg: `删除成功: ${fileName}`
84 | }
85 | },
86 |
87 | updateFile: async (fileName, sha, content) => {
88 | content = utf8ToBase64(content)
89 | const data = { access_token: getToken(), message: `update ${fileName}`, content, sha }
90 | const [err, res] = await to(axios.put(`${baseRepoURL}/contents/${fileName}`, data))
91 | if (err) {
92 | return {
93 | status: "Fail",
94 | msg: `更新失败: ${err}`
95 | }
96 | }
97 | return {
98 | status: 'OK',
99 | msg: `更新成功: ${fileName}`
100 | }
101 | },
102 |
103 | checkRepo: async _ => {
104 | const params = { access_token: getToken() }
105 | const [err, res] = await to(axios.get(baseRepoURL, { params }))
106 | if (err) {
107 | return false
108 | }
109 | return true
110 | },
111 |
112 | forkRepo: async _ => {
113 | const data = { access_token: getToken() }
114 | const [err, res] = await to(axios.post(`${baseApiURL}/repos/zclzone/gitee-db/forks`, data))
115 | if (err) {
116 | return {
117 | status: 'Fail'
118 | }
119 | }
120 | return {
121 | status: 'OK'
122 | }
123 | },
124 | }
125 |
126 | export {
127 | giteeApi
128 | }
--------------------------------------------------------------------------------
/src/utils/loading.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import loadingComponent from '@/components/loading.vue'
3 |
4 | const LoadingConstructor = Vue.extend(loadingComponent)
5 |
6 | const instance = new LoadingConstructor({
7 | el: document.createElement('div')
8 | })
9 |
10 | instance.show = false // 默认隐藏
11 | const loading = {
12 | show () { // 显示方法
13 | instance.show = true
14 | document.body.appendChild(instance.$el)
15 | },
16 | hide () { // 隐藏方法
17 | instance.show = false
18 | }
19 | }
20 |
21 | export default {
22 | install () {
23 | if (!Vue.$loading) {
24 | Vue.$loading = loading
25 | }
26 | Vue.mixin({
27 | created () {
28 | this.$loading = Vue.$loading
29 | }
30 | })
31 | }
32 | }
--------------------------------------------------------------------------------
/src/utils/oauth-util.js:
--------------------------------------------------------------------------------
1 | import axios from '@/ajax/request'
2 | import { to } from './common'
3 |
4 | import { setUser } from '@/utils/cookie-util'
5 |
6 | const { giteeOauthOptions } = require('@/settings.js')
7 | const { baseOauthUrl, clientId, clientSecret, redirectUri } = giteeOauthOptions
8 |
9 | export function getOauthUrl() {
10 | return `${baseOauthUrl}/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code`
11 | }
12 |
13 | export async function getAccessToken(code) {
14 | const [err, res] = await to(axios.post(`${baseOauthUrl}/token?grant_type=authorization_code&code=${code}&client_id=${clientId}&redirect_uri=${redirectUri}`, { client_secret: clientSecret }))
15 | if (err) {
16 | return null
17 | }
18 | return res.data.access_token
19 | }
20 |
21 | export async function getUserInfo(access_token) {
22 | const [err, res] = await to(axios.get(`https://gitee.com/api/v5/user?access_token=${access_token}`))
23 | if (err) {
24 | return null
25 | }
26 | const { avatar_url, email, id, name, login, html_url } = res.data
27 | return setUser({ avatar_url, email, id, name, login, html_url })
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/page-title-util.js:
--------------------------------------------------------------------------------
1 | import settings from '@/settings'
2 |
3 | const title = settings.title || '奇思笔记'
4 |
5 | export function setPageTitle(pageTitle) {
6 | if (pageTitle) {
7 | return document.title = `${pageTitle} | ${title}`
8 | }
9 | document.title = title
10 | }
--------------------------------------------------------------------------------
/src/views/admin/category/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{item}}
5 |
6 |
7 |
8 | + New Category
9 |
10 |
11 |
12 |
78 |
79 |
--------------------------------------------------------------------------------
/src/views/admin/post-list/detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 退出
6 |
7 | 保存
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 |
206 |
207 |
208 |
--------------------------------------------------------------------------------
/src/views/admin/post-list/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 新增
5 | 查看
6 | 编辑
7 | 删除
8 |
9 |
10 |
19 |
20 |
26 |
27 |
28 | {{
29 | scope.row[item.prop] | dateFormat
30 | }}
31 |
32 | {{ scope.row[item.prop] }}
33 |
34 |
35 |
36 |
46 |
47 |
48 |
49 |
211 |
212 |
--------------------------------------------------------------------------------
/src/views/error-page/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
404,页面打酱油去了
4 |
5 |
--------------------------------------------------------------------------------
/src/views/home/about/index.vue:
--------------------------------------------------------------------------------
1 |
2 | About Page
3 |
--------------------------------------------------------------------------------
/src/views/home/article/components/index-primary.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
10 |
11 |
12 |
32 |
33 |
--------------------------------------------------------------------------------
/src/views/home/article/components/index-secondary.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
24 |
25 | {{author}}
26 | [ {{description}} ]
27 |
28 |
29 | {{item}}
30 |
31 |
32 |
33 | Star Me
34 |
Contact Me
35 |
36 |
37 |
48 |
49 |
50 |
51 |
52 |
53 |
59 | - {{item.name}}
60 |
61 |
62 |
63 |
64 |
65 |
71 | - {{item.name}}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
150 |
151 |
--------------------------------------------------------------------------------
/src/views/home/article/components/post-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | {{item.name}}
7 |
8 | — {{item.author}}
9 |
10 | {{item.description}}
11 |
12 |
13 | 类别:
14 | {{item.category}}
15 |
16 | {{item.date | dateFormat}}
17 |
18 |
19 |
20 |
21 |
22 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/views/home/article/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
93 |
94 |
--------------------------------------------------------------------------------
/src/views/home/article/post-detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
56 |
57 |
--------------------------------------------------------------------------------
/src/views/home/works/index.vue:
--------------------------------------------------------------------------------
1 |
2 | Works Page
3 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const settings = require('./src/settings.js')
2 | const title = settings.title || '奇思笔记'
3 |
4 | module.exports = {
5 | publicPath: './',
6 | // publicPath: process.env.NODE_ENV === 'production' ? `/${settings.pagesRepo}/` : './',
7 | chainWebpack: config => {
8 | config.plugin('html').tap(args => {
9 | args[0].title = title
10 | return args
11 | })
12 | },
13 | configureWebpack: config => {
14 | if (process.env.NODE_ENV === 'production') {
15 | config.mode = 'production'
16 | //打包文件大小配置
17 | config['performance'] = {
18 | maxEntrypointSize: 10000000,
19 | maxAssetSize: 30000000
20 | }
21 | }
22 | },
23 | devServer: {
24 | // proxy: 'http://localhost:3000'
25 | }
26 | }
27 |
--------------------------------------------------------------------------------