├── LICENSE
├── README.md
├── node-server-api
├── api.js
├── db.js
├── index.js
└── package.json
├── vue-blog-admin
├── .editorconfig
├── .env.development
├── .env.production
├── .env.staging
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── .travis.yml
├── babel.config.js
├── build
│ └── index.js
├── jest.config.js
├── package.json
├── postcss.config.js
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── api
│ │ ├── article.js
│ │ ├── food.js
│ │ ├── updateLog.js
│ │ ├── user.js
│ │ └── whisper.js
│ ├── components
│ │ ├── Breadcrumb
│ │ │ └── index.vue
│ │ ├── Hamburger
│ │ │ └── index.vue
│ │ ├── Pagination
│ │ │ └── index.vue
│ │ └── SvgIcon
│ │ │ └── index.vue
│ ├── directive
│ │ ├── clipboard
│ │ │ ├── clipboard.js
│ │ │ └── index.js
│ │ ├── el-drag-dialog
│ │ │ ├── drag.js
│ │ │ └── index.js
│ │ ├── el-table
│ │ │ ├── adaptive.js
│ │ │ └── index.js
│ │ ├── permission
│ │ │ ├── index.js
│ │ │ └── permission.js
│ │ ├── sticky.js
│ │ └── waves
│ │ │ ├── index.js
│ │ │ ├── waves.css
│ │ │ └── waves.js
│ ├── icons
│ │ ├── index.js
│ │ ├── svg
│ │ │ ├── dashboard.svg
│ │ │ ├── example.svg
│ │ │ ├── eye-open.svg
│ │ │ ├── eye.svg
│ │ │ ├── form.svg
│ │ │ ├── link.svg
│ │ │ ├── nested.svg
│ │ │ ├── password.svg
│ │ │ ├── table.svg
│ │ │ ├── tree.svg
│ │ │ └── user.svg
│ │ └── svgo.yml
│ ├── layout
│ │ ├── components
│ │ │ ├── AppMain.vue
│ │ │ ├── Navbar.vue
│ │ │ ├── Sidebar
│ │ │ │ ├── FixiOSBug.js
│ │ │ │ ├── Item.vue
│ │ │ │ ├── Link.vue
│ │ │ │ ├── Logo.vue
│ │ │ │ ├── SidebarItem.vue
│ │ │ │ └── index.vue
│ │ │ └── index.js
│ │ ├── index.vue
│ │ └── mixin
│ │ │ └── ResizeHandler.js
│ ├── main.js
│ ├── permission.js
│ ├── router
│ │ ├── index.js
│ │ └── modules
│ │ │ └── basicData.js
│ ├── settings.js
│ ├── store
│ │ ├── getters.js
│ │ ├── index.js
│ │ └── modules
│ │ │ ├── app.js
│ │ │ ├── settings.js
│ │ │ └── user.js
│ ├── styles
│ │ ├── element-ui.scss
│ │ ├── index.scss
│ │ ├── mixin.scss
│ │ ├── sidebar.scss
│ │ ├── transition.scss
│ │ └── variables.scss
│ ├── utils
│ │ ├── auth.js
│ │ ├── get-page-title.js
│ │ ├── index.js
│ │ ├── request.js
│ │ ├── scroll-to.js
│ │ └── validate.js
│ └── views
│ │ ├── 404.vue
│ │ ├── basicData
│ │ ├── articleList.vue
│ │ ├── foodList.vue
│ │ ├── updateLogList.vue
│ │ └── whisperList.vue
│ │ ├── dashboard
│ │ └── index.vue
│ │ └── login
│ │ └── index.vue
└── vue.config.js
├── vue-blog-h5
├── .browserslistrc
├── .editorconfig
├── .env.development
├── .env.production
├── .env.staging
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── .prettierrc
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── api
│ │ ├── common.js
│ │ ├── index.js
│ │ └── user.js
│ ├── assets
│ │ ├── css
│ │ │ ├── index.scss
│ │ │ ├── mixin.scss
│ │ │ └── variables.scss
│ │ └── logo.png
│ ├── components
│ │ ├── Header.vue
│ │ └── TabBar.vue
│ ├── config
│ │ ├── env.development.js
│ │ ├── env.production.js
│ │ ├── env.staging.js
│ │ └── index.js
│ ├── filters
│ │ ├── filter.js
│ │ └── index.js
│ ├── main.js
│ ├── plugins
│ │ └── vant.js
│ ├── router
│ │ ├── index.js
│ │ └── router.config.js
│ ├── store
│ │ ├── getters.js
│ │ ├── index.js
│ │ └── modules
│ │ │ └── app.js
│ ├── utils
│ │ ├── index.js
│ │ ├── request.js
│ │ └── validate.js
│ └── views
│ │ ├── about
│ │ └── index.vue
│ │ ├── album
│ │ └── index.vue
│ │ ├── home
│ │ └── index.vue
│ │ ├── layouts
│ │ └── index.vue
│ │ ├── updateLog
│ │ └── index.vue
│ │ └── whisper
│ │ └── index.vue
└── vue.config.js
└── vue-blog-template
├── .browserslistrc
├── .eslintrc.js
├── .gitignore
├── babel.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ └── styles
│ │ ├── index.scss
│ │ └── normalize.scss
├── main.js
├── router
│ └── index.js
├── store.js
├── utils
│ └── index.js
└── views
│ ├── about
│ └── About.vue
│ ├── album
│ └── Album.vue
│ ├── home
│ └── Home.vue
│ ├── layout
│ ├── components
│ │ ├── Header.vue
│ │ ├── Main.vue
│ │ └── index.js
│ └── index.vue
│ ├── updateLog
│ └── UpdateLog.vue
│ └── whisper
│ └── Whisper.vue
└── vue.config.js
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Jason Chen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## 简介
6 |
7 | [vue-blog-template](https://github.com/chenyicai622/vue-blog-template) 是基于vue2.0,包含了vue-blog-template(PC版)、vue-blog-h5(H5版)、vue-blog-admin(blog后台管理系统)、node-server-api(blog服务端API),用于个人博客的开发和管理。
8 |
9 | - [在线预览](http://cycblog.cn)
10 | - [H5版在线预览](http://m.cycblog.cn) (浏览器访问请切换移动模式)
11 |
12 | **该项目为本人自主设计与开发,一切以学习为主,如有改进及优化建议,请发送本人邮箱chenyicai622@qq.com**
13 |
14 | **如果觉得不错,别忘了右上角,点个星星哟~~**
15 |
16 | ## 前序准备
17 |
18 | 你需要在本地安装 [node](http://nodejs.org/) 和 [git](https://git-scm.com/)。本项目技术栈基于 [ES2015+](http://es6.ruanyifeng.com/)、[vue](https://cn.vuejs.org/index.html)、[vuex](https://vuex.vuejs.org/zh-cn/)、[vue-router](https://router.vuejs.org/zh-cn/) 、[vue-cli](https://github.com/vuejs/vue-cli) 、[axios](https://github.com/axios/axios) 、[element-ui](https://github.com/ElemeFE/element)、[vant](https://vant-contrib.gitee.io/vant/#/zh-CN/)、[express](https://expressjs.com/)、[mongodb](https://www.mongodb.com/3)、[mongoose](https://mongoosejs.com/),提前了解和学习这些知识会对使用本项目有很大的帮助。
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 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | ## 文件
59 |
60 | ```bash
61 | # node-server-api
62 | - api.js API接口
63 | - db.js MongoDB连接配置及接口建表
64 | - index.js 相关配置
65 |
66 | # vue-blog-template/vue-blog-h5
67 | - src
68 | - views
69 | - about 关于
70 | - album 相册
71 | - home 文章
72 | - updateLog 更新
73 | - whisper 留言
74 |
75 | # vue-blog-admin
76 | - src
77 | - views
78 | - basiceData
79 | - articleList.vue 文章
80 | - foodList.vue 图片
81 | - updateLogList.vue 更新
82 | - whisperList.vue 留言
83 | ```
84 |
85 | ## 开发
86 |
87 | ```bash
88 | # 克隆项目
89 | git clone https://github.com/chenyicai622/vue-blog-template.git
90 |
91 | # 进入项目目录
92 | cd vue-blog-template
93 |
94 | # 进入各版本目录
95 | # PC版
96 | cd vue-blog-template
97 | # H5版
98 | cd vue-blog-h5
99 | # Admin
100 | cd vue-blog-admin
101 | # Server API
102 | cd node-server-api
103 |
104 | # 安装依赖
105 | npm install
106 |
107 | # 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
108 | npm install --registry=https://registry.npm.taobao.org
109 |
110 | # 启动服务
111 | # PC版
112 | npm run serve
113 | # H5版
114 | npm run serve
115 | # Admin
116 | npm run dev
117 | # Server API
118 | node index.js
119 | ```
120 |
121 | ## 感谢
122 |
123 | - [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
124 | - [vue-h5-template](https://github.com/sunniejs/vue-h5-template)
125 |
126 | ## License
127 |
128 | [MIT](https://github.com/chenyicai622/vue-blog-template/blob/main/LICENSE)
129 |
130 | Copyright (c) 2021 Jason Chen
131 |
--------------------------------------------------------------------------------
/node-server-api/db.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | // 连接数据库 如果不自己创建 默认test数据库会自动生成
3 | mongoose.connect(
4 | "mongodb://127.0.0.1:27017/blog", //mongodb默认27017端口,自行创建数据库文件
5 | { useNewUrlParser: true, useUnifiedTopology: true },
6 | function(err) {
7 | if (err) {
8 | console.log("连接失败:" + err);
9 | } else {
10 | console.log("连接成功");
11 | }
12 | }
13 | );
14 |
15 | /************** 定义模式Schema **************/
16 | //登录
17 | const userSchema = new mongoose.Schema(
18 | {
19 | username: String,
20 | password: String,
21 | date: {
22 | type: Date,
23 | default: Date.now
24 | }
25 | },
26 | { collection: "user", versionKey: false }
27 | );
28 |
29 | //文章
30 | const articleSchema = new mongoose.Schema(
31 | {
32 | date: String,
33 | title: String,
34 | content: String,
35 | url: String
36 | },
37 | { collection: "articles", versionKey: false }
38 | );
39 |
40 | //留言
41 | const whisperSchema = new mongoose.Schema(
42 | {
43 | userIP: String,
44 | userCity: String,
45 | date: String,
46 | content: String,
47 | avatar: String
48 | },
49 | { collection: "whispers", versionKey: false }
50 | );
51 |
52 | //更新记录
53 | const updateLogSchema = new mongoose.Schema(
54 | {
55 | date: String,
56 | info: String,
57 | content: String
58 | },
59 | { collection: "updateLogs", versionKey: false }
60 | );
61 |
62 | //美食
63 | const foodSchema = new mongoose.Schema(
64 | {
65 | date: String,
66 | url: String,
67 | name: String
68 | },
69 | { collection: "foods", versionKey: false }
70 | );
71 |
72 | /************** 定义模型Model **************/
73 | const Models = {
74 | Food: mongoose.model("Food", foodSchema),
75 | User: mongoose.model("User", userSchema),
76 | Article: mongoose.model("Article", articleSchema),
77 | Whisper: mongoose.model("Whisper", whisperSchema),
78 | UpdateLog: mongoose.model("UpdateLog", updateLogSchema)
79 | };
80 |
81 | module.exports = Models;
82 |
--------------------------------------------------------------------------------
/node-server-api/index.js:
--------------------------------------------------------------------------------
1 | // 引入编写好的api
2 | const api = require('./api')
3 | // 引入文件模块
4 | const fs = require('fs')
5 | // 引入处理路径的模块
6 | const path = require('path')
7 | // 引入处理post数据的模块
8 | const bodyParser = require('body-parser')
9 | // 引入Express
10 | const express = require('express')
11 | const app = express()
12 |
13 | var cors = require('cors')
14 | app.use(cors())
15 | app.use(bodyParser.json())
16 | app.use(bodyParser.urlencoded({ extended: false }))
17 | app.use(api)
18 |
19 | // 访问静态资源文件 这里是访问所有dist目录下的静态资源文件
20 | app.use(express.static(path.resolve(__dirname, '../web')))
21 |
22 | // 因为是单页应用 所有请求都走/dist/index.html
23 | app.get('*', function (req, res) {
24 | const html = fs.readFileSync(
25 | path.resolve(__dirname, '../web/index.html'),
26 | 'utf-8'
27 | )
28 | res.send(html)
29 | })
30 |
31 | app.listen(8090)
32 | console.log('success listen…………')
33 |
--------------------------------------------------------------------------------
/node-server-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog-server",
3 | "author": "Jason Chen",
4 | "version": "1.0.0",
5 | "description": "a blog server",
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "prd": "pm2 start index.js --name='server'"
10 | },
11 | "license": "MIT",
12 | "dependencies": {
13 | "body-parser": "^1.19.0",
14 | "cors": "^2.8.5",
15 | "express": "^4.17.1",
16 | "jsonwebtoken": "^8.5.1",
17 | "mongoose": "^5.6.4"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/vue-blog-admin/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/vue-blog-admin/.env.development:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'development'
3 |
4 | # base api
5 | VUE_APP_BASE_API = 'http://localhost:8090'
6 |
7 | # vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
8 | # to control whether the babel-plugin-dynamic-import-node plugin is enabled.
9 | # It only does one thing by converting all import() to require().
10 | # This configuration can significantly increase the speed of hot updates,
11 | # when you have a large number of pages.
12 | # Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
13 |
14 | VUE_CLI_BABEL_TRANSPILE_MODULES = true
15 |
--------------------------------------------------------------------------------
/vue-blog-admin/.env.production:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'production'
3 |
4 | # base api
5 | VUE_APP_BASE_API = 'http://127.0.0.1:8090'
6 |
7 |
--------------------------------------------------------------------------------
/vue-blog-admin/.env.staging:
--------------------------------------------------------------------------------
1 | NODE_ENV = production
2 |
3 | # just a flag
4 | ENV = 'staging'
5 |
6 | # base api
7 | VUE_APP_BASE_API = '/stage-api'
8 |
9 |
--------------------------------------------------------------------------------
/vue-blog-admin/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | src/assets
3 | public
4 | dist
5 |
--------------------------------------------------------------------------------
/vue-blog-admin/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | package-lock.json
8 | tests/**/coverage/
9 |
10 | # Editor directories and files
11 | .idea
12 | .vscode
13 | *.suo
14 | *.ntvs*
15 | *.njsproj
16 | *.sln
17 |
--------------------------------------------------------------------------------
/vue-blog-admin/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "eslintIntegration": true,
3 | "singleQuote": true,
4 | "semi": false
5 | }
6 |
--------------------------------------------------------------------------------
/vue-blog-admin/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: 10
3 | script: npm run test
4 | notifications:
5 | email: false
6 |
--------------------------------------------------------------------------------
/vue-blog-admin/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/vue-blog-admin/build/index.js:
--------------------------------------------------------------------------------
1 | const { run } = require('runjs')
2 | const chalk = require('chalk')
3 | const config = require('../vue.config.js')
4 | const rawArgv = process.argv.slice(2)
5 | const args = rawArgv.join(' ')
6 |
7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
8 | const report = rawArgv.includes('--report')
9 |
10 | run(`vue-cli-service build ${args}`)
11 |
12 | const port = 9526
13 | const publicPath = config.publicPath
14 |
15 | var connect = require('connect')
16 | var serveStatic = require('serve-static')
17 | const app = connect()
18 |
19 | app.use(
20 | publicPath,
21 | serveStatic('./dist', {
22 | index: ['index.html', '/']
23 | })
24 | )
25 |
26 | app.listen(port, function() {
27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
28 | if (report) {
29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
30 | }
31 | })
32 | } else {
33 | run(`vue-cli-service build ${args}`)
34 | }
35 |
--------------------------------------------------------------------------------
/vue-blog-admin/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
3 | transform: {
4 | '^.+\\.vue$': 'vue-jest',
5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
6 | 'jest-transform-stub',
7 | '^.+\\.jsx?$': 'babel-jest'
8 | },
9 | moduleNameMapper: {
10 | '^@/(.*)$': '/src/$1'
11 | },
12 | snapshotSerializers: ['jest-serializer-vue'],
13 | testMatch: [
14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
15 | ],
16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
17 | coverageDirectory: '/tests/unit/coverage',
18 | // 'collectCoverage': true,
19 | 'coverageReporters': [
20 | 'lcov',
21 | 'text-summary'
22 | ],
23 | testURL: 'http://localhost/'
24 | }
25 |
--------------------------------------------------------------------------------
/vue-blog-admin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog-admin",
3 | "version": "1.0.0",
4 | "description": "a blog admin",
5 | "author": "Jason Chen",
6 | "license": "MIT",
7 | "scripts": {
8 | "dev": "vue-cli-service serve",
9 | "build:prod": "vue-cli-service build",
10 | "build:stage": "vue-cli-service build --mode staging",
11 | "preview": "node build/index.js --preview",
12 | "lint": "eslint --ext .js,.vue src",
13 | "test:unit": "jest --clearCache && vue-cli-service test:unit",
14 | "test:ci": "npm run lint && npm run test:unit",
15 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
16 | },
17 | "dependencies": {
18 | "axios": "0.18.1",
19 | "element-ui": "2.7.2",
20 | "js-cookie": "2.2.0",
21 | "normalize.css": "7.0.0",
22 | "nprogress": "0.2.0",
23 | "path-to-regexp": "2.4.0",
24 | "vue": "2.6.10",
25 | "vue-router": "3.0.6",
26 | "vuex": "3.1.0"
27 | },
28 | "devDependencies": {
29 | "@babel/core": "7.0.0",
30 | "@babel/register": "7.0.0",
31 | "@vue/cli-plugin-babel": "3.6.0",
32 | "@vue/cli-plugin-eslint": "^3.9.1",
33 | "@vue/cli-plugin-unit-jest": "3.6.3",
34 | "@vue/cli-service": "3.6.0",
35 | "@vue/test-utils": "1.0.0-beta.29",
36 | "autoprefixer": "^9.5.1",
37 | "babel-core": "7.0.0-bridge.0",
38 | "babel-eslint": "10.0.1",
39 | "babel-jest": "23.6.0",
40 | "chalk": "2.4.2",
41 | "connect": "3.6.6",
42 | "eslint": "5.15.3",
43 | "eslint-plugin-vue": "5.2.2",
44 | "html-webpack-plugin": "3.2.0",
45 | "mockjs": "1.0.1-beta3",
46 | "node-sass": "^4.9.0",
47 | "runjs": "^4.3.2",
48 | "sass-loader": "^7.1.0",
49 | "script-ext-html-webpack-plugin": "2.1.3",
50 | "script-loader": "0.7.2",
51 | "serve-static": "^1.13.2",
52 | "svg-sprite-loader": "4.1.3",
53 | "svgo": "1.2.2",
54 | "vue-template-compiler": "2.6.10"
55 | },
56 | "engines": {
57 | "node": ">=8.9",
58 | "npm": ">= 3.0.0"
59 | },
60 | "browserslist": [
61 | "> 1%",
62 | "last 2 versions"
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------
/vue-blog-admin/postcss.config.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | 'plugins': {
5 | // to edit target browsers: use "browserslist" field in package.json
6 | 'autoprefixer': {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/vue-blog-admin/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jcyicai/vue-blog-template/525110674b567994f807ac23ed23556a3f12b043/vue-blog-admin/public/favicon.ico
--------------------------------------------------------------------------------
/vue-blog-admin/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 | <%= webpackConfig.name %>
12 |
164 |
165 |
166 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/api/article.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function fetchList(query) {
4 | return request({
5 | url: '/article/list',
6 | method: 'get',
7 | params: query
8 | })
9 | }
10 |
11 | export function fetchArticle(id) {
12 | return request({
13 | url: '/article/detail',
14 | method: 'get',
15 | params: { id }
16 | })
17 | }
18 |
19 | export function createArticle(data) {
20 | return request({
21 | url: '/article/create',
22 | method: 'post',
23 | data
24 | })
25 | }
26 |
27 | export function updateArticle(data) {
28 | return request({
29 | url: '/article/update',
30 | method: 'post',
31 | data
32 | })
33 | }
34 |
35 | export function deleteArticle(data) {
36 | return request({
37 | url: '/article/delete',
38 | method: 'post',
39 | data
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/api/food.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function fetchList(query) {
4 | return request({
5 | url: '/food/list',
6 | method: 'get',
7 | params: query
8 | })
9 | }
10 |
11 | export function fetchFood(id) {
12 | return request({
13 | url: '/food/detail',
14 | method: 'get',
15 | params: { id }
16 | })
17 | }
18 |
19 | export function createFood(data) {
20 | return request({
21 | url: '/food/create',
22 | method: 'post',
23 | data
24 | })
25 | }
26 |
27 | export function updateFood(data) {
28 | return request({
29 | url: '/food/update',
30 | method: 'post',
31 | data
32 | })
33 | }
34 |
35 | export function deleteFood(data) {
36 | return request({
37 | url: '/food/delete',
38 | method: 'post',
39 | data
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/api/updateLog.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function fetchList(query) {
4 | return request({
5 | url: '/updateLog/list',
6 | method: 'get',
7 | params: query
8 | })
9 | }
10 |
11 | export function fetchLog(id) {
12 | return request({
13 | url: '/updateLog/detail',
14 | method: 'get',
15 | params: { id }
16 | })
17 | }
18 |
19 | export function createLog(data) {
20 | return request({
21 | url: '/updateLog/create',
22 | method: 'post',
23 | data
24 | })
25 | }
26 |
27 | export function updateLog(data) {
28 | return request({
29 | url: '/updateLog/update',
30 | method: 'post',
31 | data
32 | })
33 | }
34 |
35 | export function deleteLog(data) {
36 | return request({
37 | url: '/updateLog/delete',
38 | method: 'post',
39 | data
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/api/user.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function login(data) {
4 | return request({
5 | url: '/user/login',
6 | method: 'post',
7 | data
8 | })
9 | }
10 |
11 | export function getInfo(token) {
12 | return request({
13 | url: '/user/info',
14 | method: 'get',
15 | params: { token }
16 | })
17 | }
18 |
19 | export function logout() {
20 | return request({
21 | url: '/user/logout',
22 | method: 'post'
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/api/whisper.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function fetchList(query) {
4 | return request({
5 | url: '/whisper/list',
6 | method: 'get',
7 | params: query
8 | })
9 | }
10 |
11 | export function fetchWhisper(id) {
12 | return request({
13 | url: '/whisper/detail',
14 | method: 'get',
15 | params: { id }
16 | })
17 | }
18 |
19 | export function createWhisper(data) {
20 | return request({
21 | url: '/whisper/create',
22 | method: 'post',
23 | data
24 | })
25 | }
26 |
27 | export function updateWhisper(data) {
28 | return request({
29 | url: '/whisper/update',
30 | method: 'post',
31 | data
32 | })
33 | }
34 |
35 | export function deleteWhisper(data) {
36 | return request({
37 | url: '/whisper/delete',
38 | method: 'post',
39 | data
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/components/Breadcrumb/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item.meta.title }}
6 | {{ item.meta.title }}
7 |
8 |
9 |
10 |
11 |
12 |
65 |
66 |
79 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
32 |
33 |
45 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/components/Pagination/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
92 |
93 |
102 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/components/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
47 |
48 |
63 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/clipboard/clipboard.js:
--------------------------------------------------------------------------------
1 | // Inspired by https://github.com/Inndy/vue-clipboard2
2 | const Clipboard = require('clipboard')
3 | if (!Clipboard) {
4 | throw new Error('you should npm install `clipboard` --save at first ')
5 | }
6 |
7 | export default {
8 | bind(el, binding) {
9 | if (binding.arg === 'success') {
10 | el._v_clipboard_success = binding.value
11 | } else if (binding.arg === 'error') {
12 | el._v_clipboard_error = binding.value
13 | } else {
14 | const clipboard = new Clipboard(el, {
15 | text() { return binding.value },
16 | action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
17 | })
18 | clipboard.on('success', e => {
19 | const callback = el._v_clipboard_success
20 | callback && callback(e) // eslint-disable-line
21 | })
22 | clipboard.on('error', e => {
23 | const callback = el._v_clipboard_error
24 | callback && callback(e) // eslint-disable-line
25 | })
26 | el._v_clipboard = clipboard
27 | }
28 | },
29 | update(el, binding) {
30 | if (binding.arg === 'success') {
31 | el._v_clipboard_success = binding.value
32 | } else if (binding.arg === 'error') {
33 | el._v_clipboard_error = binding.value
34 | } else {
35 | el._v_clipboard.text = function() { return binding.value }
36 | el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
37 | }
38 | },
39 | unbind(el, binding) {
40 | if (binding.arg === 'success') {
41 | delete el._v_clipboard_success
42 | } else if (binding.arg === 'error') {
43 | delete el._v_clipboard_error
44 | } else {
45 | el._v_clipboard.destroy()
46 | delete el._v_clipboard
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/clipboard/index.js:
--------------------------------------------------------------------------------
1 | import Clipboard from './clipboard'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('Clipboard', Clipboard)
5 | }
6 |
7 | if (window.Vue) {
8 | window.clipboard = Clipboard
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | Clipboard.install = install
13 | export default Clipboard
14 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/el-drag-dialog/drag.js:
--------------------------------------------------------------------------------
1 | export default {
2 | bind(el, binding, vnode) {
3 | const dialogHeaderEl = el.querySelector('.el-dialog__header')
4 | const dragDom = el.querySelector('.el-dialog')
5 | dialogHeaderEl.style.cssText += ';cursor:move;'
6 | dragDom.style.cssText += ';top:0px;'
7 |
8 | // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
9 | const getStyle = (function() {
10 | if (window.document.currentStyle) {
11 | return (dom, attr) => dom.currentStyle[attr]
12 | } else {
13 | return (dom, attr) => getComputedStyle(dom, false)[attr]
14 | }
15 | })()
16 |
17 | dialogHeaderEl.onmousedown = (e) => {
18 | // 鼠标按下,计算当前元素距离可视区的距离
19 | const disX = e.clientX - dialogHeaderEl.offsetLeft
20 | const disY = e.clientY - dialogHeaderEl.offsetTop
21 |
22 | const dragDomWidth = dragDom.offsetWidth
23 | const dragDomHeight = dragDom.offsetHeight
24 |
25 | const screenWidth = document.body.clientWidth
26 | const screenHeight = document.body.clientHeight
27 |
28 | const minDragDomLeft = dragDom.offsetLeft
29 | const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
30 |
31 | const minDragDomTop = dragDom.offsetTop
32 | const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
33 |
34 | // 获取到的值带px 正则匹配替换
35 | let styL = getStyle(dragDom, 'left')
36 | let styT = getStyle(dragDom, 'top')
37 |
38 | if (styL.includes('%')) {
39 | styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
40 | styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
41 | } else {
42 | styL = +styL.replace(/\px/g, '')
43 | styT = +styT.replace(/\px/g, '')
44 | }
45 |
46 | document.onmousemove = function(e) {
47 | // 通过事件委托,计算移动的距离
48 | let left = e.clientX - disX
49 | let top = e.clientY - disY
50 |
51 | // 边界处理
52 | if (-(left) > minDragDomLeft) {
53 | left = -minDragDomLeft
54 | } else if (left > maxDragDomLeft) {
55 | left = maxDragDomLeft
56 | }
57 |
58 | if (-(top) > minDragDomTop) {
59 | top = -minDragDomTop
60 | } else if (top > maxDragDomTop) {
61 | top = maxDragDomTop
62 | }
63 |
64 | // 移动当前元素
65 | dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
66 |
67 | // emit onDrag event
68 | vnode.child.$emit('dragDialog')
69 | }
70 |
71 | document.onmouseup = function(e) {
72 | document.onmousemove = null
73 | document.onmouseup = null
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/el-drag-dialog/index.js:
--------------------------------------------------------------------------------
1 | import drag from './drag'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('el-drag-dialog', drag)
5 | }
6 |
7 | if (window.Vue) {
8 | window['el-drag-dialog'] = drag
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | drag.install = install
13 | export default drag
14 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/el-table/adaptive.js:
--------------------------------------------------------------------------------
1 | import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'
2 |
3 | /**
4 | * How to use
5 | * ...
6 | * el-table height is must be set
7 | * bottomOffset: 30(default) // The height of the table from the bottom of the page.
8 | */
9 |
10 | const doResize = (el, binding, vnode) => {
11 | const { componentInstance: $table } = vnode
12 |
13 | const { value } = binding
14 |
15 | if (!$table.height) {
16 | throw new Error(`el-$table must set the height. Such as height='100px'`)
17 | }
18 | const bottomOffset = (value && value.bottomOffset) || 30
19 |
20 | if (!$table) return
21 |
22 | const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset
23 | $table.layout.setHeight(height)
24 | $table.doLayout()
25 | }
26 |
27 | export default {
28 | bind(el, binding, vnode) {
29 | el.resizeListener = () => {
30 | doResize(el, binding, vnode)
31 | }
32 | // parameter 1 is must be "Element" type
33 | addResizeListener(window.document.body, el.resizeListener)
34 | },
35 | inserted(el, binding, vnode) {
36 | doResize(el, binding, vnode)
37 | },
38 | unbind(el) {
39 | removeResizeListener(window.document.body, el.resizeListener)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/el-table/index.js:
--------------------------------------------------------------------------------
1 | import adaptive from './adaptive'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('el-height-adaptive-table', adaptive)
5 | }
6 |
7 | if (window.Vue) {
8 | window['el-height-adaptive-table'] = adaptive
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | adaptive.install = install
13 | export default adaptive
14 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/permission/index.js:
--------------------------------------------------------------------------------
1 | import permission from './permission'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('permission', permission)
5 | }
6 |
7 | if (window.Vue) {
8 | window['permission'] = permission
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | permission.install = install
13 | export default permission
14 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/permission/permission.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | export default {
4 | inserted(el, binding, vnode) {
5 | const { value } = binding
6 | const roles = store.getters && store.getters.roles
7 | if (value && value instanceof Array && value.length > 0) {
8 | const permissionRoles = value
9 |
10 | const hasPermission = roles.some(role => {
11 | return permissionRoles.includes(role)
12 | })
13 |
14 | if (!hasPermission) {
15 | el.parentNode && el.parentNode.removeChild(el)
16 | }
17 | } else {
18 | throw new Error(`need roles! Like v-permission="['admin','editor']"`)
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/sticky.js:
--------------------------------------------------------------------------------
1 | const vueSticky = {}
2 | let listenAction
3 | vueSticky.install = Vue => {
4 | Vue.directive('sticky', {
5 | inserted(el, binding) {
6 | const params = binding.value || {}
7 | const stickyTop = params.stickyTop || 0
8 | const zIndex = params.zIndex || 1000
9 | const elStyle = el.style
10 |
11 | elStyle.position = '-webkit-sticky'
12 | elStyle.position = 'sticky'
13 | // if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)
14 | // if (~elStyle.position.indexOf('sticky')) {
15 | // elStyle.top = `${stickyTop}px`;
16 | // elStyle.zIndex = zIndex;
17 | // return
18 | // }
19 | const elHeight = el.getBoundingClientRect().height
20 | const elWidth = el.getBoundingClientRect().width
21 | elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`
22 |
23 | const parentElm = el.parentNode || document.documentElement
24 | const placeholder = document.createElement('div')
25 | placeholder.style.display = 'none'
26 | placeholder.style.width = `${elWidth}px`
27 | placeholder.style.height = `${elHeight}px`
28 | parentElm.insertBefore(placeholder, el)
29 |
30 | let active = false
31 |
32 | const getScroll = (target, top) => {
33 | const prop = top ? 'pageYOffset' : 'pageXOffset'
34 | const method = top ? 'scrollTop' : 'scrollLeft'
35 | let ret = target[prop]
36 | if (typeof ret !== 'number') {
37 | ret = window.document.documentElement[method]
38 | }
39 | return ret
40 | }
41 |
42 | const sticky = () => {
43 | if (active) {
44 | return
45 | }
46 | if (!elStyle.height) {
47 | elStyle.height = `${el.offsetHeight}px`
48 | }
49 |
50 | elStyle.position = 'fixed'
51 | elStyle.width = `${elWidth}px`
52 | placeholder.style.display = 'inline-block'
53 | active = true
54 | }
55 |
56 | const reset = () => {
57 | if (!active) {
58 | return
59 | }
60 |
61 | elStyle.position = ''
62 | placeholder.style.display = 'none'
63 | active = false
64 | }
65 |
66 | const check = () => {
67 | const scrollTop = getScroll(window, true)
68 | const offsetTop = el.getBoundingClientRect().top
69 | if (offsetTop < stickyTop) {
70 | sticky()
71 | } else {
72 | if (scrollTop < elHeight + stickyTop) {
73 | reset()
74 | }
75 | }
76 | }
77 | listenAction = () => {
78 | check()
79 | }
80 |
81 | window.addEventListener('scroll', listenAction)
82 | },
83 |
84 | unbind() {
85 | window.removeEventListener('scroll', listenAction)
86 | }
87 | })
88 | }
89 |
90 | export default vueSticky
91 |
92 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/waves/index.js:
--------------------------------------------------------------------------------
1 | import waves from './waves'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('waves', waves)
5 | }
6 |
7 | if (window.Vue) {
8 | window.waves = waves
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | waves.install = install
13 | export default waves
14 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/waves/waves.css:
--------------------------------------------------------------------------------
1 | .waves-ripple {
2 | position: absolute;
3 | border-radius: 100%;
4 | background-color: rgba(0, 0, 0, 0.15);
5 | background-clip: padding-box;
6 | pointer-events: none;
7 | -webkit-user-select: none;
8 | -moz-user-select: none;
9 | -ms-user-select: none;
10 | user-select: none;
11 | -webkit-transform: scale(0);
12 | -ms-transform: scale(0);
13 | transform: scale(0);
14 | opacity: 1;
15 | }
16 |
17 | .waves-ripple.z-active {
18 | opacity: 0;
19 | -webkit-transform: scale(2);
20 | -ms-transform: scale(2);
21 | transform: scale(2);
22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out;
25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
26 | }
--------------------------------------------------------------------------------
/vue-blog-admin/src/directive/waves/waves.js:
--------------------------------------------------------------------------------
1 | import './waves.css'
2 |
3 | const context = '@@wavesContext'
4 |
5 | function handleClick(el, binding) {
6 | function handle(e) {
7 | const customOpts = Object.assign({}, binding.value)
8 | const opts = Object.assign({
9 | ele: el, // 波纹作用元素
10 | type: 'hit', // hit 点击位置扩散 center中心点扩展
11 | color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
12 | },
13 | customOpts
14 | )
15 | const target = opts.ele
16 | if (target) {
17 | target.style.position = 'relative'
18 | target.style.overflow = 'hidden'
19 | const rect = target.getBoundingClientRect()
20 | let ripple = target.querySelector('.waves-ripple')
21 | if (!ripple) {
22 | ripple = document.createElement('span')
23 | ripple.className = 'waves-ripple'
24 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
25 | target.appendChild(ripple)
26 | } else {
27 | ripple.className = 'waves-ripple'
28 | }
29 | switch (opts.type) {
30 | case 'center':
31 | ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
32 | ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
33 | break
34 | default:
35 | ripple.style.top =
36 | (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
37 | document.body.scrollTop) + 'px'
38 | ripple.style.left =
39 | (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
40 | document.body.scrollLeft) + 'px'
41 | }
42 | ripple.style.backgroundColor = opts.color
43 | ripple.className = 'waves-ripple z-active'
44 | return false
45 | }
46 | }
47 |
48 | if (!el[context]) {
49 | el[context] = {
50 | removeHandle: handle
51 | }
52 | } else {
53 | el[context].removeHandle = handle
54 | }
55 |
56 | return handle
57 | }
58 |
59 | export default {
60 | bind(el, binding) {
61 | el.addEventListener('click', handleClick(el, binding), false)
62 | },
63 | update(el, binding) {
64 | el.removeEventListener('click', el[context].removeHandle, false)
65 | el.addEventListener('click', handleClick(el, binding), false)
66 | },
67 | unbind(el) {
68 | el.removeEventListener('click', el[context].removeHandle, false)
69 | el[context] = null
70 | delete el[context]
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import SvgIcon from '@/components/SvgIcon'// svg component
3 |
4 | // register globally
5 | Vue.component('svg-icon', SvgIcon)
6 |
7 | const req = require.context('./svg', false, /\.svg$/)
8 | const requireAll = requireContext => requireContext.keys().map(requireContext)
9 | requireAll(req)
10 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/dashboard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/example.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/eye-open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/form.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/nested.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/password.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/tree.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svg/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/icons/svgo.yml:
--------------------------------------------------------------------------------
1 | # replace default config
2 |
3 | # multipass: true
4 | # full: true
5 |
6 | plugins:
7 |
8 | # - name
9 | #
10 | # or:
11 | # - name: false
12 | # - name: true
13 | #
14 | # or:
15 | # - name:
16 | # param1: 1
17 | # param2: 2
18 |
19 | - removeAttrs:
20 | attrs:
21 | - 'fill'
22 | - 'fill-rule'
23 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
19 |
20 |
32 |
33 |
41 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/components/Navbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
26 |
27 |
28 |
29 |
56 |
57 |
135 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/components/Sidebar/FixiOSBug.js:
--------------------------------------------------------------------------------
1 | export default {
2 | computed: {
3 | device() {
4 | return this.$store.state.app.device
5 | }
6 | },
7 | mounted() {
8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug
9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135
10 | this.fixBugIniOS()
11 | },
12 | methods: {
13 | fixBugIniOS() {
14 | const $subMenu = this.$refs.subMenu
15 | if ($subMenu) {
16 | const handleMouseleave = $subMenu.handleMouseleave
17 | $subMenu.handleMouseleave = (e) => {
18 | if (this.device === 'mobile') {
19 | return
20 | }
21 | handleMouseleave(e)
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/components/Sidebar/Item.vue:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/components/Sidebar/Link.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
37 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/components/Sidebar/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
33 |
34 |
83 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/components/Sidebar/SidebarItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
96 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
57 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Navbar } from './Navbar'
2 | export { default as Sidebar } from './Sidebar'
3 | export { default as AppMain } from './AppMain'
4 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
52 |
53 |
97 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/layout/mixin/ResizeHandler.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | const { body } = document
4 | const WIDTH = 992 // refer to Bootstrap's responsive design
5 |
6 | export default {
7 | watch: {
8 | $route() {//route
9 | if (this.device === 'mobile' && this.sidebar.opened) {
10 | store.dispatch('app/closeSideBar', { withoutAnimation: false })
11 | }
12 | }
13 | },
14 | beforeMount() {
15 | window.addEventListener('resize', this.$_resizeHandler)
16 | },
17 | beforeDestroy() {
18 | window.removeEventListener('resize', this.$_resizeHandler)
19 | },
20 | mounted() {
21 | const isMobile = this.$_isMobile()
22 | if (isMobile) {
23 | store.dispatch('app/toggleDevice', 'mobile')
24 | store.dispatch('app/closeSideBar', { withoutAnimation: true })
25 | }
26 | },
27 | methods: {
28 | // use $_ for mixins properties
29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
30 | $_isMobile() {
31 | const rect = body.getBoundingClientRect()
32 | return rect.width - 1 < WIDTH
33 | },
34 | $_resizeHandler() {
35 | if (!document.hidden) {
36 | const isMobile = this.$_isMobile()
37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
38 |
39 | if (isMobile) {
40 | store.dispatch('app/closeSideBar', { withoutAnimation: true })
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Cookies from 'js-cookie'
3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets
4 |
5 | import ElementUI from 'element-ui'
6 | import 'element-ui/lib/theme-chalk/index.css'
7 | import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
8 |
9 | import '@/styles/index.scss' // global css
10 |
11 | import App from './App'
12 | import store from './store'
13 | import router from './router'
14 |
15 | import '@/icons' // icon
16 | import '@/permission' // permission control
17 |
18 | // set ElementUI lang to EN
19 | Vue.use(ElementUI, { locale, size: Cookies.get('size') || 'medium' })
20 |
21 | Vue.config.productionTip = false
22 |
23 | new Vue({
24 | el: '#app',
25 | router,
26 | store,
27 | render: h => h(App)
28 | })
29 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/permission.js:
--------------------------------------------------------------------------------
1 | import router from './router'
2 | import store from './store'
3 | import { Message } from 'element-ui'
4 | import NProgress from 'nprogress' // progress bar
5 | import 'nprogress/nprogress.css' // progress bar style
6 | import { getToken } from '@/utils/auth' // get token from cookie
7 | import getPageTitle from '@/utils/get-page-title'
8 |
9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration
10 |
11 | const whiteList = ['/login'] // no redirect whitelist
12 |
13 | router.beforeEach(async(to, from, next) => {
14 | // start progress bar
15 | NProgress.start()
16 |
17 | // set page title
18 | document.title = getPageTitle(to.meta.title)
19 |
20 | // determine whether the user has logged in
21 | const hasToken = getToken()
22 |
23 | if (hasToken) {
24 | if (to.path === '/login') {
25 | // if is logged in, redirect to the home page
26 | next({ path: '/' })
27 | NProgress.done()
28 | } else {
29 | const hasGetUserInfo = store.getters.name
30 | if (hasGetUserInfo) {
31 | next()
32 | } else {
33 | try {
34 | // get user info
35 | await store.dispatch('user/getInfo')
36 | next()
37 | } catch (error) {
38 | // remove token and go to login page to re-login
39 | await store.dispatch('user/resetToken')
40 | Message.error(error || 'Has Error')
41 | next(`/login?redirect=${to.path}`)
42 | NProgress.done()
43 | }
44 | }
45 | }
46 | } else {
47 | /* has no token*/
48 |
49 | if (whiteList.indexOf(to.path) !== -1) {
50 | // in the free login whitelist, go directly
51 | next()
52 | } else {
53 | // other pages that do not have permission to access are redirected to the login page.
54 | next(`/login?redirect=${to.path}`)
55 | NProgress.done()
56 | }
57 | }
58 | })
59 |
60 | router.afterEach(() => {
61 | // finish progress bar
62 | NProgress.done()
63 | })
64 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 |
6 | /* Layout */
7 | import Layout from '@/layout'
8 | import basicDataRouter from './modules/basicData'
9 | /**
10 | * Note: sub-menu only appear when route children.length >= 1
11 | * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
12 | *
13 | * hidden: true if set true, item will not show in the sidebar(default is false)
14 | * alwaysShow: true if set true, will always show the root menu
15 | * if not set alwaysShow, when item has more than one children route,
16 | * it will becomes nested mode, otherwise not show the root menu
17 | * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
18 | * name:'router-name' the name is used by (must set!!!)
19 | * meta : {
20 | roles: ['admin','editor'] control the page roles (you can set multiple roles)
21 | title: 'title' the name show in sidebar and breadcrumb (recommend set)
22 | icon: 'svg-name' the icon show in the sidebar
23 | breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
24 | activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
25 | }
26 | */
27 |
28 | /**
29 | * constantRoutes
30 | * a base page that does not have permission requirements
31 | * all roles can be accessed
32 | */
33 | export const constantRoutes = [
34 | {
35 | path: '/login',
36 | component: () => import('@/views/login/index'),
37 | hidden: true
38 | },
39 |
40 | {
41 | path: '/404',
42 | component: () => import('@/views/404'),
43 | hidden: true
44 | },
45 | {
46 | path: '/',
47 | component: Layout,
48 | redirect: '/dashboard',
49 | children: [{
50 | path: 'dashboard',
51 | name: '首页',
52 | component: () => import('@/views/dashboard/index'),
53 | meta: { title: '首页', icon: 'dashboard' }
54 | }]
55 | },
56 | basicDataRouter,
57 | // 404 page must be placed at the end !!!
58 | { path: '*', redirect: '/404', hidden: true }
59 | ]
60 |
61 | const createRouter = () => new Router({
62 | mode: 'history', // require service support
63 | scrollBehavior: () => ({ y: 0 }),
64 | routes: constantRoutes
65 | })
66 |
67 | const router = createRouter()
68 |
69 | export function resetRouter() {
70 | const newRouter = createRouter()
71 | router.matcher = newRouter.matcher // reset router
72 | }
73 |
74 | export default router
75 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/router/modules/basicData.js:
--------------------------------------------------------------------------------
1 | import Layout from '@/layout'
2 |
3 | const basicDataRouter = {
4 | path: '/basicData',
5 | component: Layout,
6 | redirect: '/basicData/articleList',
7 | name: '数据管理',
8 | meta: { title: '数据管理', icon: 'example' },
9 | alwaysShow: true,
10 | children: [
11 | {
12 | path: 'articleList',
13 | name: '文章',
14 | component: () => import('@/views/basicData/articleList'),
15 | meta: { title: '文章' }
16 | },
17 | {
18 | path: 'whisperList',
19 | name: '留言',
20 | component: () => import('@/views/basicData/whisperList'),
21 | meta: { title: '留言' }
22 | },
23 | {
24 | path: 'updateLogList',
25 | name: '更新记录',
26 | component: () => import('@/views/basicData/updateLogList'),
27 | meta: { title: '更新记录' }
28 | },
29 | {
30 | path: 'foodList',
31 | name: '美食',
32 | component: () => import('@/views/basicData/foodList'),
33 | meta: { title: '美食' }
34 | }
35 | ]
36 | }
37 |
38 | export default basicDataRouter
39 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/settings.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | title: 'CYC Blog Admin',
4 |
5 | /**
6 | * @type {boolean} true | false
7 | * @description Whether fix the header
8 | */
9 | fixedHeader: true,
10 |
11 | /**
12 | * @type {boolean} true | false
13 | * @description Whether show the logo in sidebar
14 | */
15 | sidebarLogo: true
16 | }
17 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | sidebar: state => state.app.sidebar,
3 | device: state => state.app.device,
4 | token: state => state.user.token,
5 | avatar: state => state.user.avatar,
6 | name: state => state.user.name
7 | }
8 | export default getters
9 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import getters from './getters'
4 | import app from './modules/app'
5 | import settings from './modules/settings'
6 | import user from './modules/user'
7 |
8 | Vue.use(Vuex)
9 |
10 | const store = new Vuex.Store({
11 | modules: {
12 | app,
13 | settings,
14 | user
15 | },
16 | getters
17 | })
18 |
19 | export default store
20 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const state = {
4 | sidebar: {
5 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
6 | withoutAnimation: false
7 | },
8 | device: 'desktop'
9 | }
10 |
11 | const mutations = {
12 | TOGGLE_SIDEBAR: state => {
13 | state.sidebar.opened = !state.sidebar.opened
14 | state.sidebar.withoutAnimation = false
15 | if (state.sidebar.opened) {
16 | Cookies.set('sidebarStatus', 1)
17 | } else {
18 | Cookies.set('sidebarStatus', 0)
19 | }
20 | },
21 | CLOSE_SIDEBAR: (state, withoutAnimation) => {
22 | Cookies.set('sidebarStatus', 0)
23 | state.sidebar.opened = false
24 | state.sidebar.withoutAnimation = withoutAnimation
25 | },
26 | TOGGLE_DEVICE: (state, device) => {
27 | state.device = device
28 | }
29 | }
30 |
31 | const actions = {
32 | toggleSideBar({ commit }) {
33 | commit('TOGGLE_SIDEBAR')
34 | },
35 | closeSideBar({ commit }, { withoutAnimation }) {
36 | commit('CLOSE_SIDEBAR', withoutAnimation)
37 | },
38 | toggleDevice({ commit }, device) {
39 | commit('TOGGLE_DEVICE', device)
40 | }
41 | }
42 |
43 | export default {
44 | namespaced: true,
45 | state,
46 | mutations,
47 | actions
48 | }
49 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/store/modules/settings.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from '@/settings'
2 |
3 | const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
4 |
5 | const state = {
6 | showSettings: showSettings,
7 | fixedHeader: fixedHeader,
8 | sidebarLogo: sidebarLogo
9 | }
10 |
11 | const mutations = {
12 | CHANGE_SETTING: (state, { key, value }) => {
13 | if (state.hasOwnProperty(key)) {
14 | state[key] = value
15 | }
16 | }
17 | }
18 |
19 | const actions = {
20 | changeSetting({ commit }, data) {
21 | commit('CHANGE_SETTING', data)
22 | }
23 | }
24 |
25 | export default {
26 | namespaced: true,
27 | state,
28 | mutations,
29 | actions
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | import { login, logout, getInfo } from '@/api/user'
2 | import { getToken, setToken, removeToken } from '@/utils/auth'
3 | import { resetRouter } from '@/router'
4 |
5 | const state = {
6 | token: getToken(),
7 | name: '',
8 | avatar: ''
9 | }
10 |
11 | const mutations = {
12 | SET_TOKEN: (state, token) => {
13 | state.token = token
14 | },
15 | SET_NAME: (state, name) => {
16 | state.name = name
17 | },
18 | SET_AVATAR: (state, avatar) => {
19 | state.avatar = avatar
20 | }
21 | }
22 |
23 | const actions = {
24 | // user login
25 | login({ commit }, userInfo) {
26 | const { username, password } = userInfo
27 | return new Promise((resolve, reject) => {
28 | login({ username: username.trim(), password: password })
29 | .then(response => {
30 | const { data } = response
31 | commit('SET_TOKEN', data.token)
32 | setToken(data.token)
33 | resolve()
34 | })
35 | .catch(error => {
36 | reject(error)
37 | })
38 | })
39 | },
40 |
41 | // get user info
42 | getInfo({ commit, state }) {
43 | return new Promise((resolve, reject) => {
44 | getInfo(state.token)
45 | .then(response => {
46 | const { data } = response
47 |
48 | if (!data) {
49 | reject('验证失败,请重新登录')
50 | }
51 |
52 | const { username, avatar } = data
53 |
54 | commit('SET_NAME', username)
55 | commit('SET_AVATAR', avatar)
56 | resolve(data)
57 | })
58 | .catch(error => {
59 | reject(error)
60 | })
61 | })
62 | },
63 |
64 | // user logout
65 | logout({ commit, state }) {
66 | return new Promise((resolve, reject) => {
67 | logout(state.token)
68 | .then(() => {
69 | commit('SET_TOKEN', '')
70 | removeToken()
71 | resetRouter()
72 | resolve()
73 | })
74 | .catch(error => {
75 | reject(error)
76 | })
77 | })
78 | },
79 |
80 | // remove token
81 | resetToken({ commit }) {
82 | return new Promise(resolve => {
83 | commit('SET_TOKEN', '')
84 | removeToken()
85 | resolve()
86 | })
87 | }
88 | }
89 |
90 | export default {
91 | namespaced: true,
92 | state,
93 | mutations,
94 | actions
95 | }
96 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/styles/element-ui.scss:
--------------------------------------------------------------------------------
1 | // cover some element-ui styles
2 |
3 | .el-breadcrumb__inner,
4 | .el-breadcrumb__inner a {
5 | font-weight: 400 !important;
6 | }
7 |
8 | .el-upload {
9 | input[type="file"] {
10 | display: none !important;
11 | }
12 | }
13 |
14 | .el-upload__input {
15 | display: none;
16 | }
17 |
18 |
19 | // to fixed https://github.com/ElemeFE/element/issues/2461
20 | .el-dialog {
21 | transform: none;
22 | left: 0;
23 | position: relative;
24 | margin: 0 auto;
25 | }
26 |
27 | // refine element ui upload
28 | .upload-container {
29 | .el-upload {
30 | width: 100%;
31 |
32 | .el-upload-dragger {
33 | width: 100%;
34 | height: 200px;
35 | }
36 | }
37 | }
38 |
39 | // dropdown
40 | .el-dropdown-menu {
41 | a {
42 | display: block
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 | @import './mixin.scss';
3 | @import './transition.scss';
4 | @import './element-ui.scss';
5 | @import './sidebar.scss';
6 |
7 | body {
8 | height: 100%;
9 | -moz-osx-font-smoothing: grayscale;
10 | -webkit-font-smoothing: antialiased;
11 | text-rendering: optimizeLegibility;
12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
13 | }
14 |
15 | label {
16 | font-weight: 700;
17 | }
18 |
19 | html {
20 | height: 100%;
21 | box-sizing: border-box;
22 | }
23 |
24 | #app {
25 | height: 100%;
26 | }
27 |
28 | *,
29 | *:before,
30 | *:after {
31 | box-sizing: inherit;
32 | }
33 |
34 | a:focus,
35 | a:active {
36 | outline: none;
37 | }
38 |
39 | a,
40 | a:focus,
41 | a:hover {
42 | cursor: pointer;
43 | color: inherit;
44 | text-decoration: none;
45 | }
46 |
47 | div:focus {
48 | outline: none;
49 | }
50 |
51 | .clearfix {
52 | &:after {
53 | visibility: hidden;
54 | display: block;
55 | font-size: 0;
56 | content: " ";
57 | clear: both;
58 | height: 0;
59 | }
60 | }
61 |
62 | // main-container global css
63 | .app-container {
64 | padding: 20px;
65 | }
66 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/styles/mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix {
2 | &:after {
3 | content: "";
4 | display: table;
5 | clear: both;
6 | }
7 | }
8 |
9 | @mixin scrollBar {
10 | &::-webkit-scrollbar-track-piece {
11 | background: #d3dce6;
12 | }
13 |
14 | &::-webkit-scrollbar {
15 | width: 6px;
16 | }
17 |
18 | &::-webkit-scrollbar-thumb {
19 | background: #99a9bf;
20 | border-radius: 20px;
21 | }
22 | }
23 |
24 | @mixin relative {
25 | position: relative;
26 | width: 100%;
27 | height: 100%;
28 | }
29 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/styles/sidebar.scss:
--------------------------------------------------------------------------------
1 | #app {
2 |
3 | .main-container {
4 | min-height: 100%;
5 | transition: margin-left .28s;
6 | margin-left: $sideBarWidth;
7 | position: relative;
8 | }
9 |
10 | .sidebar-container {
11 | transition: width 0.28s;
12 | width: $sideBarWidth !important;
13 | background-color: $menuBg;
14 | height: 100%;
15 | position: fixed;
16 | font-size: 0px;
17 | top: 0;
18 | bottom: 0;
19 | left: 0;
20 | z-index: 1001;
21 | overflow: hidden;
22 |
23 | // reset element-ui css
24 | .horizontal-collapse-transition {
25 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
26 | }
27 |
28 | .scrollbar-wrapper {
29 | overflow-x: hidden !important;
30 | }
31 |
32 | .el-scrollbar__bar.is-vertical {
33 | right: 0px;
34 | }
35 |
36 | .el-scrollbar {
37 | height: 100%;
38 | }
39 |
40 | &.has-logo {
41 | .el-scrollbar {
42 | height: calc(100% - 50px);
43 | }
44 | }
45 |
46 | .is-horizontal {
47 | display: none;
48 | }
49 |
50 | a {
51 | display: inline-block;
52 | width: 100%;
53 | overflow: hidden;
54 | }
55 |
56 | .svg-icon {
57 | margin-right: 16px;
58 | }
59 |
60 | .el-menu {
61 | border: none;
62 | height: 100%;
63 | width: 100% !important;
64 | }
65 |
66 | // menu hover
67 | .submenu-title-noDropdown,
68 | .el-submenu__title {
69 | &:hover {
70 | background-color: $menuHover !important;
71 | }
72 | }
73 |
74 | .is-active>.el-submenu__title {
75 | color: $subMenuActiveText !important;
76 | }
77 |
78 | & .nest-menu .el-submenu>.el-submenu__title,
79 | & .el-submenu .el-menu-item {
80 | min-width: $sideBarWidth !important;
81 | background-color: $subMenuBg !important;
82 |
83 | &:hover {
84 | background-color: $subMenuHover !important;
85 | }
86 | }
87 | }
88 |
89 | .hideSidebar {
90 | .sidebar-container {
91 | width: 54px !important;
92 | }
93 |
94 | .main-container {
95 | margin-left: 54px;
96 | }
97 |
98 | .submenu-title-noDropdown {
99 | padding: 0 !important;
100 | position: relative;
101 |
102 | .el-tooltip {
103 | padding: 0 !important;
104 |
105 | .svg-icon {
106 | margin-left: 20px;
107 | }
108 | }
109 | }
110 |
111 | .el-submenu {
112 | overflow: hidden;
113 |
114 | &>.el-submenu__title {
115 | padding: 0 !important;
116 |
117 | .svg-icon {
118 | margin-left: 20px;
119 | }
120 |
121 | .el-submenu__icon-arrow {
122 | display: none;
123 | }
124 | }
125 | }
126 |
127 | .el-menu--collapse {
128 | .el-submenu {
129 | &>.el-submenu__title {
130 | &>span {
131 | height: 0;
132 | width: 0;
133 | overflow: hidden;
134 | visibility: hidden;
135 | display: inline-block;
136 | }
137 | }
138 | }
139 | }
140 | }
141 |
142 | .el-menu--collapse .el-menu .el-submenu {
143 | min-width: $sideBarWidth !important;
144 | }
145 |
146 | // mobile responsive
147 | .mobile {
148 | .main-container {
149 | margin-left: 0px;
150 | }
151 |
152 | .sidebar-container {
153 | transition: transform .28s;
154 | width: $sideBarWidth !important;
155 | }
156 |
157 | &.hideSidebar {
158 | .sidebar-container {
159 | pointer-events: none;
160 | transition-duration: 0.3s;
161 | transform: translate3d(-$sideBarWidth, 0, 0);
162 | }
163 | }
164 | }
165 |
166 | .withoutAnimation {
167 |
168 | .main-container,
169 | .sidebar-container {
170 | transition: none;
171 | }
172 | }
173 | }
174 |
175 | // when menu collapsed
176 | .el-menu--vertical {
177 | &>.el-menu {
178 | .svg-icon {
179 | margin-right: 16px;
180 | }
181 | }
182 |
183 | .nest-menu .el-submenu>.el-submenu__title,
184 | .el-menu-item {
185 | &:hover {
186 | // you can use $subMenuHover
187 | background-color: $menuHover !important;
188 | }
189 | }
190 |
191 | // the scroll bar appears when the subMenu is too long
192 | >.el-menu--popup {
193 | max-height: 100vh;
194 | overflow-y: auto;
195 |
196 | &::-webkit-scrollbar-track-piece {
197 | background: #d3dce6;
198 | }
199 |
200 | &::-webkit-scrollbar {
201 | width: 6px;
202 | }
203 |
204 | &::-webkit-scrollbar-thumb {
205 | background: #99a9bf;
206 | border-radius: 20px;
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | // global transition css
2 |
3 | /* fade */
4 | .fade-enter-active,
5 | .fade-leave-active {
6 | transition: opacity 0.28s;
7 | }
8 |
9 | .fade-enter,
10 | .fade-leave-active {
11 | opacity: 0;
12 | }
13 |
14 | /* fade-transform */
15 | .fade-transform-leave-active,
16 | .fade-transform-enter-active {
17 | transition: all .5s;
18 | }
19 |
20 | .fade-transform-enter {
21 | opacity: 0;
22 | transform: translateX(-30px);
23 | }
24 |
25 | .fade-transform-leave-to {
26 | opacity: 0;
27 | transform: translateX(30px);
28 | }
29 |
30 | /* breadcrumb transition */
31 | .breadcrumb-enter-active,
32 | .breadcrumb-leave-active {
33 | transition: all .5s;
34 | }
35 |
36 | .breadcrumb-enter,
37 | .breadcrumb-leave-active {
38 | opacity: 0;
39 | transform: translateX(20px);
40 | }
41 |
42 | .breadcrumb-move {
43 | transition: all .5s;
44 | }
45 |
46 | .breadcrumb-leave-active {
47 | position: absolute;
48 | }
49 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | // sidebar
2 | $menuText:#bfcbd9;
3 | $menuActiveText:#409EFF;
4 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
5 |
6 | $menuBg:#304156;
7 | $menuHover:#263445;
8 |
9 | $subMenuBg:#1f2d3d;
10 | $subMenuHover:#001528;
11 |
12 | $sideBarWidth: 210px;
13 |
14 | // the :export directive is the magic sauce for webpack
15 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
16 | :export {
17 | menuText: $menuText;
18 | menuActiveText: $menuActiveText;
19 | subMenuActiveText: $subMenuActiveText;
20 | menuBg: $menuBg;
21 | menuHover: $menuHover;
22 | subMenuBg: $subMenuBg;
23 | subMenuHover: $subMenuHover;
24 | sideBarWidth: $sideBarWidth;
25 | }
26 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/utils/auth.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const TokenKey = 'cyc_blog_admin_token'
4 |
5 | export function getToken() {
6 | return Cookies.get(TokenKey)
7 | }
8 |
9 | export function setToken(token) {
10 | return Cookies.set(TokenKey, token)
11 | }
12 |
13 | export function removeToken() {
14 | return Cookies.remove(TokenKey)
15 | }
16 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/utils/get-page-title.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from '@/settings'
2 |
3 | const title = defaultSettings.title || 'CYC Blog Admin'
4 |
5 | export default function getPageTitle(pageTitle) {
6 | if (pageTitle) {
7 | return `${pageTitle} - ${title}`
8 | }
9 | return `${title}`
10 | }
11 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by PanJiaChen on 16/11/18.
3 | */
4 |
5 | /**
6 | * Parse the time to string
7 | * @param {(Object|string|number)} time
8 | * @param {string} cFormat
9 | * @returns {string}
10 | */
11 | export function parseTime(time, cFormat) {
12 | if (arguments.length === 0) {
13 | return null
14 | }
15 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
16 | let date
17 | if (typeof time === 'object') {
18 | date = time
19 | } else {
20 | if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
21 | time = parseInt(time)
22 | }
23 | if ((typeof time === 'number') && (time.toString().length === 10)) {
24 | time = time * 1000
25 | }
26 | date = new Date(time)
27 | }
28 | const formatObj = {
29 | y: date.getFullYear(),
30 | m: date.getMonth() + 1,
31 | d: date.getDate(),
32 | h: date.getHours(),
33 | i: date.getMinutes(),
34 | s: date.getSeconds(),
35 | a: date.getDay()
36 | }
37 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
38 | let value = formatObj[key]
39 | // Note: getDay() returns 0 on Sunday
40 | if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
41 | if (result.length > 0 && value < 10) {
42 | value = '0' + value
43 | }
44 | return value || 0
45 | })
46 | return time_str
47 | }
48 |
49 | /**
50 | * @param {number} time
51 | * @param {string} option
52 | * @returns {string}
53 | */
54 | export function formatTime(time, option) {
55 | if (('' + time).length === 10) {
56 | time = parseInt(time) * 1000
57 | } else {
58 | time = +time
59 | }
60 | const d = new Date(time)
61 | const now = Date.now()
62 |
63 | const diff = (now - d) / 1000
64 |
65 | if (diff < 30) {
66 | return '刚刚'
67 | } else if (diff < 3600) {
68 | // less 1 hour
69 | return Math.ceil(diff / 60) + '分钟前'
70 | } else if (diff < 3600 * 24) {
71 | return Math.ceil(diff / 3600) + '小时前'
72 | } else if (diff < 3600 * 24 * 2) {
73 | return '1天前'
74 | }
75 | if (option) {
76 | return parseTime(time, option)
77 | } else {
78 | return (
79 | d.getMonth() +
80 | 1 +
81 | '月' +
82 | d.getDate() +
83 | '日' +
84 | d.getHours() +
85 | '时' +
86 | d.getMinutes() +
87 | '分'
88 | )
89 | }
90 | }
91 |
92 | /**
93 | * @param {string} url
94 | * @returns {Object}
95 | */
96 | export function param2Obj(url) {
97 | const search = url.split('?')[1]
98 | if (!search) {
99 | return {}
100 | }
101 | return JSON.parse(
102 | '{"' +
103 | decodeURIComponent(search)
104 | .replace(/"/g, '\\"')
105 | .replace(/&/g, '","')
106 | .replace(/=/g, '":"')
107 | .replace(/\+/g, ' ') +
108 | '"}'
109 | )
110 | }
111 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { MessageBox, Message } from 'element-ui'
3 | import store from '@/store'
4 | import { getToken } from '@/utils/auth'
5 |
6 | // create an axios instance
7 | const service = axios.create({
8 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
9 | // withCredentials: true, // send cookies when cross-domain requests
10 | timeout: 5000 // request timeout
11 | })
12 |
13 | // request interceptor
14 | service.interceptors.request.use(
15 | config => {
16 | // do something before request is sent
17 |
18 | if (store.getters.token) {
19 | // let each request carry token
20 | // ['X-Token'] is a custom headers key
21 | // please modify it according to the actual situation
22 | config.headers['X-Token'] = getToken()
23 | }
24 | return config
25 | },
26 | error => {
27 | // do something with request error
28 | console.log(error) // for debug
29 | return Promise.reject(error)
30 | }
31 | )
32 |
33 | // response interceptor
34 | service.interceptors.response.use(
35 | /**
36 | * If you want to get http information such as headers or status
37 | * Please return response => response
38 | */
39 |
40 | /**
41 | * Determine the request status by custom code
42 | * Here is just an example
43 | * You can also judge the status by HTTP Status Code
44 | */
45 | response => {
46 | const res = response.data
47 |
48 | // if the custom code is not 20000, it is judged as an error.
49 | if (res.code !== 200) {
50 | Message({
51 | message: res.message || 'Error',
52 | type: 'error',
53 | duration: 5 * 1000
54 | })
55 |
56 | // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
57 | if (
58 | res.code === 50008 ||
59 | res.code === 50012 ||
60 | res.code === 50014
61 | ) {
62 | // to re-login
63 | MessageBox.confirm(
64 | '您已经注销,您可以取消以停留在此页面,或再次登录',
65 | '确认注销',
66 | {
67 | confirmButtonText: '重新登录',
68 | cancelButtonText: '确认',
69 | type: 'warning'
70 | }
71 | ).then(() => {
72 | store.dispatch('user/resetToken').then(() => {
73 | location.reload()
74 | })
75 | })
76 | }
77 | return Promise.reject(new Error(res.message || 'Error'))
78 | } else {
79 | return res
80 | }
81 | },
82 | error => {
83 | console.log('err' + error) // for debug
84 | Message({
85 | message: error.message,
86 | type: 'error',
87 | duration: 5 * 1000
88 | })
89 | return Promise.reject(error)
90 | }
91 | )
92 |
93 | export default service
94 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/utils/scroll-to.js:
--------------------------------------------------------------------------------
1 | Math.easeInOutQuad = function(t, b, c, d) {
2 | t /= d / 2
3 | if (t < 1) {
4 | return c / 2 * t * t + b
5 | }
6 | t--
7 | return -c / 2 * (t * (t - 2) - 1) + b
8 | }
9 |
10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
11 | var requestAnimFrame = (function() {
12 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
13 | })()
14 |
15 | /**
16 | * Because it's so fucking difficult to detect the scrolling element, just move them all
17 | * @param {number} amount
18 | */
19 | function move(amount) {
20 | document.documentElement.scrollTop = amount
21 | document.body.parentNode.scrollTop = amount
22 | document.body.scrollTop = amount
23 | }
24 |
25 | function position() {
26 | return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
27 | }
28 |
29 | /**
30 | * @param {number} to
31 | * @param {number} duration
32 | * @param {Function} callback
33 | */
34 | export function scrollTo(to, duration, callback) {
35 | const start = position()
36 | const change = to - start
37 | const increment = 20
38 | let currentTime = 0
39 | duration = (typeof (duration) === 'undefined') ? 500 : duration
40 | var animateScroll = function() {
41 | // increment the time
42 | currentTime += increment
43 | // find the value with the quadratic in-out easing function
44 | var val = Math.easeInOutQuad(currentTime, start, change, duration)
45 | // move the document.body
46 | move(val)
47 | // do the animation unless its over
48 | if (currentTime < duration) {
49 | requestAnimFrame(animateScroll)
50 | } else {
51 | if (callback && typeof (callback) === 'function') {
52 | // the animation is done so lets callback
53 | callback()
54 | }
55 | }
56 | }
57 | animateScroll()
58 | }
59 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {string} path
3 | * @returns {Boolean}
4 | */
5 | export function isExternal(path) {
6 | return /^(https?:|mailto:|tel:)/.test(path)
7 | }
8 |
9 | /**
10 | * @param {string} str
11 | * @returns {Boolean}
12 | */
13 | export function validUsername(str) {
14 | const valid_map = ['admin', 'editor']
15 | return valid_map.indexOf(str.trim()) >= 0
16 | }
17 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/views/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
哎哟!
12 |
{{ message }}
13 |
请检查您输入的网址是否正确,或点击下面的按钮返回首页。
14 |
返回首页
15 |
16 |
17 |
18 |
19 |
20 |
31 |
32 |
226 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
55 |
56 |
86 |
--------------------------------------------------------------------------------
/vue-blog-admin/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
![logo]()
13 |
CYC Blog Admin
14 |
15 |
16 |
17 |
18 |
19 |
20 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
46 |
47 |
50 |
51 |
52 |
53 | 登 录
61 |
62 |
Copyright @ 2019 By CYC
63 |
64 |
65 |
66 |
143 |
144 |
190 |
191 |
262 |
--------------------------------------------------------------------------------
/vue-blog-admin/vue.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const defaultSettings = require('./src/settings.js')
4 |
5 | function resolve(dir) {
6 | return path.join(__dirname, dir)
7 | }
8 |
9 | const name = defaultSettings.title || 'CYC Blog Admin' // page title
10 | module.exports = {
11 | publicPath: '/',
12 | outputDir: 'dist',
13 | assetsDir: 'static',
14 | productionSourceMap: false,
15 | configureWebpack: {
16 | // provide the app's title in webpack's name field, so that
17 | // it can be accessed in index.html to inject the correct title.
18 | name: name,
19 | resolve: {
20 | alias: {
21 | '@': resolve('src')
22 | }
23 | },
24 | performance: {
25 | hints: 'warning',
26 | //入口起点的最大体积 整数类型(以字节为单位)
27 | maxEntrypointSize: 50000000,
28 | //生成文件的最大体积 整数类型(以字节为单位 300k)
29 | maxAssetSize: 30000000,
30 | //只给出 js 文件的性能提示
31 | assetFilter: function(assetFilename) {
32 | return assetFilename.endsWith('.js')
33 | }
34 | }
35 | },
36 | chainWebpack(config) {
37 | config.plugins.delete('preload') // TODO: need test
38 | config.plugins.delete('prefetch') // TODO: need test
39 |
40 | // set svg-sprite-loader
41 | config.module
42 | .rule('svg')
43 | .exclude.add(resolve('src/icons'))
44 | .end()
45 | config.module
46 | .rule('icons')
47 | .test(/\.svg$/)
48 | .include.add(resolve('src/icons'))
49 | .end()
50 | .use('svg-sprite-loader')
51 | .loader('svg-sprite-loader')
52 | .options({
53 | symbolId: 'icon-[name]'
54 | })
55 | .end()
56 |
57 | // set preserveWhitespace
58 | config.module
59 | .rule('vue')
60 | .use('vue-loader')
61 | .loader('vue-loader')
62 | .tap(options => {
63 | options.compilerOptions.preserveWhitespace = true
64 | return options
65 | })
66 | .end()
67 |
68 | config
69 | // https://webpack.js.org/configuration/devtool/#development
70 | .when(process.env.NODE_ENV === 'development', config =>
71 | config.devtool('cheap-source-map')
72 | )
73 |
74 | config.when(process.env.NODE_ENV !== 'development', config => {
75 | config
76 | .plugin('ScriptExtHtmlWebpackPlugin')
77 | .after('html')
78 | .use('script-ext-html-webpack-plugin', [
79 | {
80 | // `runtime` must same as runtimeChunk name. default is `runtime`
81 | inline: /runtime\..*\.js$/
82 | }
83 | ])
84 | .end()
85 | config.optimization.splitChunks({
86 | chunks: 'all',
87 | cacheGroups: {
88 | libs: {
89 | name: 'chunk-libs',
90 | test: /[\\/]node_modules[\\/]/,
91 | priority: 10,
92 | chunks: 'initial' // only package third parties that are initially dependent
93 | },
94 | elementUI: {
95 | name: 'chunk-elementUI', // split elementUI into a single package
96 | priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
97 | test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
98 | },
99 | commons: {
100 | name: 'chunk-commons',
101 | test: resolve('src/components'), // can customize your rules
102 | minChunks: 3, // minimum common number
103 | priority: 5,
104 | reuseExistingChunk: true
105 | }
106 | }
107 | })
108 | config.optimization.runtimeChunk('single')
109 | })
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/vue-blog-h5/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 |
--------------------------------------------------------------------------------
/vue-blog-h5/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/vue-blog-h5/.env.development:
--------------------------------------------------------------------------------
1 | NODE_ENV='development'
2 | # must start with VUE_APP_
3 | VUE_APP_ENV = 'development'
4 |
5 |
--------------------------------------------------------------------------------
/vue-blog-h5/.env.production:
--------------------------------------------------------------------------------
1 | NODE_ENV='production'
2 | # must start with VUE_APP_
3 | VUE_APP_ENV = 'production'
4 |
--------------------------------------------------------------------------------
/vue-blog-h5/.env.staging:
--------------------------------------------------------------------------------
1 | NODE_ENV='production'
2 | # must start with VUE_APP_
3 | VUE_APP_ENV = 'staging'
4 |
5 |
--------------------------------------------------------------------------------
/vue-blog-h5/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | src/assets
3 | public
4 | dist
5 |
--------------------------------------------------------------------------------
/vue-blog-h5/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: ['plugin:vue/essential', 'eslint:recommended', '@vue/prettier'],
7 | parserOptions: {
8 | parser: 'babel-eslint'
9 | },
10 | rules: {
11 | "vue/max-attributes-per-line": [2, {
12 | "singleline": 10,
13 | "multiline": {
14 | "max": 1,
15 | "allowFirstLine": false
16 | }
17 | }],
18 | "vue/singleline-html-element-content-newline": "off",
19 | "vue/multiline-html-element-content-newline":"off",
20 | "vue/name-property-casing": ["error", "PascalCase"],
21 | "vue/no-v-html": "off",
22 | 'accessor-pairs': 2,
23 | 'arrow-spacing': [2, {
24 | 'before': true,
25 | 'after': true
26 | }],
27 | 'block-spacing': [2, 'always'],
28 | 'brace-style': [2, '1tbs', {
29 | 'allowSingleLine': true
30 | }],
31 | 'camelcase': [0, {
32 | 'properties': 'always'
33 | }],
34 | 'comma-dangle': [2, 'never'],
35 | 'comma-spacing': [2, {
36 | 'before': false,
37 | 'after': true
38 | }],
39 | 'comma-style': [2, 'last'],
40 | 'constructor-super': 2,
41 | 'curly': [2, 'multi-line'],
42 | 'dot-location': [2, 'property'],
43 | 'eol-last': 2,
44 | 'eqeqeq': ["error", "always", {"null": "ignore"}],
45 | 'generator-star-spacing': [2, {
46 | 'before': true,
47 | 'after': true
48 | }],
49 | 'handle-callback-err': [2, '^(err|error)$'],
50 | 'indent': [2, 2, {
51 | 'SwitchCase': 1
52 | }],
53 | 'jsx-quotes': [2, 'prefer-single'],
54 | 'key-spacing': [2, {
55 | 'beforeColon': false,
56 | 'afterColon': true
57 | }],
58 | 'keyword-spacing': [2, {
59 | 'before': true,
60 | 'after': true
61 | }],
62 | 'new-cap': [2, {
63 | 'newIsCap': true,
64 | 'capIsNew': false
65 | }],
66 | 'new-parens': 2,
67 | 'no-array-constructor': 2,
68 | 'no-caller': 2,
69 | 'no-console': 'off',
70 | 'no-class-assign': 2,
71 | 'no-cond-assign': 2,
72 | 'no-const-assign': 2,
73 | 'no-control-regex': 0,
74 | 'no-delete-var': 2,
75 | 'no-dupe-args': 2,
76 | 'no-dupe-class-members': 2,
77 | 'no-dupe-keys': 2,
78 | 'no-duplicate-case': 2,
79 | 'no-empty-character-class': 2,
80 | 'no-empty-pattern': 2,
81 | 'no-eval': 2,
82 | 'no-ex-assign': 2,
83 | 'no-extend-native': 2,
84 | 'no-extra-bind': 2,
85 | 'no-extra-boolean-cast': 2,
86 | 'no-extra-parens': [2, 'functions'],
87 | 'no-fallthrough': 2,
88 | 'no-floating-decimal': 2,
89 | 'no-func-assign': 2,
90 | 'no-implied-eval': 2,
91 | 'no-inner-declarations': [2, 'functions'],
92 | 'no-invalid-regexp': 2,
93 | 'no-irregular-whitespace': 2,
94 | 'no-iterator': 2,
95 | 'no-label-var': 2,
96 | 'no-labels': [2, {
97 | 'allowLoop': false,
98 | 'allowSwitch': false
99 | }],
100 | 'no-lone-blocks': 2,
101 | 'no-mixed-spaces-and-tabs': 2,
102 | 'no-multi-spaces': 2,
103 | 'no-multi-str': 2,
104 | 'no-multiple-empty-lines': [2, {
105 | 'max': 1
106 | }],
107 | 'no-native-reassign': 2,
108 | 'no-negated-in-lhs': 2,
109 | 'no-new-object': 2,
110 | 'no-new-require': 2,
111 | 'no-new-symbol': 2,
112 | 'no-new-wrappers': 2,
113 | 'no-obj-calls': 2,
114 | 'no-octal': 2,
115 | 'no-octal-escape': 2,
116 | 'no-path-concat': 2,
117 | 'no-proto': 2,
118 | 'no-redeclare': 2,
119 | 'no-regex-spaces': 2,
120 | 'no-return-assign': [2, 'except-parens'],
121 | 'no-self-assign': 2,
122 | 'no-self-compare': 2,
123 | 'no-sequences': 2,
124 | 'no-shadow-restricted-names': 2,
125 | 'no-spaced-func': 2,
126 | 'no-sparse-arrays': 2,
127 | 'no-this-before-super': 2,
128 | 'no-throw-literal': 2,
129 | 'no-trailing-spaces': 2,
130 | 'no-undef': 2,
131 | 'no-undef-init': 2,
132 | 'no-unexpected-multiline': 2,
133 | 'no-unmodified-loop-condition': 2,
134 | 'no-unneeded-ternary': [2, {
135 | 'defaultAssignment': false
136 | }],
137 | 'no-unreachable': 2,
138 | 'no-unsafe-finally': 2,
139 | 'no-unused-vars': [2, {
140 | 'vars': 'all',
141 | 'args': 'none'
142 | }],
143 | 'no-useless-call': 2,
144 | 'no-useless-computed-key': 2,
145 | 'no-useless-constructor': 2,
146 | 'no-useless-escape': 0,
147 | 'no-whitespace-before-property': 2,
148 | 'no-with': 2,
149 | 'one-var': [2, {
150 | 'initialized': 'never'
151 | }],
152 | 'operator-linebreak': [2, 'after', {
153 | 'overrides': {
154 | '?': 'before',
155 | ':': 'before'
156 | }
157 | }],
158 | 'padded-blocks': [2, 'never'],
159 | 'quotes': [2, 'single', {
160 | 'avoidEscape': true,
161 | 'allowTemplateLiterals': true
162 | }],
163 | 'semi': [2, 'never'],
164 | 'semi-spacing': [2, {
165 | 'before': false,
166 | 'after': true
167 | }],
168 | 'space-before-blocks': [2, 'always'],
169 | 'space-before-function-paren': [2, 'never'],
170 | 'space-in-parens': [2, 'never'],
171 | 'space-infix-ops': 2,
172 | 'space-unary-ops': [2, {
173 | 'words': true,
174 | 'nonwords': false
175 | }],
176 | 'spaced-comment': [2, 'always', {
177 | 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
178 | }],
179 | 'template-curly-spacing': [2, 'never'],
180 | 'use-isnan': 2,
181 | 'valid-typeof': 2,
182 | 'wrap-iife': [2, 'any'],
183 | 'yield-star-spacing': [2, 'both'],
184 | 'yoda': [2, 'never'],
185 | 'prefer-const': 2,
186 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
187 | 'object-curly-spacing': [2, 'always', {
188 | objectsInObjects: false
189 | }],
190 | 'array-bracket-spacing': [2, 'never']
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/vue-blog-h5/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 | /docs
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 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
23 | package-lock.json
24 | yarn.lock
--------------------------------------------------------------------------------
/vue-blog-h5/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 | module.exports = {
3 | plugins: {
4 | autoprefixer: {
5 | overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8']
6 | },
7 | 'postcss-pxtorem': {
8 | rootValue: 37.5,
9 | propList: ['*'],
10 | //selectorBlackList: ['van-']
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/vue-blog-h5/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "tabWidth": 2,
4 | "singleQuote": true,
5 | "trailingComma": "none",
6 | "semi": false,
7 | "wrap_line_length": 120,
8 | "wrap_attributes": "auto",
9 | "proseWrap": "always",
10 | "arrowParens": "avoid",
11 | "bracketSpacing": true,
12 | "jsxBracketSameLine": true,
13 | "useTabs": false,
14 | "eslintIntegration":true,
15 | "overrides": [
16 | {
17 | "files": ".prettierrc",
18 | "options": {
19 | "parser": "json"
20 | }
21 | }
22 | ],
23 | "endOfLine": "auto"
24 | }
25 |
--------------------------------------------------------------------------------
/vue-blog-h5/babel.config.js:
--------------------------------------------------------------------------------
1 | // 获取 VUE_APP_ENV 非 NODE_ENV,测试环境依然 console
2 | const IS_PROD = ['production', 'prod'].includes(process.env.VUE_APP_ENV)
3 | const plugins = [
4 | [
5 | 'import',
6 | {
7 | libraryName: 'vant',
8 | libraryDirectory: 'es',
9 | style: true
10 | },
11 | 'vant'
12 | ]
13 | ]
14 | // 去除 console.log
15 | if (IS_PROD) {
16 | plugins.push('transform-remove-console')
17 | }
18 |
19 | module.exports = {
20 | presets: [['@vue/cli-plugin-babel/preset', {useBuiltIns: 'usage', corejs: 3}]],
21 | plugins
22 | }
23 |
--------------------------------------------------------------------------------
/vue-blog-h5/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog-h5",
3 | "version": "2.0.0",
4 | "description": "a vue blog h5",
5 | "author": "Jason Chen",
6 | "private": true,
7 | "scripts": {
8 | "serve": "vue-cli-service serve --open",
9 | "build": "vue-cli-service build",
10 | "stage": "vue-cli-service build --mode staging",
11 | "lint": "vue-cli-service lint"
12 | },
13 | "dependencies": {
14 | "axios": "^0.19.2",
15 | "core-js": "^3.6.4",
16 | "lib-flexible": "^0.3.2",
17 | "lodash": "^4.17.15",
18 | "regenerator-runtime": "^0.13.5",
19 | "vant": "^2.4.7",
20 | "vue": "^2.6.11",
21 | "vue-router": "^3.1.5",
22 | "vuex": "^3.1.2"
23 | },
24 | "devDependencies": {
25 | "@vue/cli-plugin-babel": "~4.3.0",
26 | "@vue/cli-plugin-eslint": "~4.3.0",
27 | "@vue/cli-service": "~4.3.0",
28 | "@vue/eslint-config-prettier": "^6.0.0",
29 | "babel-eslint": "^10.0.3",
30 | "babel-plugin-import": "^1.13.0",
31 | "babel-plugin-transform-remove-console": "^6.9.4",
32 | "eslint": "^6.7.2",
33 | "eslint-plugin-prettier": "^3.1.1",
34 | "eslint-plugin-vue": "^6.2.2",
35 | "node-sass": "^4.13.1",
36 | "postcss-pxtorem": "^4.0.1",
37 | "prettier": "^1.19.1",
38 | "sass-loader": "^8.0.2",
39 | "script-ext-html-webpack-plugin": "^2.1.4",
40 | "vue-template-compiler": "^2.6.11",
41 | "webpack-bundle-analyzer": "^3.7.0"
42 | }
43 | }
--------------------------------------------------------------------------------
/vue-blog-h5/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jcyicai/vue-blog-template/525110674b567994f807ac23ed23556a3f12b043/vue-blog-h5/public/favicon.ico
--------------------------------------------------------------------------------
/vue-blog-h5/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 | <%= webpackConfig.name %>
16 |
17 |
171 |
172 |
173 |
174 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
200 |
201 |
202 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
21 |
22 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/api/common.js:
--------------------------------------------------------------------------------
1 | import api from './index'
2 | // axios
3 | import request from '@/utils/request'
4 |
5 | //获取文章
6 | export function getArticleList(data) {
7 | return request({
8 | url: api.Article,
9 | method: 'get',
10 | params: data,
11 | hideloading: true
12 | })
13 | }
14 |
15 | //获取照片
16 | export function getAlbumList(data) {
17 | return request({
18 | url: api.Album,
19 | method: 'get',
20 | params: data,
21 | hideloading: true
22 | })
23 | }
24 |
25 | //获取照片
26 | export function getUpdateLogList(data) {
27 | return request({
28 | url: api.UpdateLog,
29 | method: 'get',
30 | params: data,
31 | hideloading: true
32 | })
33 | }
34 |
35 | //发送留言
36 | export function postSendWhisper(data) {
37 | return request({
38 | url: api.SendWhisper,
39 | method: 'post',
40 | data,
41 | hideloading: true
42 | })
43 | }
44 |
45 | //获取留言
46 | export function getWhisperList(data) {
47 | return request({
48 | url: api.Whisper,
49 | method: 'get',
50 | params: data,
51 | hideloading: true
52 | })
53 | }
--------------------------------------------------------------------------------
/vue-blog-h5/src/api/index.js:
--------------------------------------------------------------------------------
1 | const api = {
2 | Login: '/user/login',
3 | UserInfo: '/user/userinfo',
4 | UserName: '/user/name',
5 |
6 | Article: '/blogArticle/list', //文章
7 | Album: '/blogAlbum/list', //照片
8 | UpdateLog: '/blogUpdateLog/list', //更新日志
9 | Whisper: '/blogWhisper/list', //留言
10 | SendWhisper: '/blogWhisper/insert', //发送留言
11 | }
12 |
13 | export default api
14 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/api/user.js:
--------------------------------------------------------------------------------
1 | import api from './index'
2 | // axios
3 | import request from '@/utils/request'
4 |
5 | // 登录
6 | export function login(data) {
7 | return request({
8 | url: api.Login,
9 | method: 'post',
10 | data
11 | })
12 | }
13 |
14 | // 用户信息 post 方法
15 | export function getUserInfo(data) {
16 | return request({
17 | url: api.UserInfo,
18 | method: 'post',
19 | data,
20 | hideloading: true
21 | })
22 | }
23 |
24 | // 用户名称 get 方法
25 | export function getUserName(params) {
26 | return request({
27 | url: api.UserName,
28 | method: 'get',
29 | params,
30 | hideloading: true
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/assets/css/index.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 | @import './mixin.scss';
3 |
4 | html,
5 | body .app {
6 | color: #333333;
7 | font-family: Arial, Helvetica, 'STHeiti STXihei', 'Microsoft YaHei', Tohoma, sans-serif;
8 | background-color: $background-color;
9 | }
10 |
11 | .app-container {
12 | padding-bottom: 50px;
13 | }
14 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/assets/css/mixin.scss:
--------------------------------------------------------------------------------
1 | // mixin
2 | // 清除浮动
3 | @mixin clearfix {
4 | &:after {
5 | content: "";
6 | display: table;
7 | clear: both;
8 | }
9 | }
10 |
11 | // 多行隐藏
12 | @mixin textoverflow($clamp:1) {
13 | display: block;
14 | overflow: hidden;
15 | text-overflow: ellipsis;
16 | -o-text-overflow: ellipsis;
17 | display: -webkit-box;
18 | -webkit-line-clamp: $clamp;
19 | /*! autoprefixer: ignore next */
20 | -webkit-box-orient: vertical;
21 | }
22 |
23 | //flex box
24 | @mixin flexbox($jc:space-between, $ai:center, $fd:row, $fw:nowrap) {
25 | display: flex;
26 | display: -webkit-flex;
27 | flex: 1;
28 | justify-content: $jc;
29 | -webkit-justify-content: $jc;
30 | align-items: $ai;
31 | -webkit-align-items: $ai;
32 | flex-direction: $fd;
33 | -webkit-flex-direction: $fd;
34 | flex-wrap: $fw;
35 | -webkit-flex-wrap: $fw;
36 | }
37 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/assets/css/variables.scss:
--------------------------------------------------------------------------------
1 |
2 | // variables
3 | $background-color: #f8f8f8;
4 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jcyicai/vue-blog-template/525110674b567994f807ac23ed23556a3f12b043/vue-blog-h5/src/assets/logo.png
--------------------------------------------------------------------------------
/vue-blog-h5/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
24 |
25 |
33 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/components/TabBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item.title }}
6 |
7 |
8 |
9 |
10 |
37 |
38 |
39 |
58 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/config/env.development.js:
--------------------------------------------------------------------------------
1 | // 本地环境配置
2 | module.exports = {
3 | title: 'CYC Blog',
4 | baseUrl: 'http://127.0.0.1:8090', // 项目地址
5 | baseApi: 'http://127.0.0.1:8090/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
6 | APPID: '',
7 | APPSECRET: '',
8 | $cdn: ''
9 | }
10 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/config/env.production.js:
--------------------------------------------------------------------------------
1 | // 正式
2 | module.exports = {
3 | title: 'CYC Blog',
4 | baseUrl: 'http://127.0.0.1:8090', // 正式项目地址
5 | baseApi: 'http://127.0.0.1:8090/api', // 正式api请求地址
6 | APPID: '',
7 | APPSECRET: '',
8 | $cdn: ''
9 | }
10 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/config/env.staging.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'CYC Blog',
3 | baseUrl: 'https://test.xxx.com', // 测试项目地址
4 | baseApi: 'https://test.xxx.com/api', // 测试api请求地址
5 | APPID: '',
6 | APPSECRET: '',
7 | $cdn: ''
8 | }
9 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/config/index.js:
--------------------------------------------------------------------------------
1 | // 根据环境引入不同配置 process.env.NODE_ENV
2 | const config = require('./env.' + process.env.VUE_APP_ENV)
3 | module.exports = config
4 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/filters/filter.js:
--------------------------------------------------------------------------------
1 | /**
2 | *格式化时间
3 | *yyyy-MM-dd hh:mm:ss
4 | */
5 | export function formatDate(time, fmt) {
6 | if (time === undefined || '') {
7 | return
8 | }
9 | const date = new Date(time)
10 | if (/(y+)/.test(fmt)) {
11 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
12 | }
13 | const o = {
14 | 'M+': date.getMonth() + 1,
15 | 'd+': date.getDate(),
16 | 'h+': date.getHours(),
17 | 'm+': date.getMinutes(),
18 | 's+': date.getSeconds()
19 | }
20 | for (const k in o) {
21 | if (new RegExp(`(${k})`).test(fmt)) {
22 | const str = o[k] + ''
23 | fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str))
24 | }
25 | }
26 | return fmt
27 | }
28 |
29 | function padLeftZero(str) {
30 | return ('00' + str).substr(str.length)
31 | }
32 | /*
33 | * 隐藏用户手机号中间四位
34 | */
35 | export function hidePhone(phone) {
36 | return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
37 | }
38 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/filters/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import * as filter from './filter'
3 |
4 | Object.keys(filter).forEach(k => Vue.filter(k, filter[k]))
5 |
6 | Vue.prototype.$formatDate = Vue.filter('formatDate')
7 | Vue.prototype.$hidePhone = Vue.filter('hidePhone')
8 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/main.js:
--------------------------------------------------------------------------------
1 | // 兼容 IE
2 | // https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md#babelpolyfill
3 | import 'core-js/stable'
4 | import 'regenerator-runtime/runtime'
5 |
6 | import Vue from 'vue'
7 | import App from './App.vue'
8 | import router from './router'
9 | import store from './store'
10 |
11 | // 设置 js中可以访问 $cdn
12 | import { $cdn } from '@/config'
13 | Vue.prototype.$cdn = $cdn
14 |
15 | // 全局引入按需引入UI库 vant
16 | import '@/plugins/vant'
17 | // 引入全局样式
18 | import '@/assets/css/index.scss'
19 | // 移动端适配
20 | import 'lib-flexible/flexible.js'
21 |
22 | // filters
23 | import './filters'
24 | Vue.config.productionTip = false
25 |
26 | new Vue({
27 | el: '#app',
28 | router,
29 | store,
30 | render: h => h(App)
31 | })
32 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/plugins/vant.js:
--------------------------------------------------------------------------------
1 | // 按需全局引入 vant组件
2 | import Vue from 'vue'
3 | import {
4 | Button,
5 | List,
6 | Cell,
7 | Tabbar,
8 | TabbarItem,
9 | NavBar,
10 | Swipe,
11 | SwipeItem,
12 | Lazyload,
13 | Col,
14 | Row,
15 | Image as VanImage,
16 | Step,
17 | Steps,
18 | Dialog,
19 | Notify,
20 | Field,
21 | Form,
22 | Skeleton
23 | } from 'vant'
24 |
25 | Vue.use(Button)
26 | Vue.use(Cell)
27 | Vue.use(List)
28 | Vue.use(Tabbar).use(TabbarItem)
29 | Vue.use(NavBar)
30 | Vue.use(Swipe).use(SwipeItem)
31 | Vue.use(Lazyload)
32 | Vue.use(Col)
33 | Vue.use(Row)
34 | Vue.use(VanImage)
35 | Vue.use(Step)
36 | Vue.use(Steps)
37 | Vue.use(Dialog)
38 | Vue.use(Notify)
39 | Vue.use(Field)
40 | Vue.use(Form)
41 | Vue.use(Skeleton)
--------------------------------------------------------------------------------
/vue-blog-h5/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import { constantRouterMap } from './router.config.js'
4 |
5 | // hack router push callback
6 | const originalPush = Router.prototype.push
7 | Router.prototype.push = function push(location, onResolve, onReject) {
8 | if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
9 | return originalPush.call(this, location).catch(err => err)
10 | }
11 |
12 | Vue.use(Router)
13 |
14 | const createRouter = () =>
15 | new Router({
16 | // mode: 'history', // 如果你是 history模式 需要配置vue.config.js publicPath
17 | // base: process.env.BASE_URL,
18 | scrollBehavior: () => ({ y: 0 }),
19 | routes: constantRouterMap
20 | })
21 |
22 | const router = createRouter()
23 |
24 | // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
25 | export function resetRouter() {
26 | const newRouter = createRouter()
27 | router.matcher = newRouter.matcher // reset router
28 | }
29 |
30 | export default router
31 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/router/router.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 基础路由
3 | * @type { *[] }
4 | */
5 | export const constantRouterMap = [
6 | {
7 | path: '/',
8 | component: () => import('@/views/layouts/index'),
9 | redirect: '/home',
10 | meta: {
11 | title: '首页',
12 | keepAlive: false
13 | },
14 | children: [
15 | {
16 | path: '/home',
17 | name: 'Home',
18 | component: () => import('@/views/home/index'),
19 | meta: { title: 'CYC Blog', keepAlive: false }
20 | },
21 | {
22 | path: '/whisper',
23 | name: 'Whisper',
24 | component: () => import('@/views/whisper/index'),
25 | meta: { title: '留言', keepAlive: false }
26 | },
27 | {
28 | path: '/album',
29 | name: 'Album',
30 | component: () => import('@/views/album/index'),
31 | meta: { title: '相册', keepAlive: false }
32 | },
33 | {
34 | path: '/updateLog',
35 | name: 'UpdateLog',
36 | component: () => import('@/views/updateLog/index'),
37 | meta: { title: '更新', keepAlive: false }
38 | },
39 | {
40 | path: '/about',
41 | name: 'About',
42 | component: () => import('@/views/about/index'),
43 | meta: { title: '关于', keepAlive: false }
44 | }
45 | ]
46 | }
47 | ]
48 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | userName: state => state.app.userName
3 | }
4 | export default getters
5 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import getters from './getters'
4 | import app from './modules/app'
5 |
6 | Vue.use(Vuex)
7 |
8 | const store = new Vuex.Store({
9 | modules: {
10 | app
11 | },
12 | getters
13 | })
14 |
15 | export default store
16 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | userName: ''
3 | }
4 | const mutations = {
5 | SET_USER_NAME(state, name) {
6 | state.userName = name
7 | }
8 | }
9 | const actions = {
10 | // 设置name
11 | setUserName({ commit }, name) {
12 | commit('SET_USER_NAME', name)
13 | }
14 | }
15 | export default {
16 | state,
17 | mutations,
18 | actions
19 | }
20 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by PanJiaChen on 16/11/18.
3 | */
4 |
5 | /**
6 | * Parse the time to string
7 | * @param {(Object|string|number)} time
8 | * @param {string} cFormat
9 | * @returns {string}
10 | */
11 | export function parseTime(time, cFormat) {
12 | if (arguments.length === 0) {
13 | return null
14 | }
15 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
16 | let date
17 | if (typeof time === 'object') {
18 | date = time
19 | } else {
20 | if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
21 | time = parseInt(time)
22 | }
23 | if ((typeof time === 'number') && (time.toString().length === 10)) {
24 | time = time * 1000
25 | }
26 | date = new Date(time)
27 | }
28 | const formatObj = {
29 | y: date.getFullYear(),
30 | m: date.getMonth() + 1,
31 | d: date.getDate(),
32 | h: date.getHours(),
33 | i: date.getMinutes(),
34 | s: date.getSeconds(),
35 | a: date.getDay()
36 | }
37 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
38 | let value = formatObj[key]
39 | // Note: getDay() returns 0 on Sunday
40 | if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
41 | if (result.length > 0 && value < 10) {
42 | value = '0' + value
43 | }
44 | return value || 0
45 | })
46 | return time_str
47 | }
48 |
49 | /**
50 | * @param {number} time
51 | * @param {string} option
52 | * @returns {string}
53 | */
54 | export function formatTime(time, option) {
55 | if (('' + time).length === 10) {
56 | time = parseInt(time) * 1000
57 | } else {
58 | time = +time
59 | }
60 | const d = new Date(time)
61 | const now = Date.now()
62 |
63 | const diff = (now - d) / 1000
64 |
65 | if (diff < 30) {
66 | return '刚刚'
67 | } else if (diff < 3600) {
68 | // less 1 hour
69 | return Math.ceil(diff / 60) + '分钟前'
70 | } else if (diff < 3600 * 24) {
71 | return Math.ceil(diff / 3600) + '小时前'
72 | } else if (diff < 3600 * 24 * 2) {
73 | return '1天前'
74 | }
75 | if (option) {
76 | return parseTime(time, option)
77 | } else {
78 | return (
79 | d.getMonth() +
80 | 1 +
81 | '月' +
82 | d.getDate() +
83 | '日' +
84 | d.getHours() +
85 | '时' +
86 | d.getMinutes() +
87 | '分'
88 | )
89 | }
90 | }
91 |
92 | /**
93 | * @param {string} url
94 | * @returns {Object}
95 | */
96 | export function param2Obj(url) {
97 | const search = url.split('?')[1]
98 | if (!search) {
99 | return {}
100 | }
101 | return JSON.parse(
102 | '{"' +
103 | decodeURIComponent(search)
104 | .replace(/"/g, '\\"')
105 | .replace(/&/g, '","')
106 | .replace(/=/g, '":"')
107 | .replace(/\+/g, ' ') +
108 | '"}'
109 | )
110 | }
111 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import store from '@/store'
3 | import { Toast } from 'vant'
4 | // 根据环境不同引入不同api地址
5 | import { baseApi } from '@/config'
6 | // create an axios instance
7 | const service = axios.create({
8 | baseURL: baseApi, // url = base api url + request url
9 | // withCredentials: true, // send cookies when cross-domain requests
10 | timeout: 5000 // request timeout
11 | })
12 |
13 | // request拦截器 request interceptor
14 | service.interceptors.request.use(
15 | config => {
16 | // 不传递默认开启loading
17 | if (!config.hideloading) {
18 | // loading
19 | Toast.loading({
20 | forbidClick: true
21 | })
22 | }
23 | if (store.getters.token) {
24 | config.headers['X-Token'] = ''
25 | }
26 | return config
27 | },
28 | error => {
29 | // do something with request error
30 | console.log(error) // for debug
31 | return Promise.reject(error)
32 | }
33 | )
34 | // respone拦截器
35 | service.interceptors.response.use(
36 | response => {
37 | Toast.clear()
38 | const res = response.data
39 | if (res.code && res.code !== 200) {
40 | // 登录超时,重新登录
41 | if (res.code === 401) {
42 | store.dispatch('FedLogOut').then(() => {
43 | location.reload()
44 | })
45 | }
46 | return Promise.reject(res || 'error')
47 | } else {
48 | return Promise.resolve(res)
49 | }
50 | },
51 | error => {
52 | Toast.clear()
53 | console.log('err' + error) // for debug
54 | return Promise.reject(error)
55 | }
56 | )
57 |
58 | export default service
59 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Sunnie on 19/06/04.
3 | */
4 |
5 | /**
6 | * @param {string} path
7 | * @returns {Boolean}
8 | */
9 | export function isExternal(path) {
10 | return /^(https?:|mailto:|tel:)/.test(path)
11 | }
12 |
13 | /**
14 | * @param {string} str
15 | * @returns {Boolean}
16 | */
17 | export function validUsername(str) {
18 | const valid_map = ['admin', 'editor']
19 | return valid_map.indexOf(str.trim()) >= 0
20 | }
21 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/views/about/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
关于博客
6 |
7 |
8 | - 博客基于Vue通用框架开发
9 | - 前端技术栈:Vue + Vue-Router + Vue-cli3 + Axios + Sass + Element-ui
10 | - 后端技术栈:Node.js + Express + MongoDB + Mongoose
11 | - 服务器:CentOS7.6 + 七牛云
12 |
13 |
14 |
15 |
16 |
17 |
18 |
关于我
19 |
20 |
21 | - 姓名:Jason Chen
22 | - 职位:Web前端开发工程师
23 | - 邮箱:chenyicai622@qq.com
24 | - 爱好:料理、烘焙、写代码
25 | - 座右铭:星光不问赶路人,时光不负有心人
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
45 |
46 |
76 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/views/album/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 | {{item.name}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
81 |
82 |
91 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/views/home/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
73 |
74 |
90 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/views/layouts/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
87 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/views/updateLog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item.content }}
6 | {{ item.info }}
7 |
8 |
9 |
10 |
11 |
12 |
35 |
36 |
42 |
--------------------------------------------------------------------------------
/vue-blog-h5/src/views/whisper/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 | 发送
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
![]()
19 |
{{item.date}}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
117 |
118 |
133 |
--------------------------------------------------------------------------------
/vue-blog-h5/vue.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const defaultSettings = require('./src/config/index.js')
4 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
5 |
6 | const resolve = dir => path.join(__dirname, dir)
7 | // page title
8 | const name = defaultSettings.title || 'CYC Blog'
9 | const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
10 |
11 | module.exports = {
12 | publicPath: './', // 署应用包时的基本 URL。 vue-router hash 模式使用
13 | outputDir: 'dist', // 生产环境构建文件的目录
14 | assetsDir: 'static', // outputDir的静态资源(js、css、img、fonts)目录
15 | lintOnSave: !IS_PROD,
16 | productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
17 | devServer: {
18 | port: 8080, // 端口
19 | open: false, // 启动后打开浏览器
20 | overlay: {
21 | // 当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
22 | warnings: false,
23 | errors: true
24 | },
25 | /* proxy: {
26 | //配置跨域
27 | "/api": {
28 | target: "http://127.0.0.1:8090/api",
29 | changeOrigin: true,
30 | ws: true,
31 | pathRewrite: {
32 | "^/api": "/"
33 | }
34 | }
35 | } */
36 | },
37 | css: {
38 | extract: IS_PROD, //是否将组件中的 CSS 提取至一个独立的 CSS 文件中 (而不是动态注入到 JavaScript 中的 inline 代码)。
39 | sourceMap: false,
40 | loaderOptions: {
41 | scss: {
42 | // 向全局sass样式传入共享的全局变量, $src可以配置图片cdn前缀
43 | // 详情: https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders
44 | prependData: `
45 | @import "assets/css/mixin.scss";
46 | @import "assets/css/variables.scss";
47 | $cdn: "${defaultSettings.$cdn}";
48 | `
49 | }
50 | }
51 | },
52 | configureWebpack: config => {
53 | config.name = name
54 | if (process.env.NODE_ENV === 'production') {// 为生产环境修改配置...
55 | config.mode = 'production';
56 | config["performance"] = {//打包文件大小配置
57 | "maxEntrypointSize": 10000000,
58 | "maxAssetSize": 30000000
59 | }
60 | }
61 |
62 | // 为生产环境修改配置...
63 | // if (IS_PROD) {
64 | // // externals
65 | // config.externals = externals
66 | // }
67 | },
68 |
69 | chainWebpack: config => {
70 | config.plugins.delete('preload') // TODO: need test
71 | config.plugins.delete('prefetch') // TODO: need test
72 |
73 | // 别名 alias
74 | config.resolve.alias
75 | .set('@', resolve('src'))
76 | .set('assets', resolve('src/assets'))
77 | .set('api', resolve('src/api'))
78 | .set('views', resolve('src/views'))
79 | .set('components', resolve('src/components'))
80 |
81 | /**
82 | * 添加CDN参数到htmlWebpackPlugin配置中
83 | */
84 | // config.plugin('html').tap(args => {
85 | // if (IS_PROD) {
86 | // args[0].cdn = cdn.build
87 | // } else {
88 | // args[0].cdn = cdn.dev
89 | // }
90 | // return args
91 | // })
92 |
93 | /**
94 | * 设置保留空格
95 | */
96 | config.module
97 | .rule('vue')
98 | .use('vue-loader')
99 | .loader('vue-loader')
100 | .tap(options => {
101 | options.compilerOptions.preserveWhitespace = true
102 | return options
103 | })
104 | .end()
105 | /**
106 | * 打包分析
107 | */
108 | if (IS_PROD) {
109 | config.plugin('webpack-report').use(BundleAnalyzerPlugin, [
110 | {
111 | analyzerMode: 'static'
112 | }
113 | ])
114 | }
115 | config
116 | // https://webpack.js.org/configuration/devtool/#development
117 | .when(!IS_PROD, config => config.devtool('cheap-source-map'))
118 |
119 | config.when(IS_PROD, config => {
120 | config
121 | .plugin('ScriptExtHtmlWebpackPlugin')
122 | .after('html')
123 | .use('script-ext-html-webpack-plugin', [
124 | {
125 | // 将 runtime 作为内联引入不单独存在
126 | inline: /runtime\..*\.js$/
127 | }
128 | ])
129 | .end()
130 | config.optimization.splitChunks({
131 | chunks: 'all',
132 | cacheGroups: {
133 | // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块
134 | commons: {
135 | name: 'chunk-commons',
136 | test: resolve('src/components'),
137 | minChunks: 3, // 被至少用三次以上打包分离
138 | priority: 5, // 优先级
139 | reuseExistingChunk: true // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。
140 | },
141 | node_vendors: {
142 | name: 'chunk-libs',
143 | chunks: 'initial', // 只打包初始时依赖的第三方
144 | test: /[\\/]node_modules[\\/]/,
145 | priority: 10
146 | },
147 | vantUI: {
148 | name: 'chunk-vantUI', // 单独将 vantUI 拆包
149 | priority: 20, // 数字大权重到,满足多个 cacheGroups 的条件时候分到权重高的
150 | test: /[\\/]node_modules[\\/]_?vant(.*)/
151 | }
152 | }
153 | })
154 | config.optimization.runtimeChunk('single')
155 | })
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/vue-blog-template/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 |
--------------------------------------------------------------------------------
/vue-blog-template/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: ["plugin:vue/essential", "@vue/prettier"],
7 | rules: {
8 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
9 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off"
10 | },
11 | parserOptions: {
12 | parser: "babel-eslint"
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/vue-blog-template/.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 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/vue-blog-template/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/app"]
3 | };
4 |
--------------------------------------------------------------------------------
/vue-blog-template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-blog-template",
3 | "author": "Jason Chen",
4 | "version": "1.0.0",
5 | "private": true,
6 | "scripts": {
7 | "serve": "vue-cli-service serve",
8 | "build": "vue-cli-service build",
9 | "lint": "vue-cli-service lint"
10 | },
11 | "dependencies": {
12 | "axios": "^0.19.0",
13 | "element-ui": "^2.11.1",
14 | "epic-spinners": "^1.1.0",
15 | "postcss-pxtorem": "^4.0.1",
16 | "viewerjs": "^1.3.6",
17 | "vue": "^2.6.10",
18 | "vue-ba": "^1.2.5",
19 | "vue-router": "^3.0.3",
20 | "vuex": "^3.0.1"
21 | },
22 | "devDependencies": {
23 | "@vue/cli-plugin-babel": "^3.9.0",
24 | "@vue/cli-plugin-eslint": "^3.9.0",
25 | "@vue/cli-service": "^3.9.0",
26 | "@vue/eslint-config-prettier": "^4.0.1",
27 | "babel-eslint": "^10.0.1",
28 | "babel-plugin-component": "^1.1.1",
29 | "babel-polyfill": "^6.26.0",
30 | "eslint": "^5.16.0",
31 | "eslint-plugin-vue": "^5.0.0",
32 | "node-sass": "^4.9.0",
33 | "sass-loader": "^7.1.0",
34 | "vue-template-compiler": "^2.6.10"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/vue-blog-template/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/vue-blog-template/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jcyicai/vue-blog-template/525110674b567994f807ac23ed23556a3f12b043/vue-blog-template/public/favicon.ico
--------------------------------------------------------------------------------
/vue-blog-template/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 | CYC Blog
11 |
165 |
166 |
167 |
168 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
196 |
197 |
198 |
199 |
--------------------------------------------------------------------------------
/vue-blog-template/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
24 |
25 |
55 |
--------------------------------------------------------------------------------
/vue-blog-template/src/assets/styles/index.scss:
--------------------------------------------------------------------------------
1 | body {
2 | height: 100%;
3 | -moz-osx-font-smoothing: grayscale;
4 | -webkit-font-smoothing: antialiased;
5 | text-rendering: optimizeLegibility;
6 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
7 | Microsoft YaHei, Arial, sans-serif;
8 | }
9 |
10 | html {
11 | height: 100%;
12 | box-sizing: border-box;
13 | }
14 |
15 | #app {
16 | height: 100%;
17 | }
18 |
19 | .layout {
20 | width: 1200px;
21 | margin: 0 auto;
22 | }
23 |
24 | *,
25 | *:before,
26 | *:after {
27 | box-sizing: inherit;
28 | }
29 |
30 | .no-padding {
31 | padding: 0px !important;
32 | }
33 |
34 | .padding-content {
35 | padding: 4px 0;
36 | }
37 |
38 | a:focus,
39 | a:active {
40 | outline: none;
41 | }
42 |
43 | a,
44 | a:focus,
45 | a:hover {
46 | cursor: pointer;
47 | color: inherit;
48 | text-decoration: none;
49 | }
50 |
51 | div:focus {
52 | outline: none;
53 | }
54 |
55 | .fr {
56 | float: right;
57 | }
58 |
59 | .fl {
60 | float: left;
61 | }
62 |
63 | .block {
64 | display: block;
65 | }
66 |
67 | .pointer {
68 | cursor: pointer;
69 | }
70 |
71 | .inlineBlock {
72 | display: block;
73 | }
74 |
75 | .clearfix {
76 | &:after {
77 | visibility: hidden;
78 | display: block;
79 | font-size: 0;
80 | content: " ";
81 | clear: both;
82 | height: 0;
83 | }
84 | }
85 |
86 | //main-container全局样式
87 | .app-container {
88 | padding: 20px;
89 | }
90 |
91 | .text-center {
92 | text-align: center;
93 | }
94 |
95 | /*滚动条样式修改*/
96 | ::-webkit-scrollbar {
97 | width: 8px;
98 | height: 8px;
99 | }
100 |
101 | ::-webkit-scrollbar-button:vertical {
102 | display: none;
103 | }
104 |
105 | ::-webkit-scrollbar-corner,
106 | ::-webkit-scrollbar-track {
107 | background-color: #f4f5f7;
108 | }
109 |
110 | ::-webkit-scrollbar-thumb {
111 | border-radius: 5px;
112 | background-color: #d7dbe5;
113 | }
114 |
--------------------------------------------------------------------------------
/vue-blog-template/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import router from "./router";
3 | import store from "./store";
4 | import ElementUI from "element-ui";
5 | import axios from "axios";
6 | import ba from "vue-ba"; //引入百度统计
7 | import "element-ui/lib/theme-chalk/index.css";
8 | import "@/assets/styles/normalize.scss";
9 | import "@/assets/styles/index.scss";
10 | import App from "./App.vue";
11 |
12 | Vue.config.productionTip = false;
13 | Vue.use(ElementUI);
14 | Vue.prototype.$axios = axios;
15 | Vue.use(ba, "c5d78"); //百度统计账户ID码,自行注册后更改
16 | Vue.use(ba, { siteId: "c5d78" });
17 |
18 | new Vue({
19 | router,
20 | store,
21 | render: h => h(App)
22 | }).$mount("#app");
23 |
--------------------------------------------------------------------------------
/vue-blog-template/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Router from "vue-router";
3 | import Layout from "@/views/layout";
4 |
5 | Vue.use(Router);
6 |
7 | export default new Router({
8 | mode: "history",
9 | base: process.env.BASE_URL,
10 | routes: [
11 | {
12 | path: "/",
13 | redirect: {
14 | path: "/home"
15 | }
16 | },
17 | {
18 | path: "",
19 | component: Layout,
20 | children: [
21 | {
22 | path: "/home",
23 | component: () => import("@/views/home/Home"),
24 | name: "home",
25 | meta: { title: "文章" }
26 | },
27 | {
28 | path: "/whisper",
29 | component: () => import("@/views/whisper/Whisper"),
30 | name: "whisper",
31 | meta: { title: "留言" }
32 | },
33 | {
34 | path: "/album",
35 | component: () => import("@/views/album/Album"),
36 | name: "album",
37 | meta: { title: "相册" }
38 | },
39 | {
40 | path: "/updateLog",
41 | component: () => import("@/views/updateLog/UpdateLog"),
42 | name: "updateLog",
43 | meta: { title: "更新" }
44 | },
45 | {
46 | path: "/about",
47 | component: () => import("@/views/about/About"),
48 | name: "about",
49 | meta: { title: "关于" }
50 | }
51 | ]
52 | }
53 | ],
54 | scrollBehavior() {
55 | return { x: 0, y: 0 };
56 | }
57 | });
58 |
--------------------------------------------------------------------------------
/vue-blog-template/src/store.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex from "vuex";
3 |
4 | Vue.use(Vuex);
5 |
6 | export default new Vuex.Store({
7 | state: {},
8 | mutations: {},
9 | actions: {}
10 | });
11 |
--------------------------------------------------------------------------------
/vue-blog-template/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Parse the time to string
3 | * @param {(Object|string|number)} time
4 | * @param {string} cFormat
5 | * @returns {string}
6 | */
7 | export function parseTime(time, cFormat) {
8 | if (arguments.length === 0) {
9 | return null;
10 | }
11 | const format = cFormat || "{y}-{m}-{d} {h}:{i}:{s}";
12 | let date;
13 | if (typeof time === "object") {
14 | date = time;
15 | } else {
16 | if (typeof time === "string" && /^[0-9]+$/.test(time)) {
17 | time = parseInt(time);
18 | }
19 | if (typeof time === "number" && time.toString().length === 10) {
20 | time = time * 1000;
21 | }
22 | date = new Date(time);
23 | }
24 | const formatObj = {
25 | y: date.getFullYear(),
26 | m: date.getMonth() + 1,
27 | d: date.getDate(),
28 | h: date.getHours(),
29 | i: date.getMinutes(),
30 | s: date.getSeconds(),
31 | a: date.getDay()
32 | };
33 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
34 | let value = formatObj[key];
35 | // Note: getDay() returns 0 on Sunday
36 | if (key === "a") {
37 | return ["日", "一", "二", "三", "四", "五", "六"][value];
38 | }
39 | if (result.length > 0 && value < 10) {
40 | value = "0" + value;
41 | }
42 | return value || 0;
43 | });
44 | return time_str;
45 | }
46 |
--------------------------------------------------------------------------------
/vue-blog-template/src/views/about/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
关于博客
6 |
7 |
8 |
博客简介
9 |
10 | - 博客采用服务端渲染(ssr),基于Vue通用框架开发
11 | -
12 | 前端技术栈:Vue + Vue-Router + Vue-cli3 + Axios + Sass + Element-ui
13 |
14 | -
15 | 后端技术栈:Node.js + Express + MongoDB + Mongoose
16 |
17 | -
18 | 服务器:CentOS7.6 + 七牛云
19 |
20 |
21 |
22 |
23 |
关于我
24 |
25 |
26 |
个人简介
27 |
28 | - 姓名:Jason Chen
29 | - 职位:Web前端开发工程师
30 | - 邮箱:chenyicai622@qq.com
31 | - 爱好:料理、烘焙、写代码
32 | - 座右铭:星光不问赶路人,时光不负有心人
33 |
34 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
65 |
--------------------------------------------------------------------------------
/vue-blog-template/src/views/album/Album.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
![]()
6 |
7 |
博主推荐
8 |
{{ imgName }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
{{ list.date }}
16 |
{{ list.name }}
17 |
18 |
19 |
20 |
21 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
128 |
129 |
206 |
--------------------------------------------------------------------------------
/vue-blog-template/src/views/layout/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
48 |
--------------------------------------------------------------------------------
/vue-blog-template/src/views/layout/components/Main.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/vue-blog-template/src/views/layout/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Header } from "./Header";
2 | export { default as Main } from "./Main";
3 |
--------------------------------------------------------------------------------
/vue-blog-template/src/views/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Copyright @ 2019 By CYC备案号123456
59 |
60 |
61 |
62 |
72 |
73 |
130 |
--------------------------------------------------------------------------------
/vue-blog-template/src/views/updateLog/UpdateLog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
更新日志
5 |
6 |
7 |
8 |
14 |
15 | {{ list.content }}
16 | {{ list.info }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
57 |
58 |
75 |
--------------------------------------------------------------------------------
/vue-blog-template/vue.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | module.exports = {
3 | publicPath: "./",
4 | outputDir: "dist",
5 | assetsDir: "static",
6 | productionSourceMap: false,
7 | pluginOptions: {
8 | returnCitySN: "returnCitySN"
9 | },
10 | devServer: {
11 | open: true,
12 | host: "0.0.0.0",
13 | port: 8080,
14 | proxy: {
15 | "/api": {
16 | target: "http://127.0.0.1:8090/api", //node端口自行更改
17 | changeOrigin: true,
18 | ws: true,
19 | pathRewrite: {
20 | "^/api": ""
21 | }
22 | }
23 | }
24 | },
25 | configureWebpack: {
26 | performance: {
27 | hints: "warning",
28 | //入口起点的最大体积 整数类型(以字节为单位)
29 | maxEntrypointSize: 50000000,
30 | //生成文件的最大体积 整数类型(以字节为单位 300k)
31 | maxAssetSize: 30000000,
32 | //只给出 js 文件的性能提示
33 | assetFilter: function(assetFilename) {
34 | return assetFilename.endsWith(".js");
35 | }
36 | }
37 | }
38 | };
39 |
--------------------------------------------------------------------------------