├── 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 |
2 |
3 | 权限设置
4 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
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 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/code/client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |

4 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
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 |
2 |
5 |
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 |
2 |
3 |
4 |
这是首页
5 |
6 |
7 |
8 |
16 |
17 |
18 |
24 |
--------------------------------------------------------------------------------
/code/client/src/components/Icon-svg/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
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 |
2 |
7 |
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 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/code/admin/src/views/Layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
23 |
24 |
25 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/code/client/src/views/home/info.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Wintermelon
6 |
吃喝拉撒睡...
7 |
8 |
9 |
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 |
2 |
3 |
4 |
5 | {{tag.name}}
6 |
7 |
8 |
9 |
10 |
11 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/code/client/src/components/Back/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Back
5 |
6 |
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 |
2 |
3 |
4 | {{item.name}}
5 |
6 |
7 |
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 |
2 |
8 |
9 |
10 |
36 |
37 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/code/client/src/components/Tag/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{text}}
6 |
7 |
8 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{userName}}
9 | 退出
10 |
11 |
12 |
14 |
15 |
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 |
2 |
3 |
22 |
23 |
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 |
2 |
3 |
11 |
12 |
13 |
24 |
25 |
49 |
50 |
--------------------------------------------------------------------------------
/code/admin/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
30 |
31 |
46 |
--------------------------------------------------------------------------------
/code/client/src/components/Github/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
24 |
25 |
49 |
50 |
--------------------------------------------------------------------------------
/code/client/src/views/home/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
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 |
3 |
11 |
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 |
2 |
3 |
4 | 系统登录
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 登录
14 |
15 |
16 |
17 |
18 |
65 |
66 |
--------------------------------------------------------------------------------
/code/admin/src/views/Permission/add/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 添加管理员
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
26 | 立即创建
27 |
28 |
29 |
30 |
31 |
32 |
33 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/code/client/src/views/Article/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{blogInfo.title}}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
{{blogInfo.title}}
11 |
12 |
13 |
14 |
15 |
![]()
16 |
17 |
18 |
19 |
20 |
21 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
27 |
28 |
29 | 编辑
30 |
31 |
32 |
33 |
34 |
35 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 更新
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
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 |
2 |
3 | 添加博客
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 立即创建
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
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 |
2 |
3 |
4 | -
5 |
6 |
7 |
{{v.title}}
8 |
9 |
10 |
11 | {{v.desc}}
12 |
13 |
![]()
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
71 |
--------------------------------------------------------------------------------
/code/admin/src/views/Permission/list/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 搜索
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{tag}}
18 |
19 |
20 | {{scope.row[scope.column.property] || '无'}}
21 |
22 |
23 |
24 |
25 | 编辑
26 | 删除
27 |
28 |
29 |
30 |
40 |
41 |
42 |
43 |
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)
--------------------------------------------------------------------------------