├── static ├── .gitkeep └── less-variables.less ├── code ├── client │ ├── src │ │ ├── javascript │ │ │ ├── index.js │ │ │ └── rem.js │ │ ├── images │ │ │ ├── cover.jpg │ │ │ ├── avatar.png │ │ │ ├── note-bg.jpg │ │ │ ├── blueprint.png │ │ │ ├── none-data.png │ │ │ ├── source_single_1.png │ │ │ ├── source_single_2.png │ │ │ ├── source_single_3.png │ │ │ ├── date-bg.svg │ │ │ ├── date-tag.svg │ │ │ └── date-edge.svg │ │ ├── components │ │ │ ├── Markdown │ │ │ │ ├── blueprint.png │ │ │ │ └── markdown.css │ │ │ ├── NoneData │ │ │ │ └── index.vue │ │ │ ├── Loading │ │ │ │ ├── loading.vue │ │ │ │ ├── loading-4.vue │ │ │ │ └── loading-3.vue │ │ │ ├── Icon-svg │ │ │ │ └── index.vue │ │ │ ├── Back │ │ │ │ └── index.vue │ │ │ ├── Tag │ │ │ │ └── index.vue │ │ │ └── Github │ │ │ │ └── index.vue │ │ ├── style │ │ │ ├── index.css │ │ │ └── css │ │ │ │ ├── animation.css │ │ │ │ ├── init.css │ │ │ │ ├── function.css │ │ │ │ ├── markdown.css │ │ │ │ └── reset.css │ │ ├── App.vue │ │ ├── utils │ │ │ ├── auth.js │ │ │ ├── fetch.js │ │ │ └── storage.js │ │ ├── store │ │ │ ├── index.js │ │ │ ├── modules │ │ │ │ ├── classify.js │ │ │ │ ├── app.js │ │ │ │ └── blog.js │ │ │ └── getters.js │ │ ├── custom-components.js │ │ ├── main.js │ │ ├── router │ │ │ └── index.js │ │ ├── views │ │ │ ├── home │ │ │ │ ├── tags.vue │ │ │ │ ├── info.vue │ │ │ │ ├── index.vue │ │ │ │ └── blog.vue │ │ │ └── Article │ │ │ │ └── index.vue │ │ └── filters │ │ │ └── index.js │ └── index.html ├── .DS_Store ├── admin │ ├── src │ │ ├── styles │ │ │ ├── index.less │ │ │ ├── index.css │ │ │ ├── css │ │ │ │ ├── init.css │ │ │ │ ├── function.css │ │ │ │ └── reset.css │ │ │ └── less │ │ │ │ ├── element-ui.less │ │ │ │ └── btns.less │ │ ├── .DS_Store │ │ ├── store │ │ │ ├── .DS_Store │ │ │ ├── getters.js │ │ │ ├── index.js │ │ │ └── modules │ │ │ │ ├── other.js │ │ │ │ ├── classify.js │ │ │ │ ├── blog.js │ │ │ │ ├── app.js │ │ │ │ ├── permission.js │ │ │ │ └── user.js │ │ ├── views │ │ │ ├── Permission │ │ │ │ ├── set │ │ │ │ │ └── index.vue │ │ │ │ ├── add │ │ │ │ │ └── index.vue │ │ │ │ ├── edit │ │ │ │ │ └── index.vue │ │ │ │ └── list │ │ │ │ │ └── index.vue │ │ │ ├── Layout │ │ │ │ ├── index.js │ │ │ │ ├── content.vue │ │ │ │ ├── index.vue │ │ │ │ ├── tabsView.vue │ │ │ │ ├── levelbar.vue │ │ │ │ ├── navBar.vue │ │ │ │ ├── account.vue │ │ │ │ └── slideBar.vue │ │ │ ├── Home │ │ │ │ └── index.vue │ │ │ ├── Login │ │ │ │ └── index.vue │ │ │ └── Article │ │ │ │ ├── edit │ │ │ │ └── index.vue │ │ │ │ └── add │ │ │ │ └── index.vue │ │ ├── components │ │ │ ├── Markdown │ │ │ │ ├── blueprint.png │ │ │ │ └── markdown.css │ │ │ ├── Icon-svg │ │ │ │ └── index.vue │ │ │ ├── Github │ │ │ │ └── index.vue │ │ │ └── Hamburger │ │ │ │ └── index.vue │ │ ├── custom-components.js │ │ ├── App.vue │ │ ├── utils │ │ │ ├── auth.js │ │ │ ├── fetch.js │ │ │ └── storage.js │ │ ├── main.js │ │ ├── filters │ │ │ └── index.js │ │ ├── router │ │ │ ├── permission.js │ │ │ └── index.js │ │ └── font │ │ │ └── iconfont.js │ ├── .DS_Store │ └── index.html └── server │ ├── .DS_Store │ ├── index.js │ ├── controller │ ├── .DS_Store │ ├── admin │ │ ├── other.js │ │ ├── blog.js │ │ └── user.js │ └── client │ │ └── blog.js │ ├── middleware │ ├── .DS_Store │ ├── log │ │ ├── .DS_Store │ │ ├── index.js │ │ ├── access.js │ │ └── log.js │ ├── func │ │ ├── get_info.js │ │ ├── index.js │ │ ├── db.js │ │ └── file.js │ ├── send │ │ └── index.js │ ├── auth │ │ └── index.js │ ├── rule │ │ └── index.js │ └── index.js │ ├── models │ ├── user.js │ └── blog.js │ ├── app.js │ ├── mongodb.js │ ├── config.js │ └── router │ └── index.js ├── .DS_Store ├── public └── .DS_Store ├── postcss.config.js ├── .gitignore ├── .babelrc ├── README.md ├── config └── index.js └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/client/src/javascript/index.js: -------------------------------------------------------------------------------- 1 | import './rem' -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/.DS_Store -------------------------------------------------------------------------------- /code/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/.DS_Store -------------------------------------------------------------------------------- /public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/public/.DS_Store -------------------------------------------------------------------------------- /code/admin/src/styles/index.less: -------------------------------------------------------------------------------- 1 | @import './less/btns.less'; 2 | @import './less/element-ui.less'; -------------------------------------------------------------------------------- /code/admin/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/admin/.DS_Store -------------------------------------------------------------------------------- /code/server/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/server/.DS_Store -------------------------------------------------------------------------------- /code/server/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register') // babel编译 2 | module.exports = require('./app.js') -------------------------------------------------------------------------------- /code/admin/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/admin/src/.DS_Store -------------------------------------------------------------------------------- /code/admin/src/store/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/admin/src/store/.DS_Store -------------------------------------------------------------------------------- /code/admin/src/styles/index.css: -------------------------------------------------------------------------------- 1 | @import './css/function.css'; 2 | @import './css/reset.css'; 3 | @import './css/init.css'; -------------------------------------------------------------------------------- /code/client/src/images/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/client/src/images/cover.jpg -------------------------------------------------------------------------------- /code/server/controller/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/server/controller/.DS_Store -------------------------------------------------------------------------------- /code/server/middleware/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/server/middleware/.DS_Store -------------------------------------------------------------------------------- /code/client/src/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/client/src/images/avatar.png -------------------------------------------------------------------------------- /code/client/src/images/note-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/client/src/images/note-bg.jpg -------------------------------------------------------------------------------- /code/client/src/images/blueprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/client/src/images/blueprint.png -------------------------------------------------------------------------------- /code/client/src/images/none-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/client/src/images/none-data.png -------------------------------------------------------------------------------- /code/server/middleware/log/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/server/middleware/log/.DS_Store -------------------------------------------------------------------------------- /code/client/src/images/source_single_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/client/src/images/source_single_1.png -------------------------------------------------------------------------------- /code/client/src/images/source_single_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/client/src/images/source_single_2.png -------------------------------------------------------------------------------- /code/client/src/images/source_single_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/client/src/images/source_single_3.png -------------------------------------------------------------------------------- /code/admin/src/views/Permission/set/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /code/admin/src/components/Markdown/blueprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/admin/src/components/Markdown/blueprint.png -------------------------------------------------------------------------------- /code/client/src/components/Markdown/blueprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cd-dongzi/vue-node-blog/HEAD/code/client/src/components/Markdown/blueprint.png -------------------------------------------------------------------------------- /code/admin/src/custom-components.js: -------------------------------------------------------------------------------- 1 | import IconSvg from 'components/Icon-svg' 2 | const install = Vue => { 3 | Vue.component('Icon', IconSvg) 4 | } 5 | export default install 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require("autoprefixer")({ 4 | browsers: ["iOS >= 7", "Android >= 4.1", 'last 5 versions'] 5 | }) 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /code/admin/src/views/Layout/index.js: -------------------------------------------------------------------------------- 1 | export { default as NavBar } from './navBar' 2 | export { default as SlideBar } from './slideBar' 3 | export { default as ContentView } from './content' 4 | -------------------------------------------------------------------------------- /code/client/src/style/index.css: -------------------------------------------------------------------------------- 1 | @import './css/animation.css'; 2 | @import './css/function.css'; 3 | @import './css/markdown.css'; 4 | @import './css/reset.css'; 5 | @import './css/init.css'; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | logs/ 5 | 6 | 7 | # Editor directories and files 8 | .idea 9 | .vscode 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | -------------------------------------------------------------------------------- /code/admin/src/views/Layout/content.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /static/less-variables.less: -------------------------------------------------------------------------------- 1 | @theme-color: #c1866a; 2 | @vice-color: rgba(186,164,119,0.99); 3 | @paper-color: #FFEAB1; 4 | @theme-red-color: #db5640; 5 | @pc-width: 767px; 6 | @article-color: #f9f9f3; 7 | 8 | -------------------------------------------------------------------------------- /code/admin/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /code/client/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /code/client/src/images/date-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/client/src/images/date-tag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/server/models/user.js: -------------------------------------------------------------------------------- 1 | import db from '../mongodb' 2 | let userSchema = db.Schema({ 3 | username: String, 4 | pwd: String, 5 | name: String, 6 | avatar: String, 7 | roles: Array, 8 | createTime: { type: Date, default: Date.now}, 9 | loginTime: Date 10 | }) 11 | export default db.model('user', userSchema); -------------------------------------------------------------------------------- /code/admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /code/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /code/client/src/components/NoneData/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /code/admin/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import { Cookie } from './storage' 2 | const TokenKey = 'Token-Auth' 3 | 4 | export function getToken() { 5 | return Cookie.get(TokenKey) 6 | } 7 | 8 | export function setToken(token) { 9 | return Cookie.set(TokenKey, token) 10 | } 11 | 12 | export function removeToken() { 13 | return Cookie.remove(TokenKey) 14 | } 15 | -------------------------------------------------------------------------------- /code/server/middleware/func/get_info.js: -------------------------------------------------------------------------------- 1 | export const get_client_ip = ctx => { 2 | return ctx.request.headers['x-forwarded-for'] || 3 | (ctx.request.connection && ctx.request.connection.remoteAddress) || 4 | ctx.request.socket.remoteAddress || 5 | (ctx.request.connection.socket && ctx.request.connection.socket.remoteAddress) || null 6 | } -------------------------------------------------------------------------------- /code/client/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import { Cookie } from './storage' 2 | const TokenKey = 'Token-Auth' 3 | 4 | export function getToken() { 5 | return Cookie.get(TokenKey) 6 | } 7 | 8 | export function setToken(token) { 9 | return Cookie.set(TokenKey, token) 10 | } 11 | 12 | export function removeToken() { 13 | return Cookie.remove(TokenKey) 14 | } 15 | -------------------------------------------------------------------------------- /code/client/src/images/date-edge.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/client/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 blog from './modules/blog' 6 | 7 | Vue.use(Vuex) 8 | 9 | const store = new Vuex.Store({ 10 | modules: { 11 | app, 12 | blog 13 | }, 14 | getters 15 | }) 16 | 17 | export default store; -------------------------------------------------------------------------------- /code/server/app.js: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import ip from 'ip' 3 | import conf from './config' 4 | import router from './router' 5 | import middleware from './middleware' 6 | import './mongodb' 7 | 8 | const app = new Koa() 9 | middleware(app) 10 | router(app) 11 | app.listen(conf.port, '0.0.0.0', () => { 12 | console.log(`server is running at http://${ip.address()}:${conf.port}`) 13 | }) -------------------------------------------------------------------------------- /code/admin/src/styles/css/init.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | width: 1em; height: 1em; 3 | vertical-align: -0.15em; 4 | fill: currentColor; 5 | overflow: hidden; 6 | } 7 | 8 | ::-webkit-scrollbar { 9 | width: 4px; 10 | height: 4px; 11 | } 12 | 13 | ::-webkit-scrollbar-thumb { 14 | opacity: .8; 15 | background: #c1866a; 16 | border-radius: 4px; 17 | transition: all .5s; 18 | } 19 | -------------------------------------------------------------------------------- /code/admin/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sources: state => state.app.sources, 3 | 4 | userName: state => state.user.name, 5 | userList: state =>state.user.list, 6 | userTotal: state =>state.user.total, 7 | 8 | blogTypes: state => state.blog.blogTypes, 9 | blogList: state => state.blog.list, 10 | blogTotal: state => state.blog.total 11 | } 12 | export default getters -------------------------------------------------------------------------------- /code/client/src/components/Loading/loading.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /code/server/models/blog.js: -------------------------------------------------------------------------------- 1 | import db from '../mongodb' 2 | let blogSchema = db.Schema({ 3 | type: Array, 4 | title: String, 5 | desc: String, 6 | html: String, 7 | markdown: String, 8 | level: Number, 9 | github: String, 10 | source: Number, 11 | isVisible: Boolean, 12 | releaseTime: Date, 13 | createTime: { type: Date, default: Date.now} 14 | }) 15 | export default db.model('blog', blogSchema) -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | 5 | ["env", { 6 | "modules": false, 7 | "targets": { 8 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 9 | } 10 | }], 11 | "stage-2" 12 | ], 13 | "plugins": ["transform-runtime"], 14 | "env": { 15 | "test": { 16 | "presets": ["env", "stage-2"] 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /code/server/middleware/func/index.js: -------------------------------------------------------------------------------- 1 | import * as get_Info_func from './get_info' 2 | import * as db_func from './db' 3 | import * as file_func from './file' 4 | 5 | 6 | export default () => { 7 | const func = Object.assign({}, get_Info_func, db_func, file_func) 8 | return async (ctx, next) => { 9 | for (let v in func) { 10 | if (func.hasOwnProperty(v)) ctx[v] = func[v]; 11 | } 12 | await next() 13 | } 14 | } -------------------------------------------------------------------------------- /code/client/src/store/modules/classify.js: -------------------------------------------------------------------------------- 1 | // 来源 2 | export const sources = [ 3 | { name: '原创', id: 1 }, 4 | { name: '转载', id: 2 }, 5 | { name: '翻译', id: 3 } 6 | ] 7 | 8 | //博客分类 9 | export const blogTypes = [ 10 | { name: 'HTML' }, 11 | { name: 'CSS' }, 12 | { name: 'JavaScript' }, 13 | { name: 'Vue' }, 14 | { name: 'Webpack' }, 15 | { name: 'Node' }, 16 | { name: 'MongoDB' }, 17 | { name: '算法' }, 18 | { name: '工具' }, 19 | { name: '黑科技' } 20 | ] -------------------------------------------------------------------------------- /code/client/src/custom-components.js: -------------------------------------------------------------------------------- 1 | import IconSvg from 'components/Icon-svg' 2 | import Tag from 'components/Tag' 3 | import Back from 'components/Back' 4 | import NoneData from 'components/NoneData' 5 | import Loading from 'components/Loading/loading' 6 | const install = Vue => { 7 | Vue.component('Icon', IconSvg) 8 | Vue.component('Tag', Tag) 9 | Vue.component('Back', Back) 10 | Vue.component('NoneData', NoneData) 11 | Vue.component('Loading', Loading) 12 | } 13 | export default install 14 | -------------------------------------------------------------------------------- /code/client/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | // app 3 | screen_width: state => state.app.screen_width, 4 | pc_width: state => state.app.pc_width, 5 | pc_bol: state => state.app.pc_bol, 6 | 7 | // blog 8 | blogTypes: state => state.blog.types, 9 | blogList: state => state.blog.list, 10 | blogInfo: state => state.blog.info, 11 | blogLoadingMore: state => state.blog.loadingMore, 12 | blogLoadingBol: state => state.blog.loadingBol 13 | 14 | 15 | } 16 | export default getters -------------------------------------------------------------------------------- /code/server/middleware/log/index.js: -------------------------------------------------------------------------------- 1 | import logger from './log' 2 | 3 | export default opts => { 4 | let loggerMiddleware = logger(opts); 5 | return async (ctx, next) => { 6 | return loggerMiddleware(ctx, next) 7 | .catch( e => { 8 | if (ctx.status < 500) { 9 | ctx.status = 500; 10 | } 11 | ctx.log.error(e.stack); 12 | ctx.state.logged = true; 13 | ctx.throw(e); 14 | }) 15 | } 16 | } -------------------------------------------------------------------------------- /code/admin/src/components/Icon-svg/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /code/server/mongodb.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import conf from './config' 3 | // const DB_URL = `mongodb://${conf.mongodb.address}/${conf.mongodb.db}` 4 | const DB_URL = `mongodb://${conf.mongodb.username}:${conf.mongodb.pwd}@${conf.mongodb.address}/${conf.mongodb.db}`; // 账号登陆 5 | mongoose.Promise = global.Promise 6 | mongoose.connect(DB_URL, { useMongoClient: true }, err => { 7 | if (err) { 8 | console.log("数据库连接失败!") 9 | }else{ 10 | console.log("数据库连接成功!") 11 | } 12 | }) 13 | export default mongoose 14 | -------------------------------------------------------------------------------- /code/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 user from './modules/user' 6 | import permission from './modules/permission' 7 | import blog from './modules/blog' 8 | import other from './modules/other' 9 | 10 | Vue.use(Vuex) 11 | 12 | const store = new Vuex.Store({ 13 | modules: { 14 | app, 15 | user, 16 | permission, 17 | blog, 18 | other 19 | }, 20 | getters 21 | }) 22 | 23 | export default store -------------------------------------------------------------------------------- /code/admin/src/views/Home/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 18 | 24 | -------------------------------------------------------------------------------- /code/client/src/components/Icon-svg/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /code/client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import './style/index.css' 6 | import './javascript/index' 7 | 8 | import * as filters from './filters' 9 | Object.keys(filters).forEach(key => { 10 | Vue.filter(key, filters[key]) 11 | }) 12 | 13 | import customComponents from './custom-components' 14 | Vue.use(customComponents) 15 | 16 | new Vue({ 17 | el: '#app', 18 | router, 19 | store, 20 | template: '', 21 | components: { App } 22 | }) -------------------------------------------------------------------------------- /code/server/middleware/log/access.js: -------------------------------------------------------------------------------- 1 | export default (ctx, msg, commonInfo) => { 2 | const { 3 | method, // 请求方法 get post或其他 4 | url, // 请求链接 5 | host, // 发送请求的客户端的host 6 | headers // 请求中的headers 7 | } = ctx.request; 8 | const client = { 9 | method, 10 | url, 11 | host, 12 | msg, 13 | ip: ctx.get_client_ip(ctx), 14 | referer: headers['referer'], // 请求的源地址 15 | userAgent: headers['user-agent'] // 客户端信息 设备及浏览器信息 16 | } 17 | return JSON.stringify(Object.assign(commonInfo, client)); 18 | } -------------------------------------------------------------------------------- /code/admin/src/store/modules/other.js: -------------------------------------------------------------------------------- 1 | import axios from 'src/utils/fetch' 2 | const other = { 3 | state: { 4 | 5 | }, 6 | mutations: { 7 | 8 | }, 9 | actions: { 10 | markdown_upload_img ({commit}, formData) { 11 | return new Promise( (resolve, reject) => { 12 | axios.postFile('markdown_upload_img', formData) 13 | .then( res => { 14 | resolve(res) 15 | }).catch( err => { 16 | reject(err) 17 | }) 18 | }) 19 | } 20 | } 21 | }; 22 | export default other 23 | -------------------------------------------------------------------------------- /code/server/config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | const auth = { 3 | admin_secret: 'admin-token', 4 | tokenKey: 'Token-Auth', 5 | whiteList: ['login', 'client_api'], 6 | blackList: ['admin_api'] 7 | } 8 | 9 | const log = { 10 | logLevel: 'debug', // 指定记录的日志级别 11 | dir: path.resolve(__dirname, '../../logs'), // 指定日志存放的目录名 12 | projectName: 'blog', // 项目名,记录在日志中的项目信息 13 | ip: '0.0.0.0' // 默认情况下服务器 ip 地址 14 | } 15 | const port = process.env.NODE_ENV === 'production' ? '80' : '3000' 16 | 17 | export default { 18 | env: process.env.NODE_ENV, 19 | port, 20 | auth, 21 | log, 22 | mongodb: { 23 | username: 'cd', 24 | pwd: 123456, 25 | address: 'localhost:27017', 26 | db: 'test' 27 | } 28 | } -------------------------------------------------------------------------------- /code/admin/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router/permission' 4 | import store from './store' 5 | import ElementUI from 'element-ui'; 6 | import 'element-ui/lib/theme-chalk/index.css' 7 | import axios from './utils/fetch' 8 | import customComponents from './custom-components.js' 9 | 10 | import './styles/index.css' 11 | import './styles/index.less' 12 | import './font/iconfont' 13 | 14 | 15 | import * as filters from './filters' 16 | Object.keys(filters).forEach(key => { 17 | Vue.filter(key, filters[key]) 18 | }) 19 | 20 | Vue.use(ElementUI) 21 | Vue.use(customComponents) 22 | Vue.prototype.axios = axios 23 | 24 | new Vue({ 25 | el: '#app', 26 | router, 27 | store, 28 | template: '', 29 | components: { App } 30 | }) -------------------------------------------------------------------------------- /code/server/controller/admin/other.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | async markdown_upload_img (ctx, next) { 5 | console.log('----------------添加图片 markdown_upload_img-----------------------'); 6 | let opts = { 7 | path: path.resolve(__dirname, '../../../../public') 8 | } 9 | let result = await ctx.uploadFile(ctx, opts) 10 | ctx.send(result) 11 | }, 12 | 13 | async del_markdown_upload_img (ctx, next) { 14 | console.log('----------------删除图片 del_markdown_upload_img-----------------------'); 15 | let id = ctx.request.query.id 16 | try { 17 | ctx.remove(musicModel, {_id: id}) 18 | ctx.send() 19 | }catch(e){ 20 | ctx.sendError(e) 21 | } 22 | // console.log(id) 23 | } 24 | } -------------------------------------------------------------------------------- /code/server/middleware/send/index.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | let render = ctx => { 3 | return (json, msg) => { 4 | ctx.set("Content-Type", "application/json"); 5 | ctx.body = JSON.stringify({ 6 | code: 1, 7 | data: json || {}, 8 | msg: msg || 'success' 9 | }); 10 | } 11 | } 12 | let renderError = ctx => { 13 | return msg => { 14 | ctx.set("Content-Type", "application/json"); 15 | ctx.body = JSON.stringify({ 16 | code: 0, 17 | data: {}, 18 | msg: msg.toString() 19 | }); 20 | } 21 | } 22 | return async (ctx, next) => { 23 | ctx.send = render(ctx); 24 | ctx.sendError = renderError(ctx); 25 | await next() 26 | } 27 | } -------------------------------------------------------------------------------- /code/server/middleware/auth/index.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | import conf from '../../config' 3 | 4 | export default () => { 5 | return async (ctx, next) => { 6 | if ( conf.auth.blackList.some(v => ctx.path.indexOf(v) >= 0) ) { 7 | let token = ctx.cookies.get(conf.auth.tokenKey); 8 | try { 9 | jwt.verify(token, conf.auth.admin_secret); 10 | }catch (e) { 11 | if ('TokenExpiredError' === e.name) { 12 | ctx.sendError('token已过期, 请重新登录!'); 13 | ctx.throw(401, 'token expired,请及时本地保存数据!'); 14 | } 15 | ctx.sendError('token验证失败, 请重新登录!'); 16 | ctx.throw(401, 'invalid token'); 17 | } 18 | console.log("鉴权成功"); 19 | } 20 | await next(); 21 | } 22 | } -------------------------------------------------------------------------------- /code/client/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | Vue.use(Router) 4 | 5 | // const _import_ = file => () => import('components/' + file + '.vue') 6 | const _import_ = file => () => import('views/' + file + '.vue') 7 | 8 | const router = new Router({ 9 | mode: 'history', 10 | routes: [ 11 | { 12 | path: '/', 13 | component: _import_('home/index'), 14 | redirect: 'blog/all', 15 | children: [ 16 | { 17 | path: 'blog/:classify', 18 | component: _import_('home/blog') 19 | } 20 | ] 21 | }, 22 | { 23 | path: '/article/:id', 24 | component: _import_('Article/index') 25 | } 26 | ] 27 | }) 28 | 29 | export default router -------------------------------------------------------------------------------- /code/client/src/views/home/tags.vue: -------------------------------------------------------------------------------- 1 | 8 | 26 | -------------------------------------------------------------------------------- /code/client/src/javascript/rem.js: -------------------------------------------------------------------------------- 1 | (function(win, doc){ 2 | var docEl = doc.documentElement, 3 | resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', 4 | refresh = function () { 5 | var w = docEl.clientWidth, 6 | dpr = win.devicePixelRatio || 1; 7 | 8 | docEl.style.fontSize = 100 * (w/750) + 'px'; 9 | 10 | function setBodyFontSize () { 11 | if (doc.body) { 12 | // doc.body.style.fontSize = 32 * (w/750)+ 'px' 13 | doc.body.style.fontSize = '16px'; 14 | }else { 15 | doc.addEventListener('DOMContentLoaded', refresh) 16 | } 17 | } 18 | setBodyFontSize(); 19 | }; 20 | refresh(); 21 | 22 | if (!doc.addEventListener) return; 23 | win.addEventListener(resizeEvt, refresh, false); 24 | })(window, document); -------------------------------------------------------------------------------- /code/admin/src/store/modules/classify.js: -------------------------------------------------------------------------------- 1 | // 来源 2 | export const sources = [ 3 | { name: '原创', id: 1 }, 4 | { name: '转载', id: 2 }, 5 | { name: '翻译', id: 3 } 6 | ] 7 | 8 | //博客分类 9 | export const blogTypes = [ 10 | { name: 'HTML' }, 11 | { name: 'CSS' }, 12 | { name: 'JavaScript' }, 13 | { name: 'Vue' }, 14 | { name: 'Webpack' }, 15 | { name: 'Node' }, 16 | { name: 'MongoDB' }, 17 | { name: '算法' }, 18 | { name: '工具' }, 19 | { name: '黑科技' } 20 | ] 21 | export const blogFilters = [ 22 | { text: 'HTML', value: 'HTML' }, 23 | { text: 'CSS', value: 'CSS' }, 24 | { text: 'JavaScript', value: 'JavaScript' }, 25 | { text: 'Vue', value: 'Vue' }, 26 | { text: 'Webpack', value: 'Webpack' }, 27 | { text: 'Node', value: 'Node' }, 28 | { text: 'MongoDB', value: 'MongoDB' }, 29 | { text: '算法', value: '算法' }, 30 | { text: '工具', value: '工具' }, 31 | { text: '黑科技', value: '黑科技' } 32 | ] 33 | -------------------------------------------------------------------------------- /code/client/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import { Local } from '../../utils/storage' 2 | import axios from '../../utils/fetch.js' 3 | const app = { 4 | state: { 5 | screen_width: 0, 6 | pc_width: 767, 7 | pc_bol: (function(){ 8 | let w = document.documentElement.clientWidth || document.body.clientWidth; 9 | if (w > 767) return true 10 | return false 11 | })() 12 | }, 13 | mutations: { 14 | CHANGESCREENWIDTH (state, width) { 15 | state.screen_width = width 16 | }, 17 | CHANGEHEADERHEIGHT (state, height) { 18 | state.header_height = height 19 | } 20 | }, 21 | actions: { 22 | changeScreenWidth ({commit}, width) { 23 | commit('CHANGESCREENWIDTH', width) 24 | }, 25 | changeHeaderHeight ({commit}, height) { 26 | commit('CHANGEHEADERHEIGHT', height) 27 | } 28 | } 29 | } 30 | export default app -------------------------------------------------------------------------------- /code/admin/src/filters/index.js: -------------------------------------------------------------------------------- 1 | export function parseTime(time, cFormat) { 2 | if (arguments.length === 0) { 3 | return null 4 | } 5 | if ((time + '').length === 10) { 6 | time = +time * 1000 7 | } 8 | 9 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 10 | let date 11 | if (typeof time === 'object') { 12 | date = time 13 | } else { 14 | date = new Date(time) 15 | } 16 | 17 | 18 | const formatObj = { 19 | y: date.getFullYear(), 20 | m: date.getMonth() + 1, 21 | d: date.getDate(), 22 | h: date.getHours(), 23 | i: date.getMinutes(), 24 | s: date.getSeconds(), 25 | a: date.getDay() 26 | } 27 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 28 | let value = formatObj[key] 29 | if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] 30 | if (result.length > 0 && value < 10) { 31 | value = '0' + value 32 | } 33 | return value || 0 34 | }) 35 | return time_str 36 | } 37 | -------------------------------------------------------------------------------- /code/client/src/filters/index.js: -------------------------------------------------------------------------------- 1 | export function parseTime(time, cFormat) { 2 | if (arguments.length === 0) { 3 | return null 4 | } 5 | if ((time + '').length === 10) { 6 | time = +time * 1000 7 | } 8 | 9 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 10 | let date 11 | if (typeof time === 'object') { 12 | date = time 13 | } else { 14 | date = new Date(time) 15 | } 16 | 17 | 18 | const formatObj = { 19 | y: date.getFullYear(), 20 | m: date.getMonth() + 1, 21 | d: date.getDate(), 22 | h: date.getHours(), 23 | i: date.getMinutes(), 24 | s: date.getSeconds(), 25 | a: date.getDay() 26 | } 27 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 28 | let value = formatObj[key] 29 | if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] 30 | if (result.length > 0 && value < 10) { 31 | value = '0' + value 32 | } 33 | return value || 0 34 | }) 35 | return time_str 36 | } 37 | -------------------------------------------------------------------------------- /code/server/controller/client/blog.js: -------------------------------------------------------------------------------- 1 | import blogModel from '../../models/blog' 2 | import path from 'path' 3 | 4 | module.exports = { 5 | async list(ctx, next) { 6 | console.log('----------------获取博客列表 client_demo_api/blog/list-----------------------'); 7 | let { type = null, pageindex = 1, pagesize = 5} = ctx.request.query; 8 | console.log('type:'+type+','+'pageindex:'+pageindex +','+ 'pagesize:'+pagesize) 9 | try { 10 | 11 | let data = await ctx.find(blogModel, {type, isVisible: true, source: 1}, null, {limit: pagesize*1, skip: (pageindex-1)*pagesize, sort: {level: -1, createTime: -1}}); 12 | return ctx.send(data) 13 | }catch (e){ 14 | console.log(e) 15 | return ctx.sendError(e) 16 | } 17 | 18 | }, 19 | async info(ctx, next) { 20 | console.log('----------------获取博客信息 client_demo_api/blog/info-----------------------'); 21 | let _id = ctx.request.query._id; 22 | try { 23 | let data = await ctx.findOne(blogModel, { _id }); 24 | return ctx.send(data); 25 | } catch (e) { 26 | return ctx.sendError(e) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /code/server/middleware/rule/index.js: -------------------------------------------------------------------------------- 1 | import Path from 'path' 2 | import fs from 'fs' 3 | 4 | export default opts => { 5 | let { 6 | app, 7 | rules = [] 8 | } = opts 9 | if (!app) { 10 | throw new Error("the app params is necessary!") 11 | } 12 | 13 | app.router = {}; 14 | const appKeys = Object.keys(app) 15 | rules.forEach((item) => { 16 | let { 17 | path, 18 | name 19 | } = item 20 | if (appKeys.includes(name)) { 21 | throw new Error(`the name of ${name} already exists!`) 22 | } 23 | let content = {}; 24 | //readdirSync: 方法将返回一个包含“指定目录下所有文件名称”的数组对象。 25 | //extname: 返回path路径文件扩展名,如果path以 ‘.' 为结尾,将返回 ‘.',如果无扩展名 又 不以'.'结尾,将返回空值。 26 | //basename: path.basename(p, [ext]) p->要处理的path ext->要过滤的字符 27 | fs.readdirSync(path).forEach(filename => { 28 | 29 | let extname = Path.extname(filename); 30 | if (extname === '.js') { 31 | let name = Path.basename(filename, extname) 32 | content[name] = require(Path.join(path, filename)) 33 | content[name].filename = name 34 | } 35 | }) 36 | app[name] = content 37 | }) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue + Node + Mongodb 开发一个完整博客流程 2 | 3 | ## 本地开发: 4 | 后台管理: localhost:8090 5 | 6 | web端: localhost:8080 7 | 8 | server端: localhost:3000 9 | 10 | 11 | ## 脚本命令 12 | npm run dev:admin // 本地开发后台管理 13 | 14 | npm run dev:client // 本地开发前台页面 15 | 16 | npm run build:admin // 项目打包 - 后台管理 17 | 18 | npm run build:client // 项目打包 - 前台 19 | 20 | npm run analyz // 查看打包信息 21 | 22 | npm run server // 启动服务 23 | 24 | 25 | ## 注意事项: 26 | 1. `cnpm run server` 启动服务器 27 | 2. 启动时,记得启动mongodb数据库,账号密码 可以在 server/config.js 文件下进行配置 28 | 3. `db.createUser({user:"cd",pwd:"123456",roles:[{role:"readWrite",db:'test'}]})` (mongodb 注册用户) 29 | 4. `cnpm run dev:admin` 启动后台管理界面 30 | 5. 登录后台管理界面录制数据 31 | 6. 登录后台管理时需要在数据库 创建 users 集合注册一个账号进行登录 32 | ``` 33 | db.users.insert({ 34 | "name" : "cd", 35 | "pwd" : "e10adc3949ba59abbe56e057f20f883e", 36 | "username" : "admin", 37 | "roles" : [ 38 | "admin" 39 | ] 40 | }) 41 | 42 | // 账号: admin 密码: 123456 43 | ``` 44 | 7. `cnpm run dev:client` 启动前台页面 45 | 46 | 47 | 48 | **参考文章** 49 | > [个人博客](http://dzblog.cn/article/5a69609c3c04164b0bd4b964) 50 | 51 | > [基于Koa2搭建Node.js实战项目教程](https://github.com/ikcamp/koa2-tutorial) 52 | 53 | > [手摸手,带你用vue撸后台](https://segmentfault.com/a/1190000010043013) 54 | -------------------------------------------------------------------------------- /code/client/src/components/Loading/loading-4.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /code/admin/src/views/Layout/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | 24 | 25 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /code/client/src/views/home/info.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /code/server/router/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import koaRouter from 'koa-router' 4 | const router = koaRouter() 5 | 6 | export default app => { 7 | 8 | /*----------------------admin-------------------------------*/ 9 | // 用户请求 10 | router.post('/admin_demo_api/user/login', app.admin.user.login) 11 | router.get('/admin_demo_api/user/info', app.admin.user.info) 12 | router.get('/admin_demo_api/user/list', app.admin.user.list) 13 | router.post('/admin_demo_api/user/add', app.admin.user.add) 14 | router.post('/admin_demo_api/user/update', app.admin.user.update) 15 | router.get('/admin_demo_api/user/del', app.admin.user.del) 16 | 17 | 18 | // 文章请求 19 | router.get('/admin_demo_api/blog/list', app.admin.blog.list) 20 | router.post('/admin_demo_api/blog/add', app.admin.blog.add) 21 | router.post('/admin_demo_api/blog/update', app.admin.blog.update) 22 | router.get('/admin_demo_api/blog/del', app.admin.blog.del) 23 | 24 | // 其他请求 25 | router.post('/admin_demo_api/markdown_upload_img', app.admin.other.markdown_upload_img) 26 | 27 | 28 | /*----------------------client-------------------------------*/ 29 | // client/文章请求 30 | router.get('/client_demo_api/blog/list', app.client.blog.list) 31 | router.get('/client_demo_api/blog/info', app.client.blog.info) 32 | 33 | 34 | 35 | 36 | app.use(router.routes()).use(router.allowedMethods()); 37 | } -------------------------------------------------------------------------------- /code/admin/src/router/permission.js: -------------------------------------------------------------------------------- 1 | import store from '../store' 2 | import { getToken } from 'src/utils/auth' 3 | import { router } from './index' 4 | import NProgress from 'nprogress' // Progress 进度条 5 | import 'nprogress/nprogress.css' // Progress 进度条样式 6 | 7 | const whiteList = ['/login']; 8 | router.beforeEach((to, from, next) => { 9 | NProgress.start() 10 | 11 | if (getToken()) { //存在token 12 | if (to.path === '/login') { //当前页是登录直接跳过进入主页 13 | next('/') 14 | }else{ 15 | if (!store.state.user.roles) { // 不存在用户信息, 需要重新拉取 16 | store.dispatch('getUserInfo').then( res => { //拉取用户信息 17 | let roles = res.data.roles 18 | store.dispatch('setRoutes', {roles}).then( () => { //根据权限动态添加路由 19 | router.addRoutes(store.state.permission.addRouters) 20 | next({ ...to }) //hash模式 确保路由加载完成 21 | }); 22 | }) 23 | }else{ // 存在信息,直接跳转到该页面 24 | next() 25 | } 26 | } 27 | }else{ 28 | if (whiteList.indexOf(to.path) >= 0) { //是否在白名单内,不在的话直接跳转登录页 29 | next() 30 | }else{ 31 | next('/login') 32 | } 33 | 34 | } 35 | 36 | }) 37 | router.afterEach((to, from) => { 38 | document.title = to.name 39 | NProgress.done() 40 | }) 41 | 42 | export default router 43 | -------------------------------------------------------------------------------- /code/admin/src/store/modules/blog.js: -------------------------------------------------------------------------------- 1 | import axios from 'src/utils/fetch' 2 | import { blogTypes } from './classify' 3 | 4 | const music = { 5 | state: { 6 | blogTypes, 7 | list: [], 8 | total: 0 9 | }, 10 | mutations: { 11 | BLOGLIST (state, data) { 12 | state.list = data.data.list; 13 | state.total = data.data.total; 14 | } 15 | }, 16 | actions: { 17 | addBlog ({commit}, info) { 18 | return new Promise( (resolve, reject) => { 19 | axios.postFile('blog/add', info) 20 | .then( res => { 21 | resolve(res) 22 | }).catch( err => { 23 | reject(err) 24 | }) 25 | }) 26 | }, 27 | 28 | getBlogList ({commit}, params) { 29 | return new Promise( (resolve, reject) => { 30 | axios.get('blog/list', params) 31 | .then( res => { 32 | commit('BLOGLIST', res) 33 | resolve(res) 34 | }).catch( err => { 35 | reject(err) 36 | }) 37 | }) 38 | }, 39 | delBlog ({commit}, id) { 40 | return new Promise( (resolve, reject) => { 41 | axios.get('blog/del', {id: id}) 42 | .then( res => { 43 | resolve(res) 44 | }).catch( err => { 45 | reject(err) 46 | }) 47 | }) 48 | }, 49 | updateBlog ({commit}, info) { 50 | return new Promise( (resolve, reject) => { 51 | axios.postFile('blog/update', info) 52 | .then( res => { 53 | resolve(res) 54 | }).catch( err => { 55 | reject(err) 56 | }) 57 | }) 58 | } 59 | } 60 | } 61 | export default music -------------------------------------------------------------------------------- /code/admin/src/views/Layout/tabsView.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /code/client/src/components/Back/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | 27 | -------------------------------------------------------------------------------- /code/admin/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import { Local } from 'src/utils/storage' 2 | const app = { 3 | state: { 4 | slideBar: { 5 | opened: Local.get('slideBarStatus') 6 | }, 7 | tagViews: JSON.parse(Local.get('tagViews')) || [], 8 | is_add_router: false 9 | }, 10 | mutations: { 11 | TOGGLE_SIDEBAR(state) { 12 | if (state.slideBar.opened) { 13 | Local.set('slideBarStatus', false); 14 | } else { 15 | Local.set('slideBarStatus', true); 16 | } 17 | state.slideBar.opened = !state.slideBar.opened; 18 | }, 19 | ADD_TAGVIEW(state, tag) { 20 | if (state.tagViews.some( v => v.name === tag.name)) return; 21 | state.tagViews.push({name: tag.name, path: tag.path}); 22 | Local.set('tagViews',JSON.stringify(state.tagViews)); 23 | }, 24 | DEL_TAGVIEW(state, tag) { 25 | let index; 26 | for (let [i, v] of state.tagViews.entries()) { 27 | if (v.name === tag.name) index = i; 28 | } 29 | state.tagViews.splice(index, 1); 30 | Local.set('tagViews',JSON.stringify(state.tagViews)); 31 | } 32 | }, 33 | actions: { 34 | toggleSideBar({ commit }) { 35 | commit('TOGGLE_SIDEBAR'); 36 | }, 37 | addTagView({commit}, tag) { 38 | commit('ADD_TAGVIEW', tag); 39 | }, 40 | delTagView ({commit}, tag) { 41 | commit('DEL_TAGVIEW', tag) 42 | } 43 | } 44 | }; 45 | export default app 46 | -------------------------------------------------------------------------------- /code/client/src/utils/fetch.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import qs from 'qs' 3 | import { Message } from 'element-ui' 4 | 5 | 6 | axios.defaults.withCredentials = true 7 | 8 | // 发送时 9 | axios.interceptors.request.use(config => { 10 | return config 11 | }, err => { 12 | return Promise.reject(err) 13 | }) 14 | 15 | // 响应时 16 | axios.interceptors.response.use(response => response, err => Promise.resolve(err.response)) 17 | 18 | // 检查状态码 19 | function checkStatus(res) { 20 | if (res.status === 200 || res.status === 304) { 21 | return res.data 22 | } 23 | return { 24 | code: 0, 25 | msg: res.data.msg || res.statusText, 26 | data: res.statusText 27 | } 28 | return res 29 | } 30 | 31 | 32 | // 检查CODE值 33 | function checkCode(res) { 34 | if (res.code === 0) { 35 | Message({ 36 | message: res.msg, 37 | type: 'error', 38 | duration: 2 * 1000 39 | }) 40 | 41 | throw new Error(res.msg) 42 | } 43 | 44 | return res 45 | } 46 | 47 | const prefix = '/client_demo_api/' 48 | export default { 49 | get(url, params) { 50 | if (!url) return 51 | return axios({ 52 | method: 'get', 53 | url: prefix + url, 54 | params, 55 | timeout: 30000 56 | }).then(checkStatus).then(checkCode) 57 | }, 58 | post(url, data) { 59 | if (!url) return 60 | return axios({ 61 | method: 'post', 62 | url: prefix + url, 63 | data: qs.stringify(data), 64 | timeout: 30000 65 | }).then(checkStatus).then(checkCode) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /code/admin/src/views/Layout/levelbar.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 42 | 43 | -------------------------------------------------------------------------------- /code/admin/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | Vue.use(Router) 4 | 5 | const _import_ = file => () => import('views/' + file + '.vue') 6 | 7 | export const constantRouterMap = [ 8 | { path: '/login', name: '登录', component: _import_('Login/index'), hidden: true }, 9 | { 10 | path: '/', 11 | name: '首页', 12 | component: _import_('Layout/index'), 13 | redirect: '/home', 14 | icon: 'homel', 15 | children: [ 16 | { path: 'home', component: _import_('Home/index'), name: '首页' } 17 | ] 18 | } 19 | ] 20 | export const asyncRouterMap = [ 21 | { 22 | path: '/permission', 23 | name: '权限管理', 24 | meta: { role: ['admin'] }, 25 | component: _import_('Layout/index'), 26 | redirect: '/permission/list', 27 | requireAuth: true, // 是否需要登录 28 | dropdown: true, 29 | icon: 'authority', 30 | children: [ 31 | { path: 'list', component: _import_('Permission/list/index'), name: '管理员列表' }, 32 | { path: 'add', component: _import_('Permission/add/index'), name: '添加管理员' } 33 | ] 34 | }, 35 | { 36 | path: '/article', 37 | name: '文章', 38 | component: _import_('Layout/index'), 39 | redirect: '/article/list', 40 | dropdown: true, 41 | icon: 'zuowen', 42 | children: [ 43 | { path: 'list', component: _import_('Article/list/index'), name: '文章列表' }, 44 | { path: 'add', component: _import_('Article/add/index'), name: '添加文章' } 45 | ] 46 | } 47 | ] 48 | 49 | export const router = new Router({ 50 | // mode: 'history', 51 | routes: constantRouterMap 52 | }) 53 | 54 | -------------------------------------------------------------------------------- /code/server/middleware/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import bodyParser from 'koa-bodyparser' 3 | import staticFiles from 'koa-static' 4 | import Rule from './rule' 5 | import Send from './send' 6 | import Auth from './auth' 7 | import Log from './log' 8 | import Func from './func' 9 | 10 | export default app => { 11 | 12 | //缓存拦截器 13 | app.use(async (ctx, next) => { 14 | if (ctx.url == '/favicon.ico') return 15 | 16 | await next() 17 | ctx.status = 200 18 | ctx.set('Cache-Control', 'must-revalidation') 19 | if (ctx.fresh) { 20 | ctx.status = 304 21 | return 22 | } 23 | }) 24 | 25 | // 日志中间件 26 | app.use(Log()) 27 | 28 | // 数据返回的封装 29 | app.use(Send()) 30 | 31 | // 方法封装 32 | app.use(Func()) 33 | 34 | //权限中间件 35 | app.use(Auth()) 36 | 37 | //post请求中间件 38 | app.use(bodyParser()) 39 | 40 | //静态文件中间件 41 | app.use(staticFiles(path.resolve(__dirname, '../../../public'))); 42 | 43 | // 规则中间件 44 | Rule({ 45 | app, 46 | rules: [ 47 | { 48 | path: path.join(__dirname, '../controller/admin'), 49 | name: 'admin' 50 | }, 51 | { 52 | path: path.join(__dirname, '../controller/client'), 53 | name: 'client' 54 | } 55 | ] 56 | }) 57 | 58 | // 增加错误的监听处理 59 | app.on("error", (err, ctx) => { 60 | if (ctx && !ctx.headerSent && ctx.status < 500) { 61 | ctx.status = 500 62 | } 63 | if (ctx && ctx.log && ctx.log.error) { 64 | if (!ctx.state.logged) { 65 | ctx.log.error(err.stack) 66 | } 67 | } 68 | }) 69 | 70 | } -------------------------------------------------------------------------------- /code/admin/src/views/Layout/navBar.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 36 | 37 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /code/client/src/components/Tag/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 29 | -------------------------------------------------------------------------------- /code/admin/src/utils/fetch.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import qs from 'qs' 3 | import { Message } from 'element-ui' 4 | 5 | 6 | axios.defaults.withCredentials = true 7 | 8 | // 发送时 9 | axios.interceptors.request.use(config => { 10 | // 开始 11 | return config 12 | }, err => { 13 | return Promise.reject(err) 14 | }) 15 | 16 | // 响应时 17 | axios.interceptors.response.use(response => response, err => Promise.resolve(err.response)) 18 | 19 | // 检查状态码 20 | function checkStatus(res) { 21 | // 结束 22 | if (res.status === 200 || res.status === 304) { 23 | return res.data 24 | } 25 | return { 26 | code: 0, 27 | msg: res.data.msg || res.statusText, 28 | data: res.statusText 29 | } 30 | return res 31 | } 32 | 33 | 34 | // 检查CODE值 35 | function checkCode(res) { 36 | if (res.code === 0) { 37 | Message({ 38 | message: res.msg, 39 | type: 'error', 40 | duration: 2 * 1000 41 | }) 42 | 43 | throw new Error(res.msg) 44 | } 45 | 46 | return res 47 | } 48 | 49 | const prefix = '/admin_demo_api/' 50 | export default { 51 | get(url, params) { 52 | if (!url) return 53 | return axios({ 54 | method: 'get', 55 | url: prefix + url, 56 | params, 57 | timeout: 30000 58 | }).then(checkStatus).then(checkCode) 59 | }, 60 | post(url, data) { 61 | if (!url) return 62 | return axios({ 63 | method: 'post', 64 | url: prefix + url, 65 | data: qs.stringify(data), 66 | timeout: 30000 67 | }).then(checkStatus).then(checkCode) 68 | }, 69 | postFile(url, data) { 70 | if (!url) return 71 | return axios({ 72 | method: 'post', 73 | url: prefix + url, 74 | data 75 | }).then(checkStatus).then(checkCode) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /code/admin/src/views/Layout/account.vue: -------------------------------------------------------------------------------- 1 | 16 | 32 | -------------------------------------------------------------------------------- /code/admin/src/styles/less/element-ui.less: -------------------------------------------------------------------------------- 1 | @bg-color: #324157; 2 | @font-color: #bfcbd9; 3 | @font-hover-color: #48576a; 4 | @active-font-color: #409EFF; 5 | @submenu-color: #1f2d3d; 6 | .el-menu-vertical { 7 | background-color: @bg-color; 8 | .el-submenu__title { 9 | color: @font-color; 10 | background-color: @bg-color; 11 | } 12 | .el-submenu .el-menu { 13 | background-color: @submenu-color; 14 | .el-menu-item { 15 | background: transparent; 16 | } 17 | } 18 | .el-menu-item { 19 | color: @font-color; 20 | background: @bg-color; 21 | } 22 | .el-menu-item:hover, 23 | .el-submenu__title:hover { 24 | background-color: @font-hover-color; 25 | } 26 | .el-menu-item.is-active { 27 | color: @active-font-color; 28 | background-color: transparent; 29 | } 30 | } 31 | 32 | .music-cover-uploader { 33 | .el-upload { 34 | border: 1px dashed #d9d9d9; 35 | border-radius: 6px; 36 | cursor: pointer; 37 | position: relative; 38 | overflow: hidden; 39 | } 40 | .el-upload:hover { 41 | border-color: #409EFF; 42 | } 43 | .avatar-uploader-icon { 44 | font-size: 28px; 45 | color: #8c939d; 46 | width: 178px; 47 | height: 178px; 48 | line-height: 178px; 49 | text-align: center; 50 | } 51 | .avatar { 52 | width: 178px; 53 | height: 178px; 54 | display: block; 55 | } 56 | } 57 | .el-breadcrumb__inner, .el-breadcrumb__inner a { 58 | color: #48576a; 59 | font-weight: 400; 60 | } 61 | 62 | .el-breadcrumb__item:last-child .el-breadcrumb__inner, .el-breadcrumb__item:last-child .el-breadcrumb__inner a, .el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover, .el-breadcrumb__item:last-child .el-breadcrumb__inner:hover { 63 | color: #97a8be; 64 | cursor: text; 65 | font-weight: 400; 66 | } -------------------------------------------------------------------------------- /code/admin/src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { constantRouterMap, asyncRouterMap } from 'src/router' 2 | 3 | /** 4 | * 通过meta.role判断是否与当前用户权限匹配 5 | * @param role 6 | * @param route 7 | */ 8 | const hasPermission = (roles, route) => { 9 | if (route.meta && route.meta.role) { 10 | return roles.some(role => route.meta.role.indexOf(role) >= 0) 11 | } else { 12 | return true 13 | } 14 | } 15 | 16 | 17 | /** 18 | * 递归过滤异步路由表,返回符合用户角色权限的路由表 19 | * @param asyncRouterMap 20 | * @param role 21 | */ 22 | const filterAsyncRouter = (asyncRouterMap, roles) => { 23 | const accessedRouters = asyncRouterMap.filter(route => { 24 | if (hasPermission(roles, route)) { 25 | if (route.children && route.children.length) { 26 | route.children = filterAsyncRouter(route.children, roles) 27 | } 28 | return true 29 | } 30 | return false 31 | }) 32 | return accessedRouters 33 | } 34 | 35 | const permission = { 36 | state: { 37 | routes: constantRouterMap.concat(asyncRouterMap), 38 | addRouters: [] 39 | }, 40 | mutations: { 41 | SETROUTES(state, routers) { 42 | state.addRouters = routers; 43 | state.routes = constantRouterMap.concat(routers); 44 | } 45 | }, 46 | actions: { 47 | setRoutes({ commit }, info) { 48 | return new Promise( (resolve, reject) => { 49 | let {roles} = info; 50 | let accessedRouters = []; 51 | if (roles.indexOf('admin') >= 0) { 52 | accessedRouters = asyncRouterMap; 53 | }else{ 54 | accessedRouters = filterAsyncRouter(asyncRouterMap, roles) 55 | } 56 | 57 | commit('SETROUTES', accessedRouters) 58 | resolve() 59 | }) 60 | } 61 | 62 | } 63 | } 64 | export default permission -------------------------------------------------------------------------------- /code/admin/src/styles/less/btns.less: -------------------------------------------------------------------------------- 1 | @blue:#324157; 2 | @light-blue:#3A71A8; 3 | @red:#C03639; 4 | @pink: #E65D6E; 5 | @green: #30B08F; 6 | @tiffany: #4AB7BD; 7 | @yellow:#FEC171; 8 | @panGreen: #30B08F; 9 | 10 | .colorBtn(@color) { 11 | background: @color; 12 | &:hover { 13 | color: @color; 14 | &:before, 15 | &:after { 16 | background: @color; 17 | } 18 | } 19 | } 20 | 21 | .blue-btn { 22 | .colorBtn(@blue) 23 | } 24 | 25 | .light-blue-btn { 26 | .colorBtn(@light-blue) 27 | } 28 | 29 | .red-btn { 30 | .colorBtn(@red) 31 | } 32 | 33 | .pink-btn { 34 | .colorBtn(@pink) 35 | } 36 | 37 | .green-btn { 38 | .colorBtn(@green) 39 | } 40 | 41 | .tiffany-btn { 42 | .colorBtn(@tiffany) 43 | } 44 | 45 | .yellow-btn { 46 | .colorBtn(@yellow) 47 | } 48 | 49 | .pan-btn { 50 | font-size: 14px; 51 | color: #fff; 52 | padding: 14px 36px; 53 | border-radius: 8px; 54 | border: none; 55 | outline: none; 56 | margin-right: 25px; 57 | transition: 600ms ease all; 58 | position: relative; 59 | display: inline-block; 60 | &:hover { 61 | background: #fff; 62 | &:before, 63 | &:after { 64 | width: 100%; 65 | transition: 600ms ease all; 66 | } 67 | } 68 | &:before, 69 | &:after { 70 | content: ''; 71 | position: absolute; 72 | top: 0; 73 | right: 0; 74 | height: 2px; 75 | width: 0; 76 | transition: 400ms ease all; 77 | } 78 | &::after { 79 | right: inherit; 80 | top: inherit; 81 | left: 0; 82 | bottom: 0; 83 | } 84 | } 85 | 86 | .custom-button { 87 | display: inline-block; 88 | line-height: 1; 89 | white-space: nowrap; 90 | cursor: pointer; 91 | background: #fff; 92 | color: #fff; 93 | -webkit-appearance: none; 94 | text-align: center; 95 | box-sizing: border-box; 96 | outline: 0; 97 | margin: 0; 98 | padding: 10px 15px; 99 | font-size: 14px; 100 | border-radius: 4px; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /code/admin/src/views/Layout/slideBar.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 40 | 41 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | admin: { 3 | dev: { 4 | env: 'development', 5 | publicPath: '/', 6 | host: 'localhost', 7 | port: '8090', 8 | assetsSubDirectory: 'static', 9 | devtoolType: 'cheap-module-eval-source-map', 10 | proxyTable: { //proxy代理 11 | '/admin_demo_api': { 12 | target: 'http://localhost:3000/admin_demo_api/', 13 | changeOrigin: true, 14 | pathRewrite: { 15 | '^/admin_demo_api': '/' 16 | } 17 | } 18 | } 19 | }, 20 | build: { 21 | env: 'production', // 当前环境 22 | publicPath: '/admin/', // html引用资源路径 23 | assetsPath: 'static', // 静态资源目录 24 | assetsSubDirectory: 'static', // html资源存放目录 25 | devtoolType: 'source-map', // 代码位置信息 26 | productionGzip: false, //开启Gzip压缩 27 | productionGzipExtensions: ['js', 'css'] //Gzip压缩文件 28 | } 29 | }, 30 | 31 | client: { 32 | dev: { 33 | env: 'development', 34 | publicPath: '/', 35 | host: 'localhost', 36 | port: '8080', 37 | assetsSubDirectory: 'static', 38 | devtoolType: 'cheap-module-eval-source-map', 39 | proxyTable: { 40 | '/client_demo_api': { 41 | target: 'http://localhost:3000/client_demo_api/', 42 | changeOrigin: true, 43 | pathRewrite: { 44 | '^/client_demo_api': '/' 45 | } 46 | } 47 | } 48 | }, 49 | build: { 50 | env: 'production', 51 | publicPath: '/client/', 52 | assetsPath: 'static', 53 | assetsSubDirectory: 'static', 54 | devtoolType: 'source-map', 55 | productionGzip: false, 56 | productionGzipExtensions: ['js', 'css'] 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /code/admin/src/components/Github/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 24 | 25 | 49 | 50 | -------------------------------------------------------------------------------- /code/admin/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | 31 | 46 | -------------------------------------------------------------------------------- /code/client/src/components/Github/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 24 | 25 | 49 | 50 | -------------------------------------------------------------------------------- /code/client/src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 55 | -------------------------------------------------------------------------------- /code/client/src/style/css/animation.css: -------------------------------------------------------------------------------- 1 | @keyframes easeBarrageMove { 2 | 100% {left: -50%;} 3 | } 4 | 5 | @keyframes btnGroups { 6 | 0% {transform: scale(1.2, 0.8);} 7 | 1% {transform: scale(1.18, 0.82);} 8 | 2% {transform: scale(1.16, 0.84);} 9 | 3% {transform: scale(1.13, 0.87);} 10 | 4% {transform: scale(1.1, 0.9);} 11 | 5% {transform: scale(1.07, 0.93);} 12 | 6% {transform: scale(1.04, 0.96);} 13 | 7% {transform: scale(1.01, 0.99);} 14 | 8% {transform: scale(0.99, 1.01);} 15 | 9% {transform: scale(0.97, 1.03);} 16 | 10% {transform: scale(0.95, 1.05);} 17 | 11% {transform: scale(0.94, 1.06);} 18 | 12% {transform: scale(0.93, 1.07);} 19 | 13% {transform: scale(0.93, 1.07);} 20 | 14% {transform: scale(0.93, 1.07);} 21 | 15% {transform: scale(0.93, 1.07);} 22 | 16% {transform: scale(0.94, 1.06);} 23 | 17% {transform: scale(0.94, 1.06);} 24 | 18% {transform: scale(0.95, 1.05);} 25 | 19% {transform: scale(0.96, 1.04);} 26 | 20% {transform: scale(0.98, 1.02);} 27 | 21% {transform: scale(0.99, 1.01);} 28 | 22% {transform: scale(1, 1);} 29 | 23% {transform: scale(1, 1);} 30 | 24% {transform: scale(1.01, 0.99);} 31 | 25% {transform: scale(1.02, 0.98);} 32 | 26% {transform: scale(1.02, 0.98);} 33 | 27% {transform: scale(1.02, 0.98);} 34 | 28% {transform: scale(1.03, 0.97);} 35 | 29% {transform: scale(1.03, 0.97);} 36 | 30% {transform: scale(1.02, 0.98);} 37 | 31% {transform: scale(1.02, 0.98);} 38 | 32% {transform: scale(1.02, 0.98);} 39 | 33% {transform: scale(1.02, 0.98);} 40 | 34% {transform: scale(1.01, 0.99);} 41 | 35% {transform: scale(1.01, 0.99);} 42 | 36% {transform: scale(1.01, 0.99);} 43 | 37% {transform: scale(1, 1);} 44 | 38% {transform: scale(1, 1);} 45 | 39% {transform: scale(1, 1);} 46 | 40% {transform: scale(0.99, 1.01);} 47 | 41% {transform: scale(0.99, 1.01);} 48 | 42% {transform: scale(0.99, 1.01);} 49 | 43% {transform: scale(0.99, 1.01);} 50 | 44% {transform: scale(0.99, 1.01);} 51 | 45% {transform: scale(0.99, 1.01);} 52 | 46% {transform: scale(0.99, 1.01);} 53 | 47% {transform: scale(0.99, 1.01);} 54 | 48% {transform: scale(0.99, 1.01);} 55 | 49% {transform: scale(1, 1);} 56 | } -------------------------------------------------------------------------------- /code/admin/src/utils/storage.js: -------------------------------------------------------------------------------- 1 | const ls = window.localStorage; 2 | const ss = window.sessionStorage; 3 | 4 | export const Cookie = { 5 | get (key) { 6 | let arr = document.cookie.split('; ') 7 | for (let i = 0; i < arr.length; i++) { 8 | let arr2 = arr[i].trim().split('='); 9 | if (arr2[0] == key) { 10 | return arr2[1] 11 | } 12 | } 13 | return '' 14 | }, 15 | set (key, value, day) { 16 | let setting = arguments[0] 17 | if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object') { 18 | for (let i in setting) { 19 | let oDate = new Date() 20 | oDate.setDate(oDate.getDate() + day) 21 | document.cookie = i + '=' + setting[i] + ';expires=' + oDate 22 | } 23 | } else { 24 | let oDate = new Date() 25 | oDate.setDate(oDate.getDate() + day) 26 | document.cookie = key + '=' + value + ';expires=' + oDate 27 | } 28 | }, 29 | remove (key) { 30 | this.set(key, 1, -1) 31 | } 32 | }; 33 | 34 | 35 | export const Local = { 36 | get(key) { 37 | if (key) return JSON.parse(ls.getItem(key)) 38 | return null 39 | }, 40 | set(key, val) { 41 | const setting = arguments[0] 42 | if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object') { 43 | for (const i in setting) { 44 | ls.setItem(i, JSON.stringify(setting[i])) 45 | } 46 | } else { 47 | ls.setItem(key, JSON.stringify(val)) 48 | } 49 | }, 50 | remove(key) { 51 | ls.removeItem(key) 52 | }, 53 | clear() { 54 | ls.clear() 55 | } 56 | }; 57 | 58 | 59 | export const Session = { 60 | get(key) { 61 | if (key) return JSON.parse(ss.getItem(key)) 62 | return null 63 | }, 64 | set(key, val) { 65 | const setting = arguments[0] 66 | if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object') { 67 | for (const i in setting) { 68 | ss.setItem(i, JSON.stringify(setting[i])) 69 | } 70 | } else { 71 | ss.setItem(key, JSON.stringify(val)) 72 | } 73 | }, 74 | remove(key) { 75 | ss.removeItem(key) 76 | }, 77 | clear() { 78 | ss.clear() 79 | } 80 | } -------------------------------------------------------------------------------- /code/client/src/store/modules/blog.js: -------------------------------------------------------------------------------- 1 | import axios from '../../utils/fetch' 2 | import { blogTypes } from './classify' 3 | 4 | const blog = { 5 | state: { 6 | types: blogTypes, 7 | list: [], 8 | homeList: [], 9 | info: {}, 10 | currType: '', 11 | pagesize: 5, 12 | loadingMore: false, 13 | loadingBol: true 14 | }, 15 | mutations: { 16 | BLOGLIST (state, res) { 17 | state.list = res; 18 | }, 19 | BLOGINFO (state, res) { 20 | state.info = res.data; 21 | } 22 | }, 23 | actions: { 24 | // 获取博客列表 25 | async getBlogList ({commit, state}, params) { 26 | 27 | params.pagesize = params.pagesize || state.pagesize 28 | params.type = params.type === 'all' ? null:params.type 29 | state.loadingMore = true 30 | state.loadingBol = false 31 | return new Promise( (resolve, reject) => { 32 | axios.get('blog/list', params). 33 | then( res => { 34 | state.loadingMore = false; 35 | resolve(res) 36 | if (res.data.length <= 0 && params.pageindex > 1) return 37 | if (params.pageindex > 1) { 38 | commit('BLOGLIST', state.list.concat(res.data)) 39 | }else { 40 | commit('BLOGLIST', res.data) 41 | } 42 | if (res.data.length >= state.pagesize) { 43 | state.loadingBol = true; 44 | } 45 | }).catch( err => { 46 | // console.log(err) 47 | reject(err) 48 | }) 49 | }) 50 | }, 51 | 52 | // 获取博客详情 53 | getBlogInfo ({commit}, _id) { 54 | return new Promise( (resolve, reject) => { 55 | axios.get('blog/info', {_id}). 56 | then( res => { 57 | commit('BLOGINFO', res) 58 | resolve(res) 59 | }).catch( err => { 60 | // console.log(err) 61 | reject(err) 62 | }) 63 | }) 64 | } 65 | } 66 | } 67 | export default blog -------------------------------------------------------------------------------- /code/client/src/utils/storage.js: -------------------------------------------------------------------------------- 1 | const ls = window.localStorage; 2 | const ss = window.sessionStorage; 3 | 4 | export const Cookie = { 5 | get (key) { 6 | let arr = document.cookie.split('; ') 7 | for (let i = 0; i < arr.length; i++) { 8 | let arr2 = arr[i].trim().split('='); 9 | if (arr2[0] == key) { 10 | return arr2[1] 11 | } 12 | } 13 | return '' 14 | }, 15 | set (key, value, day) { 16 | let setting = arguments[0] 17 | if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object') { 18 | for (let i in setting) { 19 | let oDate = new Date() 20 | oDate.setDate(oDate.getDate() + day) 21 | document.cookie = i + '=' + setting[i] + ';expires=' + oDate 22 | } 23 | } else { 24 | let oDate = new Date() 25 | oDate.setDate(oDate.getDate() + day) 26 | document.cookie = key + '=' + value + ';expires=' + oDate 27 | } 28 | }, 29 | remove (key) { 30 | this.set(key, 1, -1) 31 | } 32 | }; 33 | 34 | 35 | export const Local = { 36 | get(key) { 37 | if (key) return JSON.parse(ls.getItem(key)) 38 | return null 39 | }, 40 | set(key, val) { 41 | const setting = arguments[0] 42 | if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object') { 43 | for (const i in setting) { 44 | ls.setItem(i, JSON.stringify(setting[i])) 45 | } 46 | } else { 47 | ls.setItem(key, JSON.stringify(val)) 48 | } 49 | }, 50 | remove(key) { 51 | ls.removeItem(key) 52 | }, 53 | clear() { 54 | ls.clear() 55 | } 56 | }; 57 | 58 | 59 | export const Session = { 60 | get(key) { 61 | if (key) return JSON.parse(ss.getItem(key)) 62 | return null 63 | }, 64 | set(key, val) { 65 | const setting = arguments[0] 66 | if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object') { 67 | for (const i in setting) { 68 | ss.setItem(i, JSON.stringify(setting[i])) 69 | } 70 | } else { 71 | ss.setItem(key, JSON.stringify(val)) 72 | } 73 | }, 74 | remove(key) { 75 | ss.removeItem(key) 76 | }, 77 | clear() { 78 | ss.clear() 79 | } 80 | } -------------------------------------------------------------------------------- /code/client/src/components/Loading/loading-3.vue: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /code/client/src/style/css/init.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: rgba(186,164,119,0.3); 3 | border-top: 1px solid transparent; 4 | margin-top: -1px; 5 | font-family: "Source Code Pro",Consolas,Menlo,Monaco,"Courier New",monospace; 6 | } 7 | button { 8 | border: none; 9 | background: none; 10 | outline: none; 11 | } 12 | input, textarea{ 13 | -webkit-appearance: none; 14 | } 15 | 16 | .icon { 17 | width: 1em; height: 1em; 18 | vertical-align: -0.15em; 19 | fill: currentColor; 20 | overflow: hidden; 21 | } 22 | 23 | ::-webkit-scrollbar { 24 | width: 4px; 25 | height: 4px; 26 | } 27 | 28 | ::-webkit-scrollbar-thumb { 29 | opacity: .8; 30 | background: #c1866a; 31 | border-radius: 4px; 32 | transition: all .5s; 33 | } 34 | 35 | 36 | /* nprogress styles */ 37 | /* Make clicks pass-through */ 38 | 39 | #nprogress { 40 | pointer-events: none; 41 | } 42 | #nprogress .bar { 43 | /* background: #29d; */ 44 | background: #c1866a; 45 | position: fixed; 46 | z-index: 1031; 47 | top: 0; 48 | left: 0; 49 | width: 100%; 50 | height: 2px; 51 | } 52 | 53 | 54 | /* Fancy blur effect */ 55 | 56 | #nprogress .peg { 57 | display: block; 58 | position: absolute; 59 | right: 0px; 60 | width: 100px; 61 | height: 100%; 62 | box-shadow: 0 0 10px #c1866a, 0 0 5px #c1866a; 63 | opacity: 1.0; 64 | transform: rotate(3deg) translate(0px, -4px); 65 | } 66 | 67 | 68 | /* Remove these to get rid of the spinner */ 69 | 70 | #nprogress .spinner { 71 | display: block; 72 | position: fixed; 73 | z-index: 1031; 74 | top: 15px; 75 | right: 15px; 76 | } 77 | 78 | #nprogress .spinner-icon { 79 | width: 18px; 80 | height: 18px; 81 | box-sizing: border-box; 82 | border: solid 2px transparent; 83 | border-top-color: #c1866a; 84 | border-left-color: #c1866a; 85 | border-radius: 50%; 86 | animation: nprogress-spinner 400ms linear infinite; 87 | } 88 | 89 | .nprogress-custom-parent { 90 | overflow: hidden; 91 | position: relative; 92 | } 93 | 94 | .nprogress-custom-parent #nprogress .spinner, 95 | .nprogress-custom-parent #nprogress .bar { 96 | position: absolute; 97 | } 98 | 99 | @keyframes nprogress-spinner { 100 | 0% { 101 | transform: rotate(0deg); 102 | } 103 | 100% { 104 | transform: rotate(360deg); 105 | } 106 | } -------------------------------------------------------------------------------- /code/server/middleware/log/log.js: -------------------------------------------------------------------------------- 1 | import log4js from 'log4js' 2 | import access from './access' // 引入日志输出信息的封装文件 3 | import config from '../../config' 4 | const methods = ["trace", "debug", "info", "warn", "error", "fatal", "mark"]; 5 | 6 | // 提取默认公用参数对象 7 | const baseInfo = config.log 8 | export default (options = {}) => { 9 | let contextLogger = {}, //错误日志等级对象,最后会赋值给ctx上,用于打印各种日志 10 | appenders = {}, //日志配置 11 | opts = Object.assign({}, baseInfo, options), //系统配置 12 | { 13 | logLevel, 14 | dir, 15 | ip, 16 | projectName 17 | } = opts, 18 | commonInfo = { 19 | projectName, 20 | ip 21 | }; //存储公用的日志信息 22 | 23 | //指定要记录的日志分类 24 | appenders.all = { 25 | type: 'dateFile', //日志文件类型,可以使用日期作为文件名的占位符 26 | filename: `${dir}/all/`, //日志文件名,可以设置相对路径或绝对路径 27 | pattern: 'task-yyyy-MM-dd.log', //占位符,紧跟在filename后面 28 | alwaysIncludePattern: true //是否总是有后缀名 29 | } 30 | 31 | // 环境变量为dev local development 认为是开发环境 32 | if (config.env === "dev" || config.env === "local" || config.env === "development") { 33 | appenders.out = { 34 | type: "console" 35 | } 36 | } 37 | 38 | let logConfig = { 39 | appenders, 40 | 41 | /** 42 | * 指定日志的默认配置项 43 | * 如果 log4js.getLogger 中没有指定,默认为 cheese 日志的配置项 44 | */ 45 | categories: { 46 | default: { 47 | appenders: Object.keys(appenders), 48 | level: logLevel 49 | } 50 | } 51 | } 52 | 53 | let logger = log4js.getLogger('cheese'); 54 | return async (ctx, next) => { 55 | const start = Date.now() // 记录请求开始的时间 56 | 57 | // 循环methods将所有方法挂载到ctx 上 58 | methods.forEach((method, i) => { 59 | contextLogger[method] = message => { 60 | logConfig.appenders.cheese = { 61 | type: 'dateFile', //日志文件类型,可以使用日期作为文件名的占位符 62 | filename: `${dir}/${method}/`, 63 | pattern: `${method}-yyyy-MM-dd.log`, 64 | alwaysIncludePattern: true //是否总是有后缀名 65 | } 66 | log4js.configure(logConfig) 67 | logger[method](access(ctx, message, commonInfo)) 68 | } 69 | }) 70 | ctx.log = contextLogger 71 | await next() 72 | // 记录完成的时间 作差 计算响应时间 73 | const responseTime = Date.now() - start 74 | 75 | ctx.log.info(access(ctx, { 76 | responseTime: `响应时间为${responseTime/1000}s` 77 | }, commonInfo)) 78 | 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /code/admin/src/styles/css/function.css: -------------------------------------------------------------------------------- 1 | /* 禁止选中文本 */ 2 | .usn{ 3 | -webkit-user-select:none; 4 | -moz-user-select:none; 5 | -ms-user-select:none; 6 | -o-user-select:none; 7 | user-select:none; 8 | } 9 | /* 浮动 */ 10 | .fl { 11 | float: left; 12 | } 13 | .fr { 14 | float: right; 15 | } 16 | .cf { 17 | zoom: 1; 18 | } 19 | .cf:after { 20 | content:"."; 21 | display:block; 22 | clear:both; 23 | visibility:hidden; 24 | height:0; 25 | overflow:hidden; 26 | } 27 | 28 | /* 元素类型 */ 29 | .db { 30 | display: block; 31 | } 32 | .dn { 33 | display: none; 34 | } 35 | .di { 36 | display:inline-block; 37 | *display:inline; 38 | *zoom:1; 39 | } 40 | 41 | /* 溢出样式 */ 42 | .ofh { 43 | overflow: hidden; 44 | } 45 | .ofs { 46 | overflow: scroll; 47 | } 48 | .ofa { 49 | overflow: auto; 50 | } 51 | .ofv { 52 | overflow: visible; 53 | } 54 | 55 | /* 定位方式 */ 56 | .ps { 57 | position: static; 58 | } 59 | .pr { 60 | position: relative; 61 | zoom:1; 62 | } 63 | .pb { 64 | position: absolute; 65 | } 66 | .pf { 67 | position: fixed; 68 | } 69 | 70 | 71 | /* 文本对其方式 */ 72 | .tc { 73 | text-align: center; 74 | } 75 | .tl { 76 | text-align: left; 77 | } 78 | .tj { 79 | text-align: justify; 80 | text-justify: inter-ideograph; 81 | } 82 | /* 垂直对齐方式 */ 83 | .vt { 84 | vertical-align: top; 85 | } 86 | .vm { 87 | vertical-align: middle; 88 | } 89 | .vb { 90 | vertical-align: bottom; 91 | } 92 | 93 | 94 | .wn { /* 强制不换行 */ 95 | word-wrap:normal; 96 | white-space:nowrap; 97 | } 98 | .wb { /* 强制换行 */ 99 | white-space:normal;word-wrap:break-word;word-break:break-all; 100 | } 101 | .wp { /* 保持空白序列*/ 102 | overflow:hidden;text-align:left;white-space:pre-wrap;word-wrap:break-word;word-break:break-all; 103 | } 104 | 105 | 106 | .tdu, .tdu:hover { /* 下划线 */ 107 | text-decoration:underline; 108 | } 109 | 110 | .tdn, .tdn:hover{ /* 去掉下划线样式 */ 111 | text-decoration:none; 112 | } 113 | 114 | .wes { /* 多出部分用省略号表示 , 用于一行 */ 115 | overflow:hidden; 116 | word-wrap:normal; 117 | white-space:nowrap; 118 | text-overflow:ellipsis; 119 | } 120 | .wes-2 { /* 适用于webkit内核和移动端 */ 121 | display: -webkit-box; 122 | -webkit-box-orient: vertical; 123 | -webkit-line-clamp: 2; 124 | overflow: hidden; 125 | } 126 | .wes-3 { 127 | display: -webkit-box; 128 | -webkit-box-orient: vertical; 129 | -webkit-line-clamp: 3; 130 | overflow: hidden; 131 | } 132 | .wes-4 { 133 | display: -webkit-box; 134 | -webkit-box-orient: vertical; 135 | -webkit-line-clamp: 4; 136 | overflow: hidden; 137 | } 138 | 139 | /* 鼠标样式 */ 140 | .csd { 141 | cursor: default; 142 | } 143 | .csp { 144 | cursor: pointer; 145 | } 146 | .csh { 147 | cursor: help; 148 | } 149 | .csm { 150 | cursor: move; 151 | } 152 | 153 | 154 | .dfc { 155 | display: flex; 156 | align-items: center; 157 | justify-content: center; 158 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev:admin": "cross-env NODE_ENV_TYPE=admin webpack-dev-server --config build/webpack.dev.conf.js", 8 | "dev:client": "set NODE_ENV_TYPE=client && webpack-dev-server --config build/webpack.dev.conf.js", 9 | "build:admin": "cross-env NODE_ENV_TYPE=admin node build/build.js", 10 | "build:client": "cross-env NODE_ENV_TYPE=client node build/build.js", 11 | "analyz:admin": "cross-env analyz_npm_config_report=true npm run build:admin", 12 | "analyz:client": "cross-env analyz_npm_config_report=true npm run build:client", 13 | "server": "cross-env NODE_ENV=development nodemon code/server/index.js", 14 | "start": "pm2 start code/server/index.js", 15 | "stop": "pm2 stop code/server/index.js", 16 | "restart": "pm2 restart code/server/index.js" 17 | }, 18 | "author": "", 19 | "license": "ISC", 20 | "devDependencies": { 21 | "babel-core": "^6.26.0", 22 | "babel-loader": "^7.1.2", 23 | "babel-plugin-transform-runtime": "^6.23.0", 24 | "babel-polyfill": "^6.26.0", 25 | "babel-preset-env": "^1.6.1", 26 | "babel-preset-es2015": "^6.24.1", 27 | "babel-preset-stage-2": "^6.24.1", 28 | "babel-register": "^6.26.0", 29 | "chalk": "^2.3.0", 30 | "compression-webpack-plugin": "^1.1.3", 31 | "copy-webpack-plugin": "^4.3.1", 32 | "cross-env": "^5.1.3", 33 | "css-loader": "^0.28.8", 34 | "extract-text-webpack-plugin": "^3.0.2", 35 | "file-loader": "^1.1.6", 36 | "friendly-errors-webpack-plugin": "^1.6.1", 37 | "html-webpack-plugin": "^2.30.1", 38 | "less": "^2.7.3", 39 | "less-loader": "^4.0.5", 40 | "node-notifier": "^5.1.2", 41 | "nodemon": "^1.12.1", 42 | "optimize-css-assets-webpack-plugin": "^3.2.0", 43 | "ora": "^1.3.0", 44 | "postcss-loader": "^2.0.10", 45 | "rimraf": "^2.6.2", 46 | "url-loader": "^0.6.2", 47 | "vue-loader": "^13.7.0", 48 | "vue-style-loader": "^3.0.3", 49 | "vue-template-compiler": "^2.5.13", 50 | "webpack": "^3.10.0", 51 | "webpack-bundle-analyzer": "^2.9.2", 52 | "webpack-dev-server": "^2.9.7", 53 | "webpack-merge": "^4.1.1" 54 | }, 55 | "dependencies": { 56 | "axios": "^0.17.0", 57 | "babel-polyfill": "^6.26.0", 58 | "busboy": "^0.2.14", 59 | "element-ui": "^2.0.11", 60 | "highlight.js": "^9.12.0", 61 | "ip": "^1.1.5", 62 | "js-md5": "^0.7.3", 63 | "jsonwebtoken": "^8.1.0", 64 | "koa": "^2.4.1", 65 | "koa-bodyparser": "^4.2.0", 66 | "koa-router": "^7.3.0", 67 | "koa-static": "^4.0.2", 68 | "log4js": "^2.4.1", 69 | "marked": "^0.3.12", 70 | "mongoose": "^4.13.9", 71 | "nprogress": "^0.2.0", 72 | "qs": "^6.5.1", 73 | "vue": "^2.5.13", 74 | "vue-router": "^3.0.1", 75 | "vuex": "^3.0.1" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /code/client/src/style/css/function.css: -------------------------------------------------------------------------------- 1 | /* 禁止选中文本 */ 2 | .usn{ 3 | -webkit-user-select:none; 4 | -moz-user-select:none; 5 | -ms-user-select:none; 6 | -o-user-select:none; 7 | user-select:none; 8 | } 9 | /* 浮动 */ 10 | .fl { 11 | float: left; 12 | } 13 | .fr { 14 | float: right; 15 | } 16 | .cf { 17 | zoom: 1; 18 | } 19 | .cf:after { 20 | content:"."; 21 | display:block; 22 | clear:both; 23 | visibility:hidden; 24 | height:0; 25 | overflow:hidden; 26 | } 27 | 28 | /* 元素类型 */ 29 | .db { 30 | display: block; 31 | } 32 | .dn { 33 | display: none; 34 | } 35 | .di { 36 | display:inline-block; 37 | *display:inline; 38 | *zoom:1; 39 | } 40 | 41 | /* 溢出样式 */ 42 | .ofh { 43 | overflow: hidden; 44 | } 45 | .ofs { 46 | overflow: scroll; 47 | } 48 | .ofa { 49 | overflow: auto; 50 | } 51 | .ofv { 52 | overflow: visible; 53 | } 54 | 55 | /* 定位方式 */ 56 | .ps { 57 | position: static; 58 | } 59 | .pr { 60 | position: relative; 61 | zoom:1; 62 | } 63 | .pb { 64 | position: absolute; 65 | } 66 | .pf { 67 | position: fixed; 68 | } 69 | 70 | 71 | /* 文本对其方式 */ 72 | .tc { 73 | text-align: center; 74 | } 75 | .tl { 76 | text-align: left; 77 | } 78 | .tj { 79 | text-align: justify; 80 | text-justify: inter-ideograph; 81 | } 82 | /* 垂直对齐方式 */ 83 | .vt { 84 | vertical-align: top; 85 | } 86 | .vm { 87 | vertical-align: middle; 88 | } 89 | .vb { 90 | vertical-align: bottom; 91 | } 92 | 93 | 94 | .wn { /* 强制不换行 */ 95 | word-wrap:normal; 96 | white-space:nowrap; 97 | } 98 | .wb { /* 强制换行 */ 99 | white-space:normal;word-wrap:break-word;word-break:break-all; 100 | } 101 | .wp { /* 保持空白序列*/ 102 | overflow:hidden;text-align:left;white-space:pre-wrap;word-wrap:break-word;word-break:break-all; 103 | } 104 | 105 | 106 | .tdu, .tdu:hover { /* 下划线 */ 107 | text-decoration:underline; 108 | } 109 | 110 | .tdn, .tdn:hover{ /* 去掉下划线样式 */ 111 | text-decoration:none; 112 | } 113 | 114 | .wes { /* 多出部分用省略号表示 , 用于一行 */ 115 | overflow:hidden; 116 | word-wrap:normal; 117 | white-space:nowrap; 118 | text-overflow:ellipsis; 119 | } 120 | .wes-2 { /* 适用于webkit内核和移动端 */ 121 | display: -webkit-box; 122 | -webkit-box-orient: vertical; 123 | -webkit-line-clamp: 2; 124 | overflow: hidden; 125 | } 126 | .wes-3 { 127 | display: -webkit-box; 128 | -webkit-box-orient: vertical; 129 | -webkit-line-clamp: 3; 130 | overflow: hidden; 131 | } 132 | .wes-4 { 133 | display: -webkit-box; 134 | -webkit-box-orient: vertical; 135 | -webkit-line-clamp: 4; 136 | overflow: hidden; 137 | } 138 | 139 | /* 鼠标样式 */ 140 | .csd { 141 | cursor: default; 142 | } 143 | .csp { 144 | cursor: pointer; 145 | } 146 | .csh { 147 | cursor: help; 148 | } 149 | .csm { 150 | cursor: move; 151 | } 152 | 153 | /* 垂直居中 */ 154 | .df-center { 155 | display:flex; 156 | align-items: center; 157 | justify-content:center; 158 | } 159 | .df-between { 160 | display:flex; 161 | align-items: center; 162 | justify-content: space-between; 163 | } -------------------------------------------------------------------------------- /code/server/controller/admin/blog.js: -------------------------------------------------------------------------------- 1 | import blogModel from '../../models/blog' 2 | import path from 'path' 3 | import marked from 'marked' 4 | 5 | marked.setOptions({ 6 | renderer: new marked.Renderer(), 7 | renderer: new marked.Renderer(), 8 | gfm: true, //允许 Git Hub标准的markdown. 9 | tables: true, //允许支持表格语法。该选项要求 gfm 为true。 10 | breaks: true, //允许回车换行。该选项要求 gfm 为true。 11 | pedantic: false, //尽可能地兼容 markdown.pl的晦涩部分。不纠正原始模型任何的不良行为和错误。 12 | sanitize: true, //对输出进行过滤(清理),将忽略任何已经输入的html代码(标签) 13 | smartLists: true, //使用比原生markdown更时髦的列表。 旧的列表将可能被作为pedantic的处理内容过滤掉. 14 | smartypants: false, //使用更为时髦的标点,比如在引用语法中加入破折号。 15 | highlight: function (code) { 16 | return require('highlight.js').highlightAuto(code).value; 17 | } 18 | }) 19 | 20 | module.exports = { 21 | async list (ctx, next) { 22 | console.log('----------------获取博客列表 blog/list-----------------------'); 23 | let { keyword, pageindex = 1, pagesize = 10} = ctx.request.query; 24 | console.log('keyword:'+keyword+','+'pageindex:'+pageindex +','+ 'pagesize:'+pagesize) 25 | try { 26 | 27 | let reg = new RegExp(keyword, 'i') 28 | let data = await ctx.findPage(blogModel, { 29 | $or: [ 30 | {type: { $regex: reg}}, 31 | {title: { $regex: reg}} 32 | ] 33 | }, {createTime: 0, html: 0}, {limit: pagesize*1, skip: (pageindex-1)*pagesize}); 34 | ctx.send(data) 35 | }catch (e){ 36 | console.log(e) 37 | ctx.sendError(e) 38 | } 39 | 40 | }, 41 | 42 | async add (ctx, next) { 43 | console.log('----------------添加博客 blog/add-----------------------'); 44 | let paramsData = ctx.request.body; 45 | try { 46 | let data = await ctx.findOne(blogModel, {title: paramsData.title}) 47 | if (data) { 48 | ctx.sendError('数据已经存在, 请重新添加!') 49 | }else{ 50 | 51 | 52 | paramsData.html = marked(paramsData.html); 53 | let data = await ctx.add(blogModel, paramsData); 54 | ctx.send(paramsData) 55 | } 56 | }catch(e) { 57 | ctx.sendError(e) 58 | } 59 | }, 60 | 61 | async update (ctx, next) { 62 | console.log('----------------更新博客 blog/update-----------------------'); 63 | let paramsData = ctx.request.body; 64 | try { 65 | paramsData.html = marked(paramsData.html); 66 | let data = await ctx.update(blogModel, {_id: paramsData._id}, paramsData) 67 | ctx.send() 68 | }catch(e) { 69 | if (e === '暂无数据') { 70 | ctx.sendError(e) 71 | } 72 | } 73 | }, 74 | 75 | async del (ctx, next) { 76 | console.log('----------------删除博客 blog/del-----------------------'); 77 | let id = ctx.request.query.id 78 | try { 79 | ctx.remove(blogModel, {_id: id}) 80 | ctx.send() 81 | }catch(e){ 82 | ctx.sendError(e) 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /code/admin/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import axios from 'src/utils/fetch' 2 | import {getToken} from 'src/utils/auth' 3 | import md5 from 'js-md5' 4 | import axios1 from 'axios' 5 | 6 | const user = { 7 | state: { 8 | list: [], 9 | userTotal: 0, 10 | name: '', 11 | username: '', 12 | roles: null, 13 | token: getToken(), 14 | otherList: [] 15 | }, 16 | mutations: { 17 | SET_TOKEN (state, token) { 18 | state.token = token; 19 | }, 20 | SET_USERINFO (state, info) { 21 | state.name = info.name; 22 | state.username = info.username; 23 | state.roles = info.roles; 24 | }, 25 | USERLIST (state, data) { 26 | state.list = data.list 27 | state.total = data.total; 28 | }, 29 | GET_INFOLIST (state, data) { 30 | state.otherList = data; 31 | }, 32 | CLEARINFO (state) { 33 | state.name = ''; 34 | state.username = ''; 35 | state.roles = null; 36 | } 37 | }, 38 | actions: { 39 | clearInfo ({commit}) { 40 | commit('CLEARINFO') 41 | }, 42 | userLogin ({state, commit}, info) { 43 | let {username, pwd} = info; 44 | return new Promise( (resolve, reject) => { 45 | axios.post('user/login',{ 46 | username: username, 47 | pwd: md5(pwd) 48 | }).then( res => { 49 | // console.log(res) 50 | state.token = getToken(); 51 | resolve(res) 52 | }).catch( err => { 53 | // console.log(err) 54 | reject(err) 55 | }) 56 | }) 57 | }, 58 | getUserInfo ({state, commit}) { 59 | return new Promise( (resolve, reject) => { 60 | axios.get('user/info',{ 61 | token: state.token 62 | }).then( res => { 63 | console.log(res) 64 | commit('SET_USERINFO', res.data) 65 | resolve(res) 66 | }).catch( err => { 67 | // console.log(err) 68 | reject(err) 69 | }) 70 | }) 71 | }, 72 | getUserList ({commit}, params) { 73 | return new Promise( (resolve, reject) => { 74 | axios.get('user/list', params).then( res => { 75 | console.log(res) 76 | commit('USERLIST', res.data) 77 | resolve(res) 78 | }).catch( err => { 79 | // console.log(err) 80 | reject(err) 81 | }) 82 | }) 83 | }, 84 | addUser ({commit}, info) { 85 | info.pwd = md5(info.pwd) 86 | return new Promise( (resolve, reject) => { 87 | axios.post('user/add', info) 88 | .then( res => { 89 | resolve(res) 90 | }).catch( err => { 91 | reject(err) 92 | }) 93 | }) 94 | }, 95 | delUser ({commit}, id) { 96 | return new Promise( (resolve, reject) => { 97 | axios.get('user/del', {id: id}) 98 | .then( res => { 99 | resolve(res) 100 | }).catch( err => { 101 | reject(err) 102 | }) 103 | }) 104 | }, 105 | updateUser ({commit}, info) { 106 | info.pwd = md5(info.pwd) 107 | info.old_pwd = md5(info.old_pwd) 108 | return new Promise( (resolve, reject) => { 109 | axios.post('user/update', info) 110 | .then( res => { 111 | resolve(res) 112 | }).catch( err => { 113 | reject(err) 114 | }) 115 | }) 116 | } 117 | // getAddOtherInfo () { 118 | // return new Promise( (resolve, reject) => { 119 | // axios.post('admin_apis/info/add').then( res => { 120 | // // console.log(res) 121 | // resolve(res) 122 | // }).catch( err => { 123 | // // console.log(err) 124 | // reject(err) 125 | // }) 126 | // }) 127 | // }, 128 | // getOtherInfoList ({commit}) { 129 | // return new Promise( (resolve, reject) => { 130 | // axios.get('admin_apis/info/list').then( res => { 131 | // // console.log(res) 132 | // commit('GET_INFOLIST', res.data) 133 | // resolve(res) 134 | // }).catch( err => { 135 | // // console.log(err) 136 | // reject(err) 137 | // }) 138 | // }) 139 | // } 140 | } 141 | } 142 | 143 | export default user -------------------------------------------------------------------------------- /code/admin/src/views/Login/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 65 | 66 | -------------------------------------------------------------------------------- /code/admin/src/views/Permission/add/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /code/client/src/views/Article/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 61 | 62 | 63 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /code/server/controller/admin/user.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | import conf from '../../config' 3 | import userModel from '../../models/user' 4 | module.exports = { 5 | async login (ctx, next) { 6 | console.log('----------------登录接口 user/login-----------------------'); 7 | let {username, pwd} = ctx.request.body; 8 | console.log(username) 9 | try { 10 | let data = await ctx.findOne(userModel, {username: username}); 11 | console.log(data) 12 | if (!data) { 13 | return ctx.sendError('用户名不存在!'); 14 | } 15 | if (pwd !== data.pwd) { 16 | return ctx.sendError('密码错误,请重新输入!'); 17 | } 18 | await ctx.update(userModel, {_id: data._id}, {$set:{loginTime: new Date()}}) //更新登陆时间 19 | 20 | let payload = { 21 | _id: data._id, 22 | username: data.username, 23 | name: data.name, 24 | roles: data.roles 25 | } 26 | let token = jwt.sign(payload, conf.auth.admin_secret, {expiresIn: '24h'}) //token签名 有效期为24小时 27 | ctx.cookies.set(conf.auth.tokenKey, token, { 28 | httpOnly: false, // 是否只用于http请求中获取 29 | }); 30 | console.log('登陆成功') 31 | ctx.send({message: '登录成功'}); 32 | } catch (e) { 33 | if (e === '暂无数据') { 34 | console.log('用户名不存在') 35 | return ctx.sendError('用户名不存在'); 36 | } 37 | ctx.throw(e); 38 | ctx.sendError(e) 39 | } 40 | 41 | }, 42 | async info (ctx, next) { 43 | console.log('----------------获取用户信息接口 user/getUserInfo-----------------------'); 44 | let token = ctx.request.query.token; 45 | try { 46 | let tokenInfo = jwt.verify(token, conf.auth.admin_secret); 47 | console.log(tokenInfo) 48 | ctx.send({ 49 | username: tokenInfo.username, 50 | name: tokenInfo.name, 51 | _id: tokenInfo._id, 52 | roles: tokenInfo.roles 53 | }) 54 | }catch (e) { 55 | if ('TokenExpiredError' === e.name) { 56 | ctx.sendError('鉴权失败, 请重新登录!'); 57 | ctx.throw(401, 'token expired,请及时本地保存数据!'); 58 | } 59 | ctx.throw(401, 'invalid token'); 60 | ctx.sendError('系统异常!'); 61 | } 62 | 63 | }, 64 | 65 | async list (ctx, next) { 66 | console.log('----------------获取用户信息列表接口 user/getUserList-----------------------'); 67 | let { keyword, pageindex = 1, pagesize = 10} = ctx.request.query; 68 | console.log('keyword:'+keyword+','+'pageindex:'+pageindex +','+ 'pagesize:'+pagesize) 69 | 70 | try { 71 | let reg = new RegExp(keyword, 'i') 72 | let data = await ctx.findPage(userModel, { 73 | $or: [ 74 | {name: { $regex: reg}}, 75 | {username: { $regex: reg}} 76 | ] 77 | }, {pwd: 0}, {limit: pagesize*1, skip: (pageindex-1)*pagesize}); 78 | 79 | ctx.send(data) 80 | }catch (e){ 81 | console.log(e) 82 | ctx.sendError(e) 83 | } 84 | 85 | }, 86 | 87 | async add (ctx, next) { 88 | console.log('----------------添加管理员 user/add-----------------------'); 89 | let paramsData = ctx.request.body; 90 | try { 91 | let data = await ctx.findOne(userModel, {name: paramsData.name}) 92 | if (data) { 93 | ctx.sendError('数据已经存在, 请重新添加!') 94 | }else{ 95 | let data = await ctx.add(userModel, paramsData); 96 | ctx.send(paramsData) 97 | } 98 | }catch(e) { 99 | ctx.sendError(e) 100 | } 101 | }, 102 | 103 | async update (ctx, next) { 104 | console.log('----------------更新管理员 user/update-----------------------'); 105 | let paramsData = ctx.request.body; 106 | console.log(paramsData) 107 | try { 108 | let data = await ctx.findOne(userModel, {name: paramsData.name}) 109 | if (paramsData.old_pwd !== data.pwd) { 110 | return ctx.sendError('密码不匹配!') 111 | } 112 | delete paramsData.old_pwd 113 | await ctx.update(userModel, {_id: paramsData._id}, paramsData) 114 | ctx.send() 115 | }catch(e) { 116 | if (e === '暂无数据') { 117 | ctx.sendError(e) 118 | } 119 | } 120 | }, 121 | 122 | async del (ctx, next) { 123 | console.log('----------------删除管理员 user/del-----------------------'); 124 | let id = ctx.request.query.id 125 | try { 126 | ctx.remove(userModel, {_id: id}) 127 | ctx.send() 128 | }catch(e){ 129 | ctx.sendError(e) 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /code/admin/src/views/Permission/edit/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /code/client/src/components/Markdown/markdown.css: -------------------------------------------------------------------------------- 1 | .fmt { 2 | line-height: 1.6; 3 | word-wrap: break-word 4 | } 5 | .fmt a{ 6 | color: #009a61; 7 | text-decoration: none; 8 | } 9 | .fmt h1 { 10 | font-size: 2.25em 11 | } 12 | 13 | .fmt h2 { 14 | font-size: 1.75em 15 | } 16 | 17 | .fmt h3 { 18 | font-size: 1.5em 19 | } 20 | 21 | .fmt h4 { 22 | font-size: 1.25em 23 | } 24 | 25 | .fmt h5 { 26 | font-size: 1em 27 | } 28 | 29 | .fmt h6 { 30 | font-size: 0.86em 31 | } 32 | 33 | .fmt h4,.fmt h5,.fmt p,.fmt ul,.fmt ol,.fmt pre,.fmt blockquote,.fmt table { 34 | margin: 1.5em 0 35 | } 36 | 37 | .fmt h1,.fmt h2,.fmt h3 { 38 | margin: 1.5em 0 0 39 | } 40 | 41 | .fmt h1+.widget-codetool+pre,.fmt h2+.widget-codetool+pre,.fmt h3+.widget-codetool+pre { 42 | margin-top: 1.5em !important 43 | } 44 | 45 | .fmt h1,.fmt h2 { 46 | border-bottom: 1px solid #eee; 47 | padding-bottom: 10px 48 | } 49 | 50 | .fmt>h1:first-child,.fmt h2:first-child,.fmt h3:first-child,.fmt h4:first-child,.fmt p:first-child,.fmt ul:first-child,.fmt ol:first-child,.fmt pre:first-child,.fmt blockquote:first-child { 51 | margin-top: 0 52 | } 53 | 54 | .fmt ul,.fmt ol { 55 | margin-left: 3em; 56 | padding-left: 0 57 | } 58 | 59 | .fmt ul li,.fmt ol li { 60 | margin: .3em 0 61 | } 62 | 63 | .fmt ul ul,.fmt ul ol,.fmt ol ul,.fmt ol ol { 64 | margin-top: 0; 65 | margin-bottom: 0 66 | } 67 | 68 | .fmt ul p,.fmt ol p { 69 | margin: 0 70 | } 71 | 72 | .fmt p:last-child { 73 | margin-bottom: 0 74 | } 75 | 76 | .fmt p>p:empty,.fmt div>p:empty,.fmt p>div:empty,.fmt div>div:empty,.fmt div>br:only-child,.fmt p+br,.fmt img+br { 77 | display: none 78 | } 79 | 80 | .fmt img,.fmt video,.fmt audio { 81 | position: static !important; 82 | max-width: 100% 83 | } 84 | 85 | .fmt img { 86 | padding: 3px; 87 | border: 1px solid #ddd 88 | } 89 | 90 | .fmt img.emoji { 91 | padding: 0; 92 | border: none 93 | } 94 | 95 | .fmt blockquote { 96 | border-left: 2px solid #009A61; 97 | background: #F6F6F6; 98 | color: #555; 99 | font-size: 1em 100 | } 101 | 102 | .fmt pre,.fmt code { 103 | font-size: 0.93em 104 | } 105 | 106 | .fmt pre { 107 | padding: 1em; 108 | border: none; 109 | overflow: auto; 110 | line-height: 1.45; 111 | max-height: 35em; 112 | position: relative; 113 | background: url('./blueprint.png') #F6F6F6; 114 | -moz-background-size: 30px,30px; 115 | -o-background-size: 30px,30px; 116 | -webkit-background-size: 30px,30px; 117 | background-size: 30px,30px 118 | } 119 | 120 | .fmt pre code { 121 | background: none; 122 | font-size: 1em; 123 | overflow-wrap: normal; 124 | white-space: inherit 125 | } 126 | 127 | .fmt hr { 128 | margin: 1.5em auto; 129 | border-top: 2px dotted #eee 130 | } 131 | 132 | .fmt kbd { 133 | margin: 0 4px; 134 | padding: 3px 4px; 135 | background: #eee; 136 | color: #555 137 | } 138 | 139 | .fmt .x-scroll { 140 | overflow-x: auto 141 | } 142 | 143 | .fmt table { 144 | width: 100% 145 | } 146 | 147 | .fmt table th,.fmt table td { 148 | border: 1px solid #E6E6E6; 149 | padding: 5px 8px; 150 | word-break: normal 151 | } 152 | 153 | .fmt table th { 154 | background: #F3F3F3 155 | } 156 | 157 | .fmt a:not(.btn) { 158 | border-bottom: 1px solid rgba(0,154,97,0.25); 159 | padding-bottom: 1px 160 | } 161 | 162 | .fmt a:not(.btn):hover { 163 | border-bottom: 1px solid #009a61; 164 | text-decoration: none 165 | } 166 | 167 | /* 168 | 169 | github.com style (c) Vasily Polovnyov 170 | 171 | */ 172 | 173 | .hljs { 174 | display: block; 175 | overflow-x: auto; 176 | padding: 0.5em; 177 | color: #333; 178 | background: #f8f8f8; 179 | } 180 | 181 | .hljs-comment, 182 | .hljs-quote { 183 | color: #998; 184 | font-style: italic; 185 | } 186 | 187 | .hljs-keyword, 188 | .hljs-selector-tag, 189 | .hljs-subst { 190 | color: #333; 191 | font-weight: bold; 192 | } 193 | 194 | .hljs-number, 195 | .hljs-literal, 196 | .hljs-variable, 197 | .hljs-template-variable, 198 | .hljs-tag .hljs-attr { 199 | color: #008080; 200 | } 201 | 202 | .hljs-string, 203 | .hljs-doctag { 204 | color: #d14; 205 | } 206 | 207 | .hljs-title, 208 | .hljs-section, 209 | .hljs-selector-id { 210 | color: #900; 211 | font-weight: bold; 212 | } 213 | 214 | .hljs-subst { 215 | font-weight: normal; 216 | } 217 | 218 | .hljs-type, 219 | .hljs-class .hljs-title { 220 | color: #458; 221 | font-weight: bold; 222 | } 223 | 224 | .hljs-tag, 225 | .hljs-name, 226 | .hljs-attribute { 227 | color: #000080; 228 | font-weight: normal; 229 | } 230 | 231 | .hljs-regexp, 232 | .hljs-link { 233 | color: #009926; 234 | } 235 | 236 | .hljs-symbol, 237 | .hljs-bullet { 238 | color: #990073; 239 | } 240 | 241 | .hljs-built_in, 242 | .hljs-builtin-name { 243 | color: #0086b3; 244 | } 245 | 246 | .hljs-meta { 247 | color: #999; 248 | font-weight: bold; 249 | } 250 | 251 | .hljs-deletion { 252 | background: #fdd; 253 | } 254 | 255 | .hljs-addition { 256 | background: #dfd; 257 | } 258 | 259 | .hljs-emphasis { 260 | font-style: italic; 261 | } 262 | 263 | .hljs-strong { 264 | font-weight: bold; 265 | } 266 | -------------------------------------------------------------------------------- /code/client/src/style/css/markdown.css: -------------------------------------------------------------------------------- 1 | .fmt { 2 | line-height: 1.6; 3 | word-wrap: break-word 4 | } 5 | .fmt a{ 6 | color: #009a61; 7 | text-decoration: none; 8 | } 9 | .fmt h1 { 10 | font-size: 2.25em 11 | } 12 | 13 | .fmt h2 { 14 | font-size: 1.75em 15 | } 16 | 17 | .fmt h3 { 18 | font-size: 1.5em 19 | } 20 | 21 | .fmt h4 { 22 | font-size: 1.25em 23 | } 24 | 25 | .fmt h5 { 26 | font-size: 1em 27 | } 28 | 29 | .fmt h6 { 30 | font-size: 0.86em 31 | } 32 | 33 | .fmt h4,.fmt h5,.fmt p,.fmt ul,.fmt ol,.fmt pre,.fmt blockquote,.fmt table { 34 | margin: 1.5em 0 35 | } 36 | 37 | .fmt h1,.fmt h2,.fmt h3 { 38 | margin: 1.5em 0 0 39 | } 40 | 41 | .fmt h1+.widget-codetool+pre,.fmt h2+.widget-codetool+pre,.fmt h3+.widget-codetool+pre { 42 | margin-top: 1.5em !important 43 | } 44 | 45 | .fmt h1,.fmt h2 { 46 | border-bottom: 1px solid #eee; 47 | padding-bottom: 10px 48 | } 49 | 50 | .fmt>h1:first-child,.fmt h2:first-child,.fmt h3:first-child,.fmt h4:first-child,.fmt p:first-child,.fmt ul:first-child,.fmt ol:first-child,.fmt pre:first-child,.fmt blockquote:first-child { 51 | margin-top: 0 52 | } 53 | 54 | .fmt ul,.fmt ol { 55 | margin-left: 3em; 56 | padding-left: 0 57 | } 58 | 59 | .fmt ul li,.fmt ol li { 60 | margin: .3em 0 61 | } 62 | 63 | .fmt ul ul,.fmt ul ol,.fmt ol ul,.fmt ol ol { 64 | margin-top: 0; 65 | margin-bottom: 0 66 | } 67 | 68 | .fmt ul p,.fmt ol p { 69 | margin: 0 70 | } 71 | 72 | .fmt p:last-child { 73 | margin-bottom: 0 74 | } 75 | 76 | .fmt p>p:empty,.fmt div>p:empty,.fmt p>div:empty,.fmt div>div:empty,.fmt div>br:only-child,.fmt p+br,.fmt img+br { 77 | display: none 78 | } 79 | 80 | .fmt img,.fmt video,.fmt audio { 81 | position: static !important; 82 | max-width: 100% 83 | } 84 | 85 | .fmt img { 86 | padding: 3px; 87 | border: 1px solid #ddd 88 | } 89 | 90 | .fmt img.emoji { 91 | padding: 0; 92 | border: none 93 | } 94 | 95 | .fmt blockquote { 96 | border-left: 2px solid #009A61; 97 | background: #F6F6F6; 98 | color: #555; 99 | font-size: 1em 100 | } 101 | 102 | .fmt pre,.fmt code { 103 | font-size: 0.93em 104 | } 105 | 106 | .fmt pre { 107 | padding: 1em; 108 | border: none; 109 | overflow: auto; 110 | line-height: 1.45; 111 | max-height: 35em; 112 | position: relative; 113 | background: url('../../images/blueprint.png') #F6F6F6; 114 | -moz-background-size: 30px,30px; 115 | -o-background-size: 30px,30px; 116 | -webkit-background-size: 30px,30px; 117 | background-size: 30px,30px 118 | } 119 | 120 | .fmt pre code { 121 | background: none; 122 | font-size: 1em; 123 | overflow-wrap: normal; 124 | white-space: inherit 125 | } 126 | 127 | .fmt hr { 128 | margin: 1.5em auto; 129 | border-top: 2px dotted #eee 130 | } 131 | 132 | .fmt kbd { 133 | margin: 0 4px; 134 | padding: 3px 4px; 135 | background: #eee; 136 | color: #555 137 | } 138 | 139 | .fmt .x-scroll { 140 | overflow-x: auto 141 | } 142 | 143 | .fmt table { 144 | width: 100% 145 | } 146 | 147 | .fmt table th,.fmt table td { 148 | border: 1px solid #E6E6E6; 149 | padding: 5px 8px; 150 | word-break: normal 151 | } 152 | 153 | .fmt table th { 154 | background: #F3F3F3 155 | } 156 | 157 | .fmt a:not(.btn) { 158 | border-bottom: 1px solid rgba(0,154,97,0.25); 159 | padding-bottom: 1px 160 | } 161 | 162 | .fmt a:not(.btn):hover { 163 | border-bottom: 1px solid #009a61; 164 | text-decoration: none 165 | } 166 | 167 | /* 168 | 169 | github.com style (c) Vasily Polovnyov 170 | 171 | */ 172 | 173 | .hljs { 174 | display: block; 175 | overflow-x: auto; 176 | padding: 0.5em; 177 | color: #333; 178 | background: #f8f8f8; 179 | } 180 | 181 | .hljs-comment, 182 | .hljs-quote { 183 | color: #998; 184 | font-style: italic; 185 | } 186 | 187 | .hljs-keyword, 188 | .hljs-selector-tag, 189 | .hljs-subst { 190 | color: #333; 191 | font-weight: bold; 192 | } 193 | 194 | .hljs-number, 195 | .hljs-literal, 196 | .hljs-variable, 197 | .hljs-template-variable, 198 | .hljs-tag .hljs-attr { 199 | color: #008080; 200 | } 201 | 202 | .hljs-string, 203 | .hljs-doctag { 204 | color: #d14; 205 | } 206 | 207 | .hljs-title, 208 | .hljs-section, 209 | .hljs-selector-id { 210 | color: #900; 211 | font-weight: bold; 212 | } 213 | 214 | .hljs-subst { 215 | font-weight: normal; 216 | } 217 | 218 | .hljs-type, 219 | .hljs-class .hljs-title { 220 | color: #458; 221 | font-weight: bold; 222 | } 223 | 224 | .hljs-tag, 225 | .hljs-name, 226 | .hljs-attribute { 227 | color: #000080; 228 | font-weight: normal; 229 | } 230 | 231 | .hljs-regexp, 232 | .hljs-link { 233 | color: #009926; 234 | } 235 | 236 | .hljs-symbol, 237 | .hljs-bullet { 238 | color: #990073; 239 | } 240 | 241 | .hljs-built_in, 242 | .hljs-builtin-name { 243 | color: #0086b3; 244 | } 245 | 246 | .hljs-meta { 247 | color: #999; 248 | font-weight: bold; 249 | } 250 | 251 | .hljs-deletion { 252 | background: #fdd; 253 | } 254 | 255 | .hljs-addition { 256 | background: #dfd; 257 | } 258 | 259 | .hljs-emphasis { 260 | font-style: italic; 261 | } 262 | 263 | .hljs-strong { 264 | font-weight: bold; 265 | } 266 | -------------------------------------------------------------------------------- /code/admin/src/views/Article/edit/index.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 48 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /code/admin/src/components/Markdown/markdown.css: -------------------------------------------------------------------------------- 1 | .fmt { 2 | line-height: 1.6; 3 | word-wrap: break-word 4 | } 5 | .fmt a{ 6 | color: #009a61; 7 | text-decoration: none; 8 | } 9 | .fmt h1 { 10 | font-size: 2.25em 11 | } 12 | 13 | .fmt h2 { 14 | font-size: 1.75em 15 | } 16 | 17 | .fmt h3 { 18 | font-size: 1.5em 19 | } 20 | 21 | .fmt h4 { 22 | font-size: 1.25em 23 | } 24 | 25 | .fmt h5 { 26 | font-size: 1em 27 | } 28 | 29 | .fmt h6 { 30 | font-size: 0.86em 31 | } 32 | 33 | .fmt h4,.fmt h5,.fmt p,.fmt ul,.fmt ol,.fmt pre,.fmt blockquote,.fmt table { 34 | margin: 1.5em 0 35 | } 36 | 37 | .fmt h1,.fmt h2,.fmt h3 { 38 | margin: 1.5em 0 0 39 | } 40 | 41 | .fmt h1+.widget-codetool+pre,.fmt h2+.widget-codetool+pre,.fmt h3+.widget-codetool+pre { 42 | margin-top: 1.5em !important 43 | } 44 | 45 | .fmt h1,.fmt h2 { 46 | border-bottom: 1px solid #eee; 47 | padding-bottom: 10px 48 | } 49 | 50 | .fmt>h1:first-child,.fmt h2:first-child,.fmt h3:first-child,.fmt h4:first-child,.fmt p:first-child,.fmt ul:first-child,.fmt ol:first-child,.fmt pre:first-child,.fmt blockquote:first-child { 51 | margin-top: 0 52 | } 53 | 54 | .fmt ul,.fmt ol { 55 | margin-left: 3em; 56 | padding-left: 0 57 | } 58 | 59 | .fmt ul li,.fmt ol li { 60 | margin: .3em 0; 61 | list-style: disc 62 | } 63 | 64 | .fmt ul ul,.fmt ul ol,.fmt ol ul,.fmt ol ol { 65 | margin-top: 0; 66 | margin-bottom: 0 67 | } 68 | 69 | .fmt ul p,.fmt ol p { 70 | margin: 0 71 | } 72 | 73 | .fmt p:last-child { 74 | margin-bottom: 0 75 | } 76 | 77 | .fmt p>p:empty,.fmt div>p:empty,.fmt p>div:empty,.fmt div>div:empty,.fmt div>br:only-child,.fmt p+br,.fmt img+br { 78 | display: none 79 | } 80 | 81 | .fmt img,.fmt video,.fmt audio { 82 | position: static !important; 83 | max-width: 100% 84 | } 85 | 86 | .fmt img { 87 | padding: 3px; 88 | border: 1px solid #ddd 89 | } 90 | 91 | .fmt img.emoji { 92 | padding: 0; 93 | border: none 94 | } 95 | 96 | .fmt blockquote { 97 | border-left: 2px solid #009A61; 98 | background: #F6F6F6; 99 | color: #555; 100 | font-size: 1em; 101 | padding: 10px 20px 102 | } 103 | 104 | .fmt pre,.fmt code { 105 | font-size: 0.93em 106 | } 107 | 108 | .fmt pre { 109 | padding: 1em; 110 | border: none; 111 | overflow: auto; 112 | line-height: 1.45; 113 | max-height: 35em; 114 | position: relative; 115 | background: url('./blueprint.png') #F6F6F6; 116 | -moz-background-size: 30px,30px; 117 | -o-background-size: 30px,30px; 118 | -webkit-background-size: 30px,30px; 119 | background-size: 30px,30px 120 | } 121 | 122 | .fmt pre code { 123 | background: none; 124 | font-size: 1em; 125 | overflow-wrap: normal; 126 | white-space: inherit 127 | } 128 | 129 | .fmt hr { 130 | margin: 1.5em auto; 131 | border-top: 2px dotted #eee 132 | } 133 | 134 | .fmt kbd { 135 | margin: 0 4px; 136 | padding: 3px 4px; 137 | background: #eee; 138 | color: #555 139 | } 140 | 141 | .fmt .x-scroll { 142 | overflow-x: auto 143 | } 144 | 145 | .fmt table { 146 | width: 100% 147 | } 148 | 149 | .fmt table th,.fmt table td { 150 | border: 1px solid #E6E6E6; 151 | padding: 5px 8px; 152 | word-break: normal 153 | } 154 | 155 | .fmt table th { 156 | background: #F3F3F3 157 | } 158 | 159 | .fmt a:not(.btn) { 160 | border-bottom: 1px solid rgba(0,154,97,0.25); 161 | padding-bottom: 1px 162 | } 163 | 164 | .fmt a:not(.btn):hover { 165 | border-bottom: 1px solid #009a61; 166 | text-decoration: none 167 | } 168 | 169 | /* 170 | 171 | github.com style (c) Vasily Polovnyov 172 | 173 | */ 174 | 175 | .hljs { 176 | display: block; 177 | overflow-x: auto; 178 | padding: 0.5em; 179 | color: #333; 180 | background: #f8f8f8; 181 | } 182 | 183 | .hljs-comment, 184 | .hljs-quote { 185 | color: #998; 186 | font-style: italic; 187 | } 188 | 189 | .hljs-keyword, 190 | .hljs-selector-tag, 191 | .hljs-subst { 192 | color: #333; 193 | font-weight: bold; 194 | } 195 | 196 | .hljs-number, 197 | .hljs-literal, 198 | .hljs-variable, 199 | .hljs-template-variable, 200 | .hljs-tag .hljs-attr { 201 | color: #008080; 202 | } 203 | 204 | .hljs-string, 205 | .hljs-doctag { 206 | color: #d14; 207 | } 208 | 209 | .hljs-title, 210 | .hljs-section, 211 | .hljs-selector-id { 212 | color: #900; 213 | font-weight: bold; 214 | } 215 | 216 | .hljs-subst { 217 | font-weight: normal; 218 | } 219 | 220 | .hljs-type, 221 | .hljs-class .hljs-title { 222 | color: #458; 223 | font-weight: bold; 224 | } 225 | 226 | .hljs-tag, 227 | .hljs-name, 228 | .hljs-attribute { 229 | color: #000080; 230 | font-weight: normal; 231 | } 232 | 233 | .hljs-regexp, 234 | .hljs-link { 235 | color: #009926; 236 | } 237 | 238 | .hljs-symbol, 239 | .hljs-bullet { 240 | color: #990073; 241 | } 242 | 243 | .hljs-built_in, 244 | .hljs-builtin-name { 245 | color: #0086b3; 246 | } 247 | 248 | .hljs-meta { 249 | color: #999; 250 | font-weight: bold; 251 | } 252 | 253 | .hljs-deletion { 254 | background: #fdd; 255 | } 256 | 257 | .hljs-addition { 258 | background: #dfd; 259 | } 260 | 261 | .hljs-emphasis { 262 | font-style: italic; 263 | } 264 | 265 | .hljs-strong { 266 | font-weight: bold; 267 | } 268 | -------------------------------------------------------------------------------- /code/server/middleware/func/db.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 公共Add方法 3 | * @param model 要操作数据库的模型 4 | * @param conditions 增加的条件,如{id:xxx} 5 | */ 6 | export const add = (model, conditions) => { 7 | return new Promise((resolve, reject) => { 8 | model.create(conditions, (err, res) => { 9 | if (err) { 10 | console.error('Error: ' + JSON.stringify(err)); 11 | reject(err); 12 | return false; 13 | } 14 | console.log('save success!') 15 | resolve(res); 16 | }) 17 | }) 18 | } 19 | 20 | 21 | 22 | /* 23 | * 公共update方法 24 | * @param model 要操作数据库的模型 25 | * @param conditions 增加的条件,如{id:xxx} 26 | * @param update 更新条件{set{id:xxx}} 27 | * @param options 28 | */ 29 | export const update = (model, conditions, update, options) => { 30 | return new Promise((resolve, reject) => { 31 | model.update(conditions, update, options, (err, res) => { 32 | if (err) { 33 | console.error('Error: ' + JSON.stringify(err)); 34 | reject(err); 35 | return false; 36 | } 37 | if (res.n != 0) { 38 | console.log('update success!'); 39 | } else { 40 | console.log('update fail:no this data!'); 41 | } 42 | resolve(res); 43 | }) 44 | }) 45 | } 46 | 47 | 48 | /** 49 | * 公共remove方法 50 | * @param model 51 | * @param conditions 52 | */ 53 | 54 | export const remove = (model, conditions) => { 55 | return new Promise((resolve, reject) => { 56 | model.remove(conditions, function (err, res) { 57 | if (err) { 58 | console.error('Error: ' + JSON.stringify(err)); 59 | reject(err); 60 | return false; 61 | } else { 62 | if (res.result.n != 0) { 63 | console.log('remove success!'); 64 | } else { 65 | console.log('remove fail:no this data!'); 66 | } 67 | reject(res); 68 | } 69 | }); 70 | }) 71 | } 72 | 73 | /** 74 | * 公共find方法 非关联查找 75 | * @param model 76 | * @param conditions 77 | * @param fields 查找时限定的条件,如顺序,某些字段不查找等 78 | * @param options 79 | * @param callback 80 | */ 81 | export const find = (model, conditions, fields, options = {}) => { 82 | var sort = options.sort == undefined ? { 83 | _id: -1 84 | } : options.sort; 85 | delete options.sort; 86 | 87 | return new Promise((resolve, reject) => { 88 | model.find(conditions, fields, options, function (err, res) { 89 | if (err) { 90 | console.error('Error: ' + JSON.stringify(err)); 91 | reject(err); 92 | return false; 93 | } else { 94 | if (res.length != 0) { 95 | console.log('find success!'); 96 | } else { 97 | console.log('find fail:no this data!'); 98 | } 99 | resolve(res) 100 | } 101 | }).sort(sort); 102 | 103 | }) 104 | } 105 | 106 | 107 | /** 108 | * 公共findOne方法 非关联查找 109 | * @param model 110 | * @param conditions 111 | * @param fields 查找时限定的条件,如顺序,某些字段不查找等 112 | * @param options 113 | * @param callback 114 | */ 115 | export const findOne = (model, conditions, fields, options = {}) => { 116 | var sort = options.sort == undefined ? { 117 | _id: -1 118 | } : options.sort; 119 | delete options.sort; 120 | return new Promise((resolve, reject) => { 121 | model.findOne(conditions, fields, options, function (err, res) { 122 | if (err) { 123 | console.error('Error: ' + JSON.stringify(err)); 124 | reject(err); 125 | return false; 126 | } else { 127 | if (res) { 128 | console.log('find success!'); 129 | } else { 130 | console.log('find fail:no this data!'); 131 | } 132 | resolve(res); 133 | } 134 | }).sort(sort); 135 | }) 136 | } 137 | 138 | 139 | export const findPage = async (model, conditions, fields, options = {}) => { 140 | var sort = options.sort == undefined ? { 141 | _id: -1 142 | } : options.sort; 143 | delete options.sort; 144 | 145 | const getCount = () => { 146 | return new Promise((resolve, reject) => { 147 | model.find(conditions, fields).count({}, (err, res) => { 148 | if (err) { 149 | console.log('查询长度错误') 150 | return reject(err); 151 | } 152 | 153 | resolve(res) 154 | }) 155 | }) 156 | } 157 | 158 | const count = await getCount() 159 | 160 | return new Promise((resolve, reject) => { 161 | model.find(conditions, fields, options, function (err, res) { 162 | if (err) { 163 | console.error('Error: ' + JSON.stringify(err)); 164 | reject(err); 165 | return false; 166 | } else { 167 | if (res.length != 0) { 168 | console.log('find success!'); 169 | resolve({ 170 | list: res, 171 | total: count 172 | }) 173 | } else { 174 | console.log('find fail:no this data!'); 175 | resolve({ 176 | list: res, 177 | total: count 178 | }) 179 | } 180 | } 181 | }) 182 | 183 | }) 184 | } 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /code/admin/src/styles/css/reset.css: -------------------------------------------------------------------------------- 1 | /* normalize.css */ 2 | html { 3 | line-height: 1.15; /* 1 */ 4 | -ms-text-size-adjust: 100%; /* 2 */ 5 | -webkit-text-size-adjust: 100%; /* 2 */ 6 | } 7 | 8 | body { 9 | margin: 0; 10 | } 11 | 12 | 13 | article, 14 | aside, 15 | footer, 16 | header, 17 | nav, 18 | section { 19 | display: block; 20 | } 21 | 22 | h1 { 23 | font-size: 2em; 24 | margin: 0.67em 0; 25 | } 26 | 27 | figcaption, 28 | figure, 29 | main { /* 1 */ 30 | display: block; 31 | } 32 | 33 | figure { 34 | margin: 1em 40px; 35 | } 36 | 37 | hr { 38 | box-sizing: content-box; /* 1 */ 39 | height: 0; /* 1 */ 40 | overflow: visible; /* 2 */ 41 | } 42 | 43 | pre { 44 | font-family: monospace, monospace; /* 1 */ 45 | font-size: 1em; /* 2 */ 46 | } 47 | 48 | a { 49 | background-color: transparent; /* 1 */ 50 | -webkit-text-decoration-skip: objects; /* 2 */ 51 | } 52 | 53 | 54 | abbr[title] { 55 | border-bottom: none; /* 1 */ 56 | text-decoration: underline; /* 2 */ 57 | text-decoration: underline dotted; /* 2 */ 58 | } 59 | 60 | b, 61 | strong { 62 | font-weight: inherit; 63 | } 64 | 65 | b, 66 | strong { 67 | font-weight: bolder; 68 | } 69 | 70 | code, 71 | kbd, 72 | samp { 73 | font-family: monospace, monospace; /* 1 */ 74 | font-size: 1em; /* 2 */ 75 | } 76 | 77 | dfn { 78 | font-style: italic; 79 | } 80 | 81 | mark { 82 | background-color: #ff0; 83 | color: #000; 84 | } 85 | 86 | 87 | small { 88 | font-size: 80%; 89 | } 90 | 91 | sub, 92 | sup { 93 | font-size: 75%; 94 | line-height: 0; 95 | position: relative; 96 | vertical-align: baseline; 97 | } 98 | 99 | sub { 100 | bottom: -0.25em; 101 | } 102 | 103 | sup { 104 | top: -0.5em; 105 | } 106 | 107 | 108 | audio, 109 | video { 110 | display: inline-block; 111 | } 112 | audio:not([controls]) { 113 | display: none; 114 | height: 0; 115 | } 116 | 117 | img { 118 | border-style: none; 119 | } 120 | 121 | svg:not(:root) { 122 | overflow: hidden; 123 | } 124 | 125 | button, 126 | input, 127 | optgroup, 128 | select, 129 | textarea { 130 | font-family: sans-serif; /* 1 */ 131 | font-size: 100%; /* 1 */ 132 | line-height: 1.15; /* 1 */ 133 | margin: 0; /* 2 */ 134 | } 135 | button, 136 | input { /* 1 */ 137 | overflow: visible; 138 | } 139 | button, 140 | select { /* 1 */ 141 | text-transform: none; 142 | } 143 | 144 | button, 145 | html [type="button"], /* 1 */ 146 | [type="reset"], 147 | [type="submit"] { 148 | -webkit-appearance: button; /* 2 */ 149 | } 150 | 151 | button::-moz-focus-inner, 152 | [type="button"]::-moz-focus-inner, 153 | [type="reset"]::-moz-focus-inner, 154 | [type="submit"]::-moz-focus-inner { 155 | border-style: none; 156 | padding: 0; 157 | } 158 | 159 | button:-moz-focusring, 160 | [type="button"]:-moz-focusring, 161 | [type="reset"]:-moz-focusring, 162 | [type="submit"]:-moz-focusring { 163 | outline: 1px dotted ButtonText; 164 | } 165 | 166 | fieldset { 167 | padding: 0.35em 0.75em 0.625em; 168 | } 169 | 170 | legend { 171 | box-sizing: border-box; /* 1 */ 172 | color: inherit; /* 2 */ 173 | display: table; /* 1 */ 174 | max-width: 100%; /* 1 */ 175 | padding: 0; /* 3 */ 176 | white-space: normal; /* 1 */ 177 | } 178 | 179 | 180 | progress { 181 | display: inline-block; /* 1 */ 182 | vertical-align: baseline; /* 2 */ 183 | } 184 | 185 | textarea { 186 | overflow: auto; 187 | } 188 | 189 | [type="checkbox"], 190 | [type="radio"] { 191 | box-sizing: border-box; /* 1 */ 192 | padding: 0; /* 2 */ 193 | } 194 | 195 | 196 | [type="number"]::-webkit-inner-spin-button, 197 | [type="number"]::-webkit-outer-spin-button { 198 | height: auto; 199 | } 200 | 201 | [type="search"] { 202 | -webkit-appearance: textfield; /* 1 */ 203 | outline-offset: -2px; /* 2 */ 204 | } 205 | 206 | 207 | [type="search"]::-webkit-search-cancel-button, 208 | [type="search"]::-webkit-search-decoration { 209 | -webkit-appearance: none; 210 | } 211 | 212 | 213 | ::-webkit-file-upload-button { 214 | -webkit-appearance: button; /* 1 */ 215 | font: inherit; /* 2 */ 216 | } 217 | 218 | details, /* 1 */ 219 | menu { 220 | display: block; 221 | } 222 | 223 | 224 | summary { 225 | display: list-item; 226 | } 227 | 228 | canvas { 229 | display: inline-block; 230 | } 231 | 232 | template { 233 | display: none; 234 | } 235 | 236 | [hidden] { 237 | display: none; 238 | } 239 | 240 | 241 | /* reset */ 242 | *{ 243 | box-sizing: border-box; 244 | } 245 | html, body{/* 禁止选中文本 */ 246 | -webkit-user-select: none; 247 | user-select: none; 248 | font: Oswald, 'Open Sans', Helvetica, Arial, sans-serif 249 | } 250 | /* 禁止长按链接与图片弹出菜单 */ 251 | a, img { 252 | -webkit-touch-callout: none; 253 | } 254 | 255 | /*ios android去除自带阴影的样式*/ 256 | a, input{ 257 | -webkit-tap-highlight-color:rgba(0,0,0,0); 258 | } 259 | 260 | input[type="text"] { 261 | -webkit-appearance: none; 262 | } 263 | 264 | 265 | html,body,h1,h2,h3,h4,h5,h6,div,dl,dt,dd,ul,ol,li,p,blockquote,pre,hr,figure,table,caption,th,td,form,fieldset,legend,input,button,textarea,menu{margin:0;padding:0;} 266 | header,footer,section,article,aside,nav,hgroup,address,figure,figcaption,menu,details{display:block;} 267 | table{border-collapse:collapse;border-spacing:0;} 268 | caption,th{text-align:left;font-weight:normal;} 269 | html,body,fieldset,img,iframe,abbr{border:0;} 270 | i,cite,em,var,address,dfn{font-style:normal;} 271 | [hidefocus],summary{outline:0;} 272 | li{list-style:none;} 273 | h1,h2,h3,h4,h5,h6,small{font-size:100%;} 274 | sup,sub{font-size:83%;} 275 | pre,code,kbd,samp{font-family:inherit;} 276 | q:before,q:after{content:none;} 277 | textarea{overflow:auto;resize:none;} 278 | label,summary{cursor:default;} 279 | a,button{cursor:pointer;} 280 | h1,h2,h3,h4,h5,h6,em,strong,b{font-weight:bold;} 281 | del,ins,u,s,a,a:hover{text-decoration:none;} -------------------------------------------------------------------------------- /code/admin/src/views/Article/add/index.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /code/server/middleware/func/file.js: -------------------------------------------------------------------------------- 1 | import Busboy from 'busboy' 2 | import fs from 'fs' 3 | import path from 'path' 4 | import conf from '../../config' 5 | 6 | //检测文件并创建文件 7 | const mkdirSync = dirname => { 8 | if (fs.existsSync(dirname)) { 9 | return true 10 | } else { 11 | if (mkdirSync(path.dirname(dirname))) { 12 | fs.mkdirSync(dirname) 13 | return true 14 | } 15 | } 16 | } 17 | 18 | export const uploadFile = (ctx, opts) => { 19 | //重命名 20 | function rename (fileName) { 21 | return Math.random().toString(16).substr(2) + '.' + fileName.split('.').pop() 22 | } 23 | 24 | console.log(ctx.req.headers) 25 | let busboy = new Busboy({headers: ctx.req.headers}); 26 | console.log('start uploading...') 27 | /* 28 | filename: 字段名, 29 | file: 文件流, 30 | filename: 文件名 31 | */ 32 | return new Promise( (resolve, reject) => { 33 | var fileObj = {} 34 | busboy.on('file', async (fieldname, file, filename, encoding, mimetype) => { 35 | let filePath = '', 36 | imgPrefix = '' 37 | 38 | if (fieldname === 'markdown_img') { 39 | filePath = path.join(opts.path, mimetype.split('/')[0] + 's/markdown') 40 | imgPrefix = `${ctx.protocol}://${ctx.host}/${mimetype.split('/')[0]}s/markdown` 41 | }else{ 42 | filePath = path.join(opts.path, mimetype.split('/')[0] + 's') 43 | imgPrefix = `${ctx.protocol}://${ctx.host}/${mimetype.split('/')[0]}s` 44 | } 45 | 46 | 47 | if (!mkdirSync(filePath)) { 48 | throw new Error('没找到目录') 49 | } 50 | let fName = rename(filename), 51 | fPath = path.join(path.join(filePath, fName)) 52 | file.pipe(fs.createWriteStream(fPath)) 53 | 54 | file.on('end', () => { 55 | fileObj[fieldname] = `${imgPrefix}/${fName}` 56 | }) 57 | }) 58 | 59 | busboy.on('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => { 60 | fileObj[fieldname] = val; 61 | }) 62 | 63 | busboy.on('finish', async () => { 64 | resolve(fileObj) 65 | console.log('finished...') 66 | }) 67 | busboy.on('error', function (err) { 68 | console.log('err:' + err) 69 | reject(err) 70 | }) 71 | 72 | ctx.req.pipe(busboy) 73 | }) 74 | } 75 | 76 | // export const uploadFile = (ctx, opts) => { 77 | // //重命名 78 | // function rename (fileName) { 79 | // return Math.random().toString(16).substr(2) + '.' + fileName.split('.').pop() 80 | // } 81 | // console.log(ctx.req.headers) 82 | // let busboy = new Busboy({headers: ctx.req.headers}); 83 | // console.log('start uploading...') 84 | // /* 85 | // filename: 字段名, 86 | // file: 文件流, 87 | // filename: 文件名 88 | // */ 89 | // return new Promise( (resolve, reject) => { 90 | // var fileObj = {}; 91 | // busboy.on('file', async (fieldname, file, filename, encoding, mimetype) => { 92 | // let filePath = '', 93 | // imgPrefix = '' 94 | // filePath = path.join(opts.path, mimetype.split('/')[0] + 's') 95 | // imgPrefix = `${ctx.header.origin}/${mimetype.split('/')[0]}s` 96 | // if (!mkdirSync(filePath)) { 97 | // throw new Error('没找到目录') 98 | // } 99 | // let fName = rename(filename), 100 | // fPath = path.join(path.join(filePath, fName)) 101 | 102 | // file.pipe(fs.createWriteStream(fPath)) 103 | 104 | // file.on('end', () => { 105 | // fileObj[fieldname] = `${imgPrefix}/${fName}` 106 | // }) 107 | // }) 108 | 109 | // busboy.on('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => { 110 | // fileObj[fieldname] = val; 111 | // }) 112 | 113 | // busboy.on('finish', async () => { 114 | // resolve(fileObj) 115 | // console.log('finished...') 116 | // }) 117 | // busboy.on('error', function (err) { 118 | // console.log('err:' + err) 119 | // reject(err) 120 | // }) 121 | 122 | // ctx.req.pipe(busboy) 123 | // }) 124 | // } 125 | 126 | 127 | // //删除文件 128 | // export const delFile = (dirname, ctx) => { 129 | 130 | // return new Promise( (resolve, reject) => { 131 | // let files = [], 132 | // filePath = dirname.replace(ctx.request.origin, _root_path).replace(/\\/g,'\/'); 133 | // if(fs.existsSync(filePath)) { 134 | // fs.unlinkSync(filePath); 135 | // resolve() 136 | // console.log('删除成功') 137 | // }else{ 138 | // reject('文件不存在') 139 | // } 140 | // }) 141 | // }; 142 | 143 | 144 | // //删除文件夹 145 | // export const delFolder = (dirname, ctx) => { 146 | // return new Promise( (resolve, reject) => { 147 | // let filePath = dirname.replace(ctx.request.origin, _root_path).replace(/\\/g,'\/').split('\/').slice(0, -1).join('\/'); 148 | // del(filePath, ctx) 149 | // function del (fPath, ctx) { 150 | // let files = []; 151 | // if(fs.existsSync(fPath)) { 152 | // files = fs.readdirSync(fPath); 153 | // files.forEach( file => { 154 | // var curPath = fPath + "/" + file; 155 | // if(fs.statSync(curPath).isDirectory()) { //遇到文件夹 递归删除 156 | // del(curPath, ctx); 157 | // } else { 158 | // fs.unlinkSync(curPath); 159 | // } 160 | // }); 161 | // fs.rmdirSync(fPath); 162 | // console.log('删除成功') 163 | // resolve() 164 | // }else{ 165 | // console.log('不存在该目录'); 166 | // reject('不存在该目录') 167 | // } 168 | // } 169 | // }) 170 | // }; -------------------------------------------------------------------------------- /code/client/src/style/css/reset.css: -------------------------------------------------------------------------------- 1 | /* normalize.css */ 2 | html { 3 | line-height: 1.15; /* 1 */ 4 | -ms-text-size-adjust: 100%; /* 2 */ 5 | -webkit-text-size-adjust: 100%; /* 2 */ 6 | } 7 | 8 | body { 9 | margin: 0; 10 | } 11 | 12 | 13 | article, 14 | aside, 15 | footer, 16 | header, 17 | nav, 18 | section { 19 | display: block; 20 | } 21 | 22 | h1 { 23 | font-size: 2em; 24 | margin: 0.67em 0; 25 | } 26 | 27 | figcaption, 28 | figure, 29 | main { /* 1 */ 30 | display: block; 31 | } 32 | 33 | figure { 34 | margin: 1em 40px; 35 | } 36 | 37 | hr { 38 | box-sizing: content-box; /* 1 */ 39 | height: 0; /* 1 */ 40 | overflow: visible; /* 2 */ 41 | } 42 | 43 | pre { 44 | font-family: monospace, monospace; /* 1 */ 45 | font-size: 1em; /* 2 */ 46 | } 47 | 48 | a { 49 | background-color: transparent; /* 1 */ 50 | -webkit-text-decoration-skip: objects; /* 2 */ 51 | } 52 | 53 | 54 | abbr[title] { 55 | border-bottom: none; /* 1 */ 56 | text-decoration: underline; /* 2 */ 57 | text-decoration: underline dotted; /* 2 */ 58 | } 59 | 60 | b, 61 | strong { 62 | font-weight: inherit; 63 | } 64 | 65 | b, 66 | strong { 67 | font-weight: bolder; 68 | } 69 | 70 | code, 71 | kbd, 72 | samp { 73 | font-family: monospace, monospace; /* 1 */ 74 | font-size: 1em; /* 2 */ 75 | } 76 | 77 | dfn { 78 | font-style: italic; 79 | } 80 | 81 | mark { 82 | background-color: #ff0; 83 | color: #000; 84 | } 85 | 86 | 87 | small { 88 | font-size: 80%; 89 | } 90 | 91 | sub, 92 | sup { 93 | font-size: 75%; 94 | line-height: 0; 95 | position: relative; 96 | vertical-align: baseline; 97 | } 98 | 99 | sub { 100 | bottom: -0.25em; 101 | } 102 | 103 | sup { 104 | top: -0.5em; 105 | } 106 | 107 | 108 | audio, 109 | video { 110 | display: inline-block; 111 | } 112 | audio:not([controls]) { 113 | display: none; 114 | height: 0; 115 | } 116 | 117 | img { 118 | border-style: none; 119 | } 120 | 121 | svg:not(:root) { 122 | overflow: hidden; 123 | } 124 | 125 | button, 126 | input, 127 | optgroup, 128 | select, 129 | textarea { 130 | font-family: sans-serif; /* 1 */ 131 | font-size: 100%; /* 1 */ 132 | line-height: 1.15; /* 1 */ 133 | margin: 0; /* 2 */ 134 | } 135 | button, 136 | input { /* 1 */ 137 | overflow: visible; 138 | } 139 | button, 140 | select { /* 1 */ 141 | text-transform: none; 142 | } 143 | 144 | button, 145 | html [type="button"], /* 1 */ 146 | [type="reset"], 147 | [type="submit"] { 148 | -webkit-appearance: button; /* 2 */ 149 | } 150 | 151 | button::-moz-focus-inner, 152 | [type="button"]::-moz-focus-inner, 153 | [type="reset"]::-moz-focus-inner, 154 | [type="submit"]::-moz-focus-inner { 155 | border-style: none; 156 | padding: 0; 157 | } 158 | 159 | button:-moz-focusring, 160 | [type="button"]:-moz-focusring, 161 | [type="reset"]:-moz-focusring, 162 | [type="submit"]:-moz-focusring { 163 | outline: 1px dotted ButtonText; 164 | } 165 | 166 | fieldset { 167 | padding: 0.35em 0.75em 0.625em; 168 | } 169 | 170 | legend { 171 | box-sizing: border-box; /* 1 */ 172 | color: inherit; /* 2 */ 173 | display: table; /* 1 */ 174 | max-width: 100%; /* 1 */ 175 | padding: 0; /* 3 */ 176 | white-space: normal; /* 1 */ 177 | } 178 | 179 | 180 | progress { 181 | display: inline-block; /* 1 */ 182 | vertical-align: baseline; /* 2 */ 183 | } 184 | 185 | textarea { 186 | overflow: auto; 187 | } 188 | 189 | [type="checkbox"], 190 | [type="radio"] { 191 | box-sizing: border-box; /* 1 */ 192 | padding: 0; /* 2 */ 193 | } 194 | 195 | 196 | [type="number"]::-webkit-inner-spin-button, 197 | [type="number"]::-webkit-outer-spin-button { 198 | height: auto; 199 | } 200 | 201 | [type="search"] { 202 | -webkit-appearance: textfield; /* 1 */ 203 | outline-offset: -2px; /* 2 */ 204 | } 205 | 206 | 207 | [type="search"]::-webkit-search-cancel-button, 208 | [type="search"]::-webkit-search-decoration { 209 | -webkit-appearance: none; 210 | } 211 | 212 | 213 | ::-webkit-file-upload-button { 214 | -webkit-appearance: button; /* 1 */ 215 | font: inherit; /* 2 */ 216 | } 217 | 218 | details, /* 1 */ 219 | menu { 220 | display: block; 221 | } 222 | 223 | 224 | summary { 225 | display: list-item; 226 | } 227 | 228 | canvas { 229 | display: inline-block; 230 | } 231 | 232 | template { 233 | display: none; 234 | } 235 | 236 | [hidden] { 237 | display: none; 238 | } 239 | 240 | 241 | /* reset */ 242 | *{ 243 | box-sizing: border-box; 244 | } 245 | html, body{/* 禁止选中文本 */ 246 | -webkit-user-select: none; 247 | user-select: none; 248 | font: Oswald, 'Open Sans', Helvetica, Arial, sans-serif 249 | } 250 | /* 禁止长按链接与图片弹出菜单 */ 251 | a, img { 252 | -webkit-touch-callout: none; 253 | } 254 | 255 | /*ios android去除自带阴影的样式*/ 256 | a, input{ 257 | -webkit-tap-highlight-color:rgba(0,0,0,0); 258 | } 259 | 260 | input[type="text"] { 261 | -webkit-appearance: none; 262 | } 263 | 264 | 265 | html,body,h1,h2,h3,h4,h5,h6,div,dl,dt,dd,ul,ol,li,p,blockquote,pre,hr,figure,table,caption,th,td,form,fieldset,legend,input,button,textarea,menu{margin:0;padding:0;} 266 | header,footer,section,article,aside,nav,hgroup,address,figure,figcaption,menu,details{display:block;} 267 | table{border-collapse:collapse;border-spacing:0;} 268 | caption,th{text-align:left;font-weight:normal;} 269 | html,body,fieldset,img,iframe,abbr{border:0;} 270 | i,cite,em,var,address,dfn{font-style:normal;} 271 | [hidefocus],summary{outline:0;} 272 | li{list-style:none;} 273 | h1,h2,h3,h4,h5,h6,small{font-size:100%;} 274 | sup,sub{font-size:83%;} 275 | pre,code,kbd,samp{font-family:inherit;} 276 | q:before,q:after{content:none;} 277 | textarea{overflow:auto;resize:none;} 278 | label,summary{cursor:default;} 279 | a,button{cursor:pointer;} 280 | h1,h2,h3,h4,h5,h6,em,strong,b{font-weight:bold;} 281 | del,ins,u,s,a,a:hover{text-decoration:none;} -------------------------------------------------------------------------------- /code/client/src/views/home/blog.vue: -------------------------------------------------------------------------------- 1 | 21 | 71 | -------------------------------------------------------------------------------- /code/admin/src/views/Permission/list/index.vue: -------------------------------------------------------------------------------- 1 | 44 | 177 | 178 | 196 | -------------------------------------------------------------------------------- /code/admin/src/font/iconfont.js: -------------------------------------------------------------------------------- 1 | (function(window){var svgSprite='';var script=function(){var scripts=document.getElementsByTagName("script");return scripts[scripts.length-1]}();var shouldInjectCss=script.getAttribute("data-injectcss");var ready=function(fn){if(document.addEventListener){if(~["complete","loaded","interactive"].indexOf(document.readyState)){setTimeout(fn,0)}else{var loadFn=function(){document.removeEventListener("DOMContentLoaded",loadFn,false);fn()};document.addEventListener("DOMContentLoaded",loadFn,false)}}else if(document.attachEvent){IEContentLoaded(window,fn)}function IEContentLoaded(w,fn){var d=w.document,done=false,init=function(){if(!done){done=true;fn()}};var polling=function(){try{d.documentElement.doScroll("left")}catch(e){setTimeout(polling,50);return}init()};polling();d.onreadystatechange=function(){if(d.readyState=="complete"){d.onreadystatechange=null;init()}}}};var before=function(el,target){target.parentNode.insertBefore(el,target)};var prepend=function(el,target){if(target.firstChild){before(el,target.firstChild)}else{target.appendChild(el)}};function appendSvg(){var div,svg;div=document.createElement("div");div.innerHTML=svgSprite;svgSprite=null;svg=div.getElementsByTagName("svg")[0];if(svg){svg.setAttribute("aria-hidden","true");svg.style.position="absolute";svg.style.width=0;svg.style.height=0;svg.style.overflow="hidden";prepend(svg,document.body)}}if(shouldInjectCss&&!window.__iconfont__svg__cssinject__){window.__iconfont__svg__cssinject__=true;try{document.write("")}catch(e){console&&console.log(e)}}ready(appendSvg)})(window) --------------------------------------------------------------------------------