├── .nuxt ├── layouts │ └── default.vue ├── empty.js ├── middleware.js ├── views │ ├── app.template.html │ └── error.html ├── components │ ├── nuxt-link.server.js │ ├── nuxt.js │ ├── nuxt-error.vue │ ├── nuxt-link.client.js │ ├── nuxt-child.js │ ├── nuxt-build-indicator.vue │ └── nuxt-loading.vue ├── router.js ├── loading.html ├── router.scrollBehavior.js ├── store.js ├── App.js ├── index.js ├── server.js └── utils.js ├── static └── favicon.ico ├── .gitignore ├── assets ├── img │ ├── night.png │ ├── star-1.png │ ├── star-2.png │ ├── weixin.png │ ├── aside-bg.jpg │ ├── averter.jpg │ ├── cloud-2.jpg │ └── cloude-1.jpg └── css │ ├── codeStyle.css │ └── init.css ├── backpack.config.js ├── store ├── modules │ ├── hero.js │ ├── project.js │ └── article.js └── index.js ├── .editorconfig ├── utils ├── footer-mixin.js ├── time-mixin.js ├── music.js ├── blowser.js └── draw.js ├── config └── axios.js ├── .eslintrc.js ├── components ├── player.vue ├── toTop.vue ├── shear.vue └── comment.vue ├── server └── index.js ├── package.json ├── nuxt.config.js ├── api └── index.js ├── README.md ├── pages ├── allarticle.vue ├── project.vue ├── index.vue ├── hero.vue ├── article │ ├── index.vue │ └── _id.vue ├── about.vue └── music.vue └── layouts ├── music-layout.vue └── layout.vue /.nuxt/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.nuxt/empty.js: -------------------------------------------------------------------------------- 1 | // This file is intentionally left empty for noop aliases 2 | -------------------------------------------------------------------------------- /.nuxt/middleware.js: -------------------------------------------------------------------------------- 1 | const middleware = {} 2 | 3 | export default middleware 4 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naihe138/naice-blog/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # logs 5 | npm-debug.log 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /assets/img/night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naihe138/naice-blog/HEAD/assets/img/night.png -------------------------------------------------------------------------------- /assets/img/star-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naihe138/naice-blog/HEAD/assets/img/star-1.png -------------------------------------------------------------------------------- /assets/img/star-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naihe138/naice-blog/HEAD/assets/img/star-2.png -------------------------------------------------------------------------------- /assets/img/weixin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naihe138/naice-blog/HEAD/assets/img/weixin.png -------------------------------------------------------------------------------- /assets/img/aside-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naihe138/naice-blog/HEAD/assets/img/aside-bg.jpg -------------------------------------------------------------------------------- /assets/img/averter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naihe138/naice-blog/HEAD/assets/img/averter.jpg -------------------------------------------------------------------------------- /assets/img/cloud-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naihe138/naice-blog/HEAD/assets/img/cloud-2.jpg -------------------------------------------------------------------------------- /assets/img/cloude-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naihe138/naice-blog/HEAD/assets/img/cloude-1.jpg -------------------------------------------------------------------------------- /backpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack: (config, options, webpack) => { 3 | config.entry.main = './server/index.js' 4 | return config 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /store/modules/hero.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | state: { 4 | data: [] 5 | }, 6 | mutations: { 7 | getHero (state, data) { 8 | state.data = data 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /store/modules/project.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | state: { 4 | data: [] 5 | }, 6 | mutations: { 7 | getProject (state, data) { 8 | state.data = data || [] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.nuxt/views/app.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ HEAD }} 5 | 6 | 7 | {{ APP }} 8 | 9 | 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-link.server.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default { 4 | name: 'NuxtLink', 5 | extends: Vue.component('RouterLink'), 6 | props: { 7 | noPrefetch: { 8 | type: Boolean, 9 | default: false 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /utils/footer-mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | footer() { 4 | if ($('#mailContent').height() < $(window).height()) { 5 | this.$store.commit('changeFooterFixed', true) 6 | } else { 7 | this.$store.commit('changeFooterFixed', false) 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config/axios.js: -------------------------------------------------------------------------------- 1 | 2 | import axios from 'axios' 3 | 4 | axios.interceptors.response.use((response) => { 5 | // Do something with response data 6 | return response.data ? response.data : {}; 7 | }, function (error) { 8 | // Do something with response error 9 | return Promise.reject(error); 10 | }) 11 | 12 | export default axios -------------------------------------------------------------------------------- /utils/time-mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | toZero (num) { 4 | return num > 9 ? num : `0${num}` 5 | }, 6 | toTime(str, type) { 7 | const date = new Date(str) 8 | return `${date.getFullYear()}${type}${this.toZero(date.getMonth() + 1)}${type}${this.toZero(date.getDate())}` 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /store/modules/article.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | state: { 4 | data: [], 5 | hotData: [], 6 | selectArticle: { 7 | meta: {} 8 | } 9 | }, 10 | mutations: { 11 | getArticle (state, data) { 12 | state.data = data || [] 13 | }, 14 | getHotArticle (state, data) { 15 | state.hotData = data || [] 16 | }, 17 | selectArticle (state, data) { 18 | state.selectArticle = data || {} 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true 6 | }, 7 | parserOptions: { 8 | parser: 'babel-eslint' 9 | }, 10 | extends: [ 11 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 12 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 13 | 'plugin:vue/essential' 14 | ], 15 | // required to lint *.vue files 16 | plugins: [ 17 | 'vue' 18 | ], 19 | // add your custom rules here 20 | rules: {} 21 | } 22 | -------------------------------------------------------------------------------- /utils/music.js: -------------------------------------------------------------------------------- 1 | function formatTime(num) { 2 | var arr = num.substring(1, num.length).split(':') 3 | return (parseFloat(arr[0] * 60) + parseFloat(arr[1])) 4 | } 5 | 6 | export const formatLyric = str => { 7 | let arr = [] 8 | if (str) { 9 | arr = str.split('[').reduce((current, v) => { 10 | let arr = v.trim().split(']') 11 | if (arr[1]) { 12 | current.push({time: formatTime(arr[0]), text: arr[1]}) 13 | } 14 | return current 15 | }, []) 16 | } 17 | return arr 18 | } 19 | 20 | export const findLine = (time, arr) => { 21 | let len = arr.length - 1 22 | for (let i = 0; i < len; i++) { 23 | if (time <= arr[i].time) { 24 | return i 25 | } 26 | } 27 | return len 28 | } 29 | -------------------------------------------------------------------------------- /store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import article from './modules/article' 4 | import project from './modules/project' 5 | import hero from './modules/hero' 6 | 7 | Vue.use(Vuex) 8 | 9 | const store = () => new Vuex.Store({ 10 | state: { 11 | scrollTop: 0, 12 | footerFixed: false, 13 | averterNum: 0 14 | }, 15 | modules: { 16 | article, 17 | project, 18 | hero 19 | }, 20 | mutations: { 21 | changeScroll (state, num) { 22 | state.scrollTop = num 23 | }, 24 | changeFooterFixed (state, isb) { 25 | state.footerFixed = isb 26 | }, 27 | changeAverter (state, num) { 28 | state.averterNum = num 29 | } 30 | } 31 | }) 32 | 33 | export default store -------------------------------------------------------------------------------- /components/player.vue: -------------------------------------------------------------------------------- 1 | 22 | 33 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import { Nuxt, Builder } from 'nuxt' 3 | 4 | async function start () { 5 | const app = new Koa() 6 | const host = process.env.HOST || '127.0.0.1' 7 | const port = process.env.PORT || 3002 8 | 9 | // Import and Set Nuxt.js options 10 | let config = require('../nuxt.config.js') 11 | config.dev = !(app.env === 'production') 12 | 13 | // Instantiate nuxt.js 14 | const nuxt = new Nuxt(config) 15 | 16 | // Build in development 17 | if (config.dev) { 18 | const builder = new Builder(nuxt) 19 | await builder.build() 20 | } 21 | 22 | app.use(async (ctx, next) => { 23 | await next() 24 | ctx.status = 200 // koa defaults to 404 when it sees that status is unset 25 | return new Promise((resolve, reject) => { 26 | ctx.res.on('close', resolve) 27 | ctx.res.on('finish', resolve) 28 | nuxt.render(ctx.req, ctx.res, promise => { 29 | // nuxt.render passes a rejected promise into callback on error. 30 | promise.then(resolve).catch(reject) 31 | }) 32 | }) 33 | }) 34 | 35 | app.listen(port, host) 36 | console.log('Server listening on ' + host + ':' + port) // eslint-disable-line no-console 37 | } 38 | 39 | start() 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-nuxt", 3 | "version": "1.1.0", 4 | "description": "Nuxt.js project", 5 | "author": "何文林 <370215230@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "backpack dev", 9 | "build": "nuxt build && backpack build", 10 | "start": "cross-env NODE_ENV=production node build/main.js", 11 | "precommit": "npm run lint", 12 | "lint": "eslint --ext .js,.vue --ignore-path .gitignore .", 13 | "pm2": "pm2 deploy ecosystem.json production" 14 | }, 15 | "dependencies": { 16 | "axios": "^0.18.0", 17 | "clipboard": "^2.0.0", 18 | "cross-env": "^5.0.1", 19 | "koa": "^2.4.1", 20 | "nuxt": "^2.0.0", 21 | "source-map-support": "^0.4.15" 22 | }, 23 | "devDependencies": { 24 | "babel-eslint": "^10.0.1", 25 | "backpack-core": "^0.3.0", 26 | "eslint": "^4.19.1", 27 | "eslint-config-standard": "^10.2.1", 28 | "eslint-friendly-formatter": "^4.0.1", 29 | "eslint-loader": "^2.1.1", 30 | "eslint-plugin-html": "^2.0.3", 31 | "eslint-plugin-import": "^2.2.0", 32 | "eslint-plugin-node": "^4.2.2", 33 | "eslint-plugin-promise": "^3.4.0", 34 | "eslint-plugin-standard": "^3.0.1", 35 | "eslint-plugin-vue": "^4.3.0", 36 | "nodemon": "^1.11.0", 37 | "scmp": "^2.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /* 3 | ** Headers of the page 4 | */ 5 | head: { 6 | title: 'Naice', 7 | meta: [ 8 | { charset: 'utf-8' }, 9 | { 'http-equiv': 'cleartype', content: 'on' }, 10 | { 'http-equiv': 'Cache-Control' }, 11 | { name: 'viewport', content: 'width=device-width, initial-scale=1, user-scalable=no' }, 12 | { hid: 'description', name: 'description', content: 'Naice, 前端, blog' }, 13 | { hid: 'keywords', name: 'keywords', content: '前端开发,JavaScript, Node, Vue,nuxt' }, 14 | { name: 'author', content: '370215230@qq.com' } 15 | ], 16 | link: [ 17 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } 18 | ], 19 | script: [ 20 | { src: 'https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js' }, 21 | { src: 'https://cdn.bootcss.com/highlight.js/9.12.0/highlight.min.js' } 22 | ] 23 | }, 24 | css: [ 25 | '~assets/css/init.css', 26 | '~assets/css/codeStyle.css' 27 | ], 28 | /* 29 | ** Customize the progress bar color 30 | */ 31 | loading: { color: '#3B8070' }, 32 | /* 33 | ** Build configuration 34 | */ 35 | build: { 36 | /* 37 | ** Run ESLint on save 38 | */ 39 | extend (config, { isDev, isClient }) { 40 | if (isDev && isClient) { 41 | config.module.rules.push({ 42 | enforce: 'pre', 43 | test: /\.(js|vue)$/, 44 | loader: 'eslint-loader', 45 | exclude: /(node_modules)/ 46 | }) 47 | } 48 | } 49 | }, 50 | cache: true 51 | } 52 | -------------------------------------------------------------------------------- /components/toTop.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 42 | 43 | 73 | 74 | -------------------------------------------------------------------------------- /api/index.js: -------------------------------------------------------------------------------- 1 | 2 | import axios from '../config/axios' 3 | 4 | const baseUrl = process.env.NODE_ENV === 'production' ? 'production api' : 'http://127.0.0.1:3009/api/' 5 | // const baseUrl = 'http://127.0.0.1:3009/api/' 6 | 7 | export const getArticle = (params = {}) => axios.get(`${baseUrl}article/get`, {params}) 8 | 9 | export const getArticleId = (id) => axios.get(`${baseUrl}article/get/${id}`) 10 | 11 | export const getArticleAll = () => axios.get(`${baseUrl}article/getAll`) 12 | 13 | export const articleLike = (id) => axios.post(`${baseUrl}article/like/${id}`) 14 | 15 | export const getTag = () => axios.get(`${baseUrl}tag/get`) 16 | 17 | export const getComment = (params) => axios.get(`${baseUrl}comment/get`, {params}) 18 | 19 | export const addComment = (params) => axios.put(`${baseUrl}comment/add`, {...params}) 20 | 21 | export const commentLike = (id) => axios.post(`${baseUrl}comment/like/${id}`) 22 | 23 | export const addReply = (params) => axios.put(`${baseUrl}reply/add`, {...params}) 24 | 25 | export const getReply = (cid) => axios.get(`${baseUrl}reply/get/${cid}`) 26 | 27 | export const getHero = (params) => axios.get(`${baseUrl}hero/get`, {params}) 28 | 29 | export const addHero = (params) => axios.put(`${baseUrl}hero/add`, {...params}) 30 | 31 | export const addArticleLike = () => axios.put(`${baseUrl}hero/add`, {...params}) 32 | 33 | export const addCommentLike = () => axios.put(`${baseUrl}hero/add`, {...params}) 34 | 35 | export const replyCommentLike = () => axios.put(`${baseUrl}hero/add`, {...params}) 36 | 37 | export const getProject = (params) => axios.get(`${baseUrl}project/get`, {params}) 38 | 39 | export const getMusic = (params) => axios.get(`${baseUrl}music/get`, {params}) -------------------------------------------------------------------------------- /.nuxt/views/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Server error 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 | 15 |
Server error
16 |
{{ message }}
17 |
18 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { compile } from '../utils' 3 | 4 | import NuxtError from './nuxt-error.vue' 5 | 6 | import NuxtChild from './nuxt-child' 7 | 8 | export default { 9 | name: 'Nuxt', 10 | components: { 11 | NuxtChild, 12 | NuxtError 13 | }, 14 | props: { 15 | nuxtChildKey: { 16 | type: String, 17 | default: undefined 18 | }, 19 | keepAlive: Boolean, 20 | keepAliveProps: { 21 | type: Object, 22 | default: undefined 23 | }, 24 | name: { 25 | type: String, 26 | default: 'default' 27 | } 28 | }, 29 | computed: { 30 | routerViewKey() { 31 | // If nuxtChildKey prop is given or current route has children 32 | if (typeof this.nuxtChildKey !== 'undefined' || this.$route.matched.length > 1) { 33 | return this.nuxtChildKey || compile(this.$route.matched[0].path)(this.$route.params) 34 | } 35 | 36 | const [matchedRoute] = this.$route.matched 37 | 38 | if (!matchedRoute) { 39 | return this.$route.path 40 | } 41 | 42 | const Component = matchedRoute.components.default 43 | 44 | if (Component && Component.options) { 45 | const { options } = Component 46 | 47 | if (options.key) { 48 | return (typeof options.key === 'function' ? options.key(this.$route) : options.key) 49 | } 50 | } 51 | 52 | const strict = /\/$/.test(matchedRoute.path) 53 | return strict ? this.$route.path : this.$route.path.replace(/\/$/, '') 54 | } 55 | }, 56 | beforeCreate() { 57 | Vue.util.defineReactive(this, 'nuxt', this.$root.$options.nuxt) 58 | }, 59 | render(h) { 60 | // If there is some error 61 | if (this.nuxt.err) { 62 | return h('NuxtError', { 63 | props: { 64 | error: this.nuxt.err 65 | } 66 | }) 67 | } 68 | // Directly return nuxt child 69 | return h('NuxtChild', { 70 | key: this.routerViewKey, 71 | props: this.$props 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /assets/css/codeStyle.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Agate by Taufik Nurrohman 3 | * ---------------------------------------------------- 4 | * 5 | * #ade5fc 6 | * #a2fca2 7 | * #c6b4f0 8 | * #d36363 9 | * #fcc28c 10 | * #fc9b9b 11 | * #ffa 12 | * #fff 13 | * #333 14 | * #62c8f3 15 | * #888 16 | * 17 | */ 18 | 19 | .hljs { 20 | display: block; 21 | overflow-x: auto; 22 | padding: 1em; 23 | background: #333; 24 | color: white; 25 | } 26 | 27 | .hljs-name, 28 | .hljs-strong { 29 | font-weight: bold; 30 | } 31 | 32 | .hljs-code, 33 | .hljs-emphasis { 34 | font-style: italic; 35 | } 36 | 37 | .hljs-tag { 38 | color: #62c8f3; 39 | } 40 | 41 | .hljs-variable, 42 | .hljs-template-variable, 43 | .hljs-selector-id, 44 | .hljs-selector-class { 45 | color: #ade5fc; 46 | } 47 | 48 | .hljs-string, 49 | .hljs-bullet { 50 | color: #a2fca2; 51 | } 52 | 53 | .hljs-type, 54 | .hljs-title, 55 | .hljs-section, 56 | .hljs-attribute, 57 | .hljs-quote, 58 | .hljs-built_in, 59 | .hljs-builtin-name { 60 | color: #ffa; 61 | } 62 | 63 | .hljs-number, 64 | .hljs-symbol, 65 | .hljs-bullet { 66 | color: #d36363; 67 | } 68 | 69 | .hljs-keyword, 70 | .hljs-selector-tag, 71 | .hljs-literal { 72 | color: #fcc28c; 73 | } 74 | 75 | .hljs-comment, 76 | .hljs-deletion, 77 | .hljs-code { 78 | color: #888; 79 | } 80 | 81 | .hljs-regexp, 82 | .hljs-link { 83 | color: #c6b4f0; 84 | } 85 | 86 | .hljs-meta { 87 | color: #fc9b9b; 88 | } 89 | 90 | .hljs-deletion { 91 | background-color: #fc9b9b; 92 | color: #333; 93 | } 94 | 95 | .hljs-addition { 96 | background-color: #a2fca2; 97 | color: #333; 98 | } 99 | 100 | .hljs a { 101 | color: inherit; 102 | } 103 | 104 | .hljs a:focus, 105 | .hljs a:hover { 106 | color: inherit; 107 | text-decoration: underline; 108 | } 109 | 110 | code, kbd, pre, samp{ 111 | font-family: Monaco, Menlo, Consolas, -apple-system,SF UI Text,Arial,PingFang SC,Hiragino Sans GB,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif; 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 一直以来都想用自己所想的技术做一些个人小项目,之前的[博客](https://segmentfault.com/a/1190000010301516)觉从视觉上和交互上得有点小小不满足,所以想着做一些小小的重构。陆陆续续花了两个多月的时间,原因是工作特别的忙(为自己懒惰找借口),所以知道今天才发布上线。 2 | 3 | 博客地址:[naice-blog](https://github.com/naihe138/naice-blog) 4 | 5 | 博客管理:[naice-blog-admin](https://github.com/naihe138/naice-blog-admin) 6 | 7 | 博客后台:[node-koa](https://github.com/naihe138/naice-blog-koa) 8 | 9 | 10 | ### 相关截图: 11 | 12 | 13 | ![首页](https://user-gold-cdn.xitu.io/2018/4/1/1627f9d60ba1bb5d?w=1440&h=877&f=png&s=259281) 14 | 15 | ![文章](https://user-gold-cdn.xitu.io/2018/4/1/1627f9dd172c24fb?w=1440&h=877&f=png&s=294054) 16 | 17 | ![文章详情](https://user-gold-cdn.xitu.io/2018/4/1/1627fa172d40b56e?w=1500&h=1055&f=png&s=644818) 18 | 19 | ![评论](https://user-gold-cdn.xitu.io/2018/4/1/1627fa1d85695f5d?w=1500&h=1055&f=png&s=299251) 20 | 21 | ![音乐](http://img.store.naice.me/upqtsjle8.png) 22 | 23 | ![留言](https://user-gold-cdn.xitu.io/2018/4/1/1627f9f2b869f78d?w=1440&h=877&f=png&s=175663) 24 | 25 | ![归档](https://user-gold-cdn.xitu.io/2018/4/1/1627fa33c4308b1d?w=1440&h=877&f=png&s=285072) 26 | 27 | ![关于](https://user-gold-cdn.xitu.io/2018/4/1/1627fa36c72e2f13?w=1440&h=877&f=png&s=345397) 28 | 29 | 30 | ### 相关技术栈: 31 | 32 | + vue + vuex + vue-router +nuxt 33 | + react + redux + react-router + ant design 34 | + token控制 35 | + 按需加载,ssr 36 | + .... 37 | 38 | 39 | ### server 40 | 41 | + node + koa + mongoose 42 | + 路由用了装饰器包装,鉴别参数是否正确 43 | + 登录权限jwt 44 | + 百度sro推送,邮件通知 45 | + pm2自动化部署 46 | + nginx + ssl + http2 47 | + 缓存 48 | + .... 49 | 50 | 51 | ### 未来可能加入 52 | 53 | + ~~音乐可视化~~ 54 | + 移动适配 55 | + 页面数据可视化统计 56 | + react-native 57 | + .... 58 | 59 | 60 | ### 启动 61 | 62 | >启动前一定要先启动[服务器](https://github.com/naihe138/naice-blog-koa),不然那请求不到数据 63 | 64 | #### Clone 65 | ```` 66 | git clone git@github.com:naihe138/naice-blog.git 67 | 68 | ```` 69 | 70 | #### Install 71 | ```` 72 | npm install 73 | 74 | ```` 75 | 76 | #### Dev 77 | 78 | ```` 79 | npm run dev 80 | ```` 81 | 82 | #### Build 83 | 84 | ```` 85 | npm run build 86 | ```` 87 | 88 | #### View build 89 | 90 | ```` 91 | npm start 92 | ```` 93 | 94 | 如在浏览中遇到任何的bug,请留言我,我会第一时间修复,就此先谢谢 95 | -------------------------------------------------------------------------------- /.nuxt/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import { interopDefault } from './utils' 4 | import scrollBehavior from './router.scrollBehavior.js' 5 | 6 | const _70fb4839 = () => interopDefault(import('../pages/about.vue' /* webpackChunkName: "pages/about" */)) 7 | const _7f5d3839 = () => interopDefault(import('../pages/allarticle.vue' /* webpackChunkName: "pages/allarticle" */)) 8 | const _744ca525 = () => interopDefault(import('../pages/article/index.vue' /* webpackChunkName: "pages/article/index" */)) 9 | const _33d8927e = () => interopDefault(import('../pages/hero.vue' /* webpackChunkName: "pages/hero" */)) 10 | const _4fd3f25e = () => interopDefault(import('../pages/music.vue' /* webpackChunkName: "pages/music" */)) 11 | const _315c1676 = () => interopDefault(import('../pages/project.vue' /* webpackChunkName: "pages/project" */)) 12 | const _06c8a666 = () => interopDefault(import('../pages/article/_id.vue' /* webpackChunkName: "pages/article/_id" */)) 13 | const _de99ba04 = () => interopDefault(import('../pages/index.vue' /* webpackChunkName: "pages/index" */)) 14 | 15 | Vue.use(Router) 16 | 17 | export const routerOptions = { 18 | mode: 'history', 19 | base: decodeURI('/'), 20 | linkActiveClass: 'nuxt-link-active', 21 | linkExactActiveClass: 'nuxt-link-exact-active', 22 | scrollBehavior, 23 | 24 | routes: [{ 25 | path: "/about", 26 | component: _70fb4839, 27 | name: "about" 28 | }, { 29 | path: "/allarticle", 30 | component: _7f5d3839, 31 | name: "allarticle" 32 | }, { 33 | path: "/article", 34 | component: _744ca525, 35 | name: "article" 36 | }, { 37 | path: "/hero", 38 | component: _33d8927e, 39 | name: "hero" 40 | }, { 41 | path: "/music", 42 | component: _4fd3f25e, 43 | name: "music" 44 | }, { 45 | path: "/project", 46 | component: _315c1676, 47 | name: "project" 48 | }, { 49 | path: "/article/:id", 50 | component: _06c8a666, 51 | name: "article-id" 52 | }, { 53 | path: "/", 54 | component: _de99ba04, 55 | name: "index" 56 | }], 57 | 58 | fallback: false 59 | } 60 | 61 | export function createRouter() { 62 | return new Router(routerOptions) 63 | } 64 | -------------------------------------------------------------------------------- /pages/allarticle.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | 52 | 98 | -------------------------------------------------------------------------------- /.nuxt/loading.html: -------------------------------------------------------------------------------- 1 | 97 | 98 | 104 | 105 |
Loading...
106 | 107 | 108 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-error.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 52 | 53 | 98 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-link.client.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const requestIdleCallback = window.requestIdleCallback || 4 | function (cb) { 5 | const start = Date.now() 6 | return setTimeout(function () { 7 | cb({ 8 | didTimeout: false, 9 | timeRemaining: function () { 10 | return Math.max(0, 50 - (Date.now() - start)) 11 | } 12 | }) 13 | }, 1) 14 | } 15 | const observer = window.IntersectionObserver && new window.IntersectionObserver((entries) => { 16 | entries.forEach(({ intersectionRatio, target: link }) => { 17 | if (intersectionRatio <= 0) { 18 | return 19 | } 20 | link.__prefetch() 21 | }) 22 | }) 23 | 24 | export default { 25 | name: 'NuxtLink', 26 | extends: Vue.component('RouterLink'), 27 | props: { 28 | noPrefetch: { 29 | type: Boolean, 30 | default: false 31 | } 32 | }, 33 | mounted() { 34 | if (!this.noPrefetch) { 35 | requestIdleCallback(this.observe, { timeout: 2e3 }) 36 | } 37 | }, 38 | beforeDestroy() { 39 | if (this.__observed) { 40 | observer.unobserve(this.$el) 41 | delete this.$el.__prefetch 42 | } 43 | }, 44 | methods: { 45 | observe() { 46 | // If no IntersectionObserver, avoid prefetching 47 | if (!observer) { 48 | return 49 | } 50 | // Add to observer 51 | if (this.shouldPrefetch()) { 52 | this.$el.__prefetch = this.prefetch.bind(this) 53 | observer.observe(this.$el) 54 | this.__observed = true 55 | } 56 | }, 57 | shouldPrefetch() { 58 | return this.getPrefetchComponents().length > 0 59 | }, 60 | canPrefetch() { 61 | const conn = navigator.connection 62 | const hasBadConnection = this.$nuxt.isOffline || (conn && ((conn.effectiveType || '').includes('2g') || conn.saveData)) 63 | 64 | return !hasBadConnection 65 | }, 66 | getPrefetchComponents() { 67 | const ref = this.$router.resolve(this.to, this.$route, this.append) 68 | const Components = ref.resolved.matched.map(r => r.components.default) 69 | 70 | return Components.filter(Component => typeof Component === 'function' && !Component.options && !Component.__prefetched) 71 | }, 72 | prefetch() { 73 | if (!this.canPrefetch()) { 74 | return 75 | } 76 | // Stop obersing this link (in case of internet connection changes) 77 | observer.unobserve(this.$el) 78 | const Components = this.getPrefetchComponents() 79 | 80 | for (const Component of Components) { 81 | const componentOrPromise = Component() 82 | if (componentOrPromise instanceof Promise) { 83 | componentOrPromise.catch(() => {}) 84 | } 85 | Component.__prefetched = true 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /.nuxt/router.scrollBehavior.js: -------------------------------------------------------------------------------- 1 | import { getMatchedComponents } from './utils' 2 | 3 | if (process.client) { 4 | if ('scrollRestoration' in window.history) { 5 | window.history.scrollRestoration = 'manual' 6 | 7 | // reset scrollRestoration to auto when leaving page, allowing page reload 8 | // and back-navigation from other pages to use the browser to restore the 9 | // scrolling position. 10 | window.addEventListener('beforeunload', () => { 11 | window.history.scrollRestoration = 'auto' 12 | }) 13 | 14 | // Setting scrollRestoration to manual again when returning to this page. 15 | window.addEventListener('load', () => { 16 | window.history.scrollRestoration = 'manual' 17 | }) 18 | } 19 | } 20 | 21 | export default function (to, from, savedPosition) { 22 | // if the returned position is falsy or an empty object, 23 | // will retain current scroll position. 24 | let position = false 25 | 26 | // if no children detected and scrollToTop is not explicitly disabled 27 | const Pages = getMatchedComponents(to) 28 | if ( 29 | Pages.length < 2 && 30 | Pages.every(Page => Page.options.scrollToTop !== false) 31 | ) { 32 | // scroll to the top of the page 33 | position = { x: 0, y: 0 } 34 | } else if (Pages.some(Page => Page.options.scrollToTop)) { 35 | // if one of the children has scrollToTop option set to true 36 | position = { x: 0, y: 0 } 37 | } 38 | 39 | // savedPosition is only available for popstate navigations (back button) 40 | if (savedPosition) { 41 | position = savedPosition 42 | } 43 | 44 | const nuxt = window.$nuxt 45 | 46 | // triggerScroll is only fired when a new component is loaded 47 | if (to.path === from.path && to.hash !== from.hash) { 48 | nuxt.$nextTick(() => nuxt.$emit('triggerScroll')) 49 | } 50 | 51 | return new Promise((resolve) => { 52 | // wait for the out transition to complete (if necessary) 53 | nuxt.$once('triggerScroll', () => { 54 | // coords will be used if no selector is provided, 55 | // or if the selector didn't match any element. 56 | if (to.hash) { 57 | let hash = to.hash 58 | // CSS.escape() is not supported with IE and Edge. 59 | if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined') { 60 | hash = '#' + window.CSS.escape(hash.substr(1)) 61 | } 62 | try { 63 | if (document.querySelector(hash)) { 64 | // scroll to anchor by returning the selector 65 | position = { selector: hash } 66 | } 67 | } catch (e) { 68 | console.warn('Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).') 69 | } 70 | } 71 | resolve(position) 72 | }) 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /utils/blowser.js: -------------------------------------------------------------------------------- 1 | 2 | export const getBrowserVersion = (agent) => { 3 | let Browser = ''; 4 | //IE 5 | if (agent.indexOf('msie') > 0) { 6 | let regStr_ie = /msie [\d.]+;/gi 7 | Browser = 'Ie' 8 | } 9 | //firefox 10 | else if (agent.indexOf('firefox') > 0) { 11 | let regStr_ff = /firefox\/[\d.]+/gi; 12 | Browser = 'Firefox'; 13 | } 14 | //Chrome 15 | else if (agent.indexOf('chrome') > 0) { 16 | let regStr_chrome = /chrome\/[\d.]+/gi; 17 | Browser = "Chrome"; 18 | } 19 | // Safari 20 | else if (agent.indexOf('safari') > 0 && agent.indexOf 21 | ('chrome') < 0) { 22 | let regStr_saf = /version\/[\d.]+/gi; 23 | Browser = 'Safari' 24 | } else { 25 | Browser = 'Chrome' 26 | } 27 | return Browser; 28 | } 29 | 30 | export const currentSystem = (str) => { 31 | const reg = /mac/ig 32 | return reg.test(str) ? 'Mac' : 'Window' 33 | } 34 | 35 | export const avarterArr = [ 36 | 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3954676606,1888158900&fm=11&gp=0.jpg', 37 | 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1286998490,3468341349&fm=11&gp=0.jpg', 38 | 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=353868547,2822233069&fm=27&gp=0.jpg', 39 | 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1178011732,3353631614&fm=27&gp=0.jpg', 40 | 'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2481763586,4087522518&fm=27&gp=0.jpg', 41 | 'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1034317729,337624616&fm=27&gp=0.jpg', 42 | 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=128013007,1946750280&fm=27&gp=0.jpg', 43 | 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=4148795727,732143832&fm=27&gp=0.jpg', 44 | 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=4121225864,785761951&fm=27&gp=0.jpg', 45 | 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3619070856,3103619106&fm=27&gp=0.jpg', 46 | 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1910862757,2676655804&fm=27&gp=0.jpg', 47 | 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=977468359,542518020&fm=27&gp=0.jpg', 48 | 'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=600840806,2113634000&fm=27&gp=0.jpg', 49 | 'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2475222708,3513835460&fm=27&gp=0.jpg', 50 | 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1478849654,1851926890&fm=27&gp=0.jpg', 51 | 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1921393379,4069134737&fm=27&gp=0.jpg', 52 | 'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1067594990,1110172622&fm=27&gp=0.jpg', 53 | 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=566083323,1162924276&fm=27&gp=0.jpg', 54 | 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2200767505,492420988&fm=27&gp=0.jpg', 55 | 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1873573939,3515804663&fm=27&gp=0.jpg' 56 | ] 57 | 58 | -------------------------------------------------------------------------------- /pages/project.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 52 | 53 | 136 | 137 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-child.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | name: 'NuxtChild', 4 | functional: true, 5 | props: { 6 | nuxtChildKey: { 7 | type: String, 8 | default: '' 9 | }, 10 | keepAlive: Boolean, 11 | keepAliveProps: { 12 | type: Object, 13 | default: undefined 14 | } 15 | }, 16 | render(h, { parent, data, props }) { 17 | data.nuxtChild = true 18 | const _parent = parent 19 | const transitions = parent.$nuxt.nuxt.transitions 20 | const defaultTransition = parent.$nuxt.nuxt.defaultTransition 21 | 22 | let depth = 0 23 | while (parent) { 24 | if (parent.$vnode && parent.$vnode.data.nuxtChild) { 25 | depth++ 26 | } 27 | parent = parent.$parent 28 | } 29 | data.nuxtChildDepth = depth 30 | const transition = transitions[depth] || defaultTransition 31 | const transitionProps = {} 32 | transitionsKeys.forEach((key) => { 33 | if (typeof transition[key] !== 'undefined') { 34 | transitionProps[key] = transition[key] 35 | } 36 | }) 37 | 38 | const listeners = {} 39 | listenersKeys.forEach((key) => { 40 | if (typeof transition[key] === 'function') { 41 | listeners[key] = transition[key].bind(_parent) 42 | } 43 | }) 44 | // Add triggerScroll event on beforeEnter (fix #1376) 45 | const beforeEnter = listeners.beforeEnter 46 | listeners.beforeEnter = (el) => { 47 | // Ensure to trigger scroll event after calling scrollBehavior 48 | window.$nuxt.$nextTick(() => { 49 | window.$nuxt.$emit('triggerScroll') 50 | }) 51 | if (beforeEnter) { 52 | return beforeEnter.call(_parent, el) 53 | } 54 | } 55 | 56 | // make sure that leave is called asynchronous (fix #5703) 57 | if (transition.css === false) { 58 | const leave = listeners.leave 59 | 60 | // only add leave listener when user didnt provide one 61 | // or when it misses the done argument 62 | if (!leave || leave.length < 2) { 63 | listeners.leave = (el, done) => { 64 | if (leave) { 65 | leave.call(_parent, el) 66 | } 67 | 68 | _parent.$nextTick(done) 69 | } 70 | } 71 | } 72 | 73 | let routerView = h('routerView', data) 74 | 75 | if (props.keepAlive) { 76 | routerView = h('keep-alive', { props: props.keepAliveProps }, [routerView]) 77 | } 78 | 79 | return h('transition', { 80 | props: transitionProps, 81 | on: listeners 82 | }, [routerView]) 83 | } 84 | } 85 | 86 | const transitionsKeys = [ 87 | 'name', 88 | 'mode', 89 | 'appear', 90 | 'css', 91 | 'type', 92 | 'duration', 93 | 'enterClass', 94 | 'leaveClass', 95 | 'appearClass', 96 | 'enterActiveClass', 97 | 'enterActiveClass', 98 | 'leaveActiveClass', 99 | 'appearActiveClass', 100 | 'enterToClass', 101 | 'leaveToClass', 102 | 'appearToClass' 103 | ] 104 | 105 | const listenersKeys = [ 106 | 'beforeEnter', 107 | 'enter', 108 | 'afterEnter', 109 | 'enterCancelled', 110 | 'beforeLeave', 111 | 'leave', 112 | 'afterLeave', 113 | 'leaveCancelled', 114 | 'beforeAppear', 115 | 'appear', 116 | 'afterAppear', 117 | 'appearCancelled' 118 | ] 119 | -------------------------------------------------------------------------------- /components/shear.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 44 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-build-indicator.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 110 | 111 | 141 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-loading.vue: -------------------------------------------------------------------------------- 1 | 155 | 156 | 178 | -------------------------------------------------------------------------------- /.nuxt/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | const VUEX_PROPERTIES = ['state', 'getters', 'actions', 'mutations'] 7 | let store = {} 8 | 9 | void (function updateModules() { 10 | store = normalizeRoot(require('../store/index.js'), 'store/index.js') 11 | 12 | // If store is an exported method = classic mode (deprecated) 13 | 14 | if (typeof store === 'function') { 15 | return console.warn('Classic mode for store/ is deprecated and will be removed in Nuxt 3.') 16 | } 17 | 18 | // Enforce store modules 19 | store.modules = store.modules || {} 20 | 21 | resolveStoreModules(require('../store/modules/article.js'), 'modules/article.js') 22 | resolveStoreModules(require('../store/modules/hero.js'), 'modules/hero.js') 23 | resolveStoreModules(require('../store/modules/project.js'), 'modules/project.js') 24 | 25 | // If the environment supports hot reloading... 26 | 27 | if (process.client && module.hot) { 28 | // Whenever any Vuex module is updated... 29 | module.hot.accept([ 30 | '../store/index.js', 31 | '../store/modules/article.js', 32 | '../store/modules/hero.js', 33 | '../store/modules/project.js', 34 | ], () => { 35 | // Update `root.modules` with the latest definitions. 36 | updateModules() 37 | // Trigger a hot update in the store. 38 | window.$nuxt.$store.hotUpdate(store) 39 | }) 40 | } 41 | })() 42 | 43 | // createStore 44 | export const createStore = store instanceof Function ? store : () => { 45 | return new Vuex.Store(Object.assign({ 46 | strict: (process.env.NODE_ENV !== 'production') 47 | }, store)) 48 | } 49 | 50 | function resolveStoreModules(moduleData, filename) { 51 | moduleData = moduleData.default || moduleData 52 | // Remove store src + extension (./foo/index.js -> foo/index) 53 | const namespace = filename.replace(/\.(js|mjs)$/, '') 54 | const namespaces = namespace.split('/') 55 | let moduleName = namespaces[namespaces.length - 1] 56 | const filePath = `store/${filename}` 57 | 58 | moduleData = moduleName === 'state' 59 | ? normalizeState(moduleData, filePath) 60 | : normalizeModule(moduleData, filePath) 61 | 62 | // If src is a known Vuex property 63 | if (VUEX_PROPERTIES.includes(moduleName)) { 64 | const property = moduleName 65 | const storeModule = getStoreModule(store, namespaces, { isProperty: true }) 66 | 67 | // Replace state since it's a function 68 | mergeProperty(storeModule, moduleData, property) 69 | return 70 | } 71 | 72 | // If file is foo/index.js, it should be saved as foo 73 | const isIndexModule = (moduleName === 'index') 74 | if (isIndexModule) { 75 | namespaces.pop() 76 | moduleName = namespaces[namespaces.length - 1] 77 | } 78 | 79 | const storeModule = getStoreModule(store, namespaces) 80 | 81 | for (const property of VUEX_PROPERTIES) { 82 | mergeProperty(storeModule, moduleData[property], property) 83 | } 84 | 85 | if (moduleData.namespaced === false) { 86 | delete storeModule.namespaced 87 | } 88 | } 89 | 90 | function normalizeRoot(moduleData, filePath) { 91 | moduleData = moduleData.default || moduleData 92 | 93 | if (moduleData.commit) { 94 | throw new Error(`[nuxt] ${filePath} should export a method that returns a Vuex instance.`) 95 | } 96 | 97 | if (typeof moduleData !== 'function') { 98 | // Avoid TypeError: setting a property that has only a getter when overwriting top level keys 99 | moduleData = Object.assign({}, moduleData) 100 | } 101 | return normalizeModule(moduleData, filePath) 102 | } 103 | 104 | function normalizeState(moduleData, filePath) { 105 | if (typeof moduleData !== 'function') { 106 | console.warn(`${filePath} should export a method that returns an object`) 107 | const state = Object.assign({}, moduleData) 108 | return () => state 109 | } 110 | return normalizeModule(moduleData, filePath) 111 | } 112 | 113 | function normalizeModule(moduleData, filePath) { 114 | if (moduleData.state && typeof moduleData.state !== 'function') { 115 | console.warn(`'state' should be a method that returns an object in ${filePath}`) 116 | const state = Object.assign({}, moduleData.state) 117 | // Avoid TypeError: setting a property that has only a getter when overwriting top level keys 118 | moduleData = Object.assign({}, moduleData, { state: () => state }) 119 | } 120 | return moduleData 121 | } 122 | 123 | function getStoreModule(storeModule, namespaces, { isProperty = false } = {}) { 124 | // If ./mutations.js 125 | if (!namespaces.length || (isProperty && namespaces.length === 1)) { 126 | return storeModule 127 | } 128 | 129 | const namespace = namespaces.shift() 130 | 131 | storeModule.modules[namespace] = storeModule.modules[namespace] || {} 132 | storeModule.modules[namespace].namespaced = true 133 | storeModule.modules[namespace].modules = storeModule.modules[namespace].modules || {} 134 | 135 | return getStoreModule(storeModule.modules[namespace], namespaces, { isProperty }) 136 | } 137 | 138 | function mergeProperty(storeModule, moduleData, property) { 139 | if (!moduleData) return 140 | 141 | if (property === 'state') { 142 | storeModule.state = moduleData || storeModule.state 143 | } else { 144 | storeModule[property] = Object.assign({}, storeModule[property], moduleData) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /layouts/music-layout.vue: -------------------------------------------------------------------------------- 1 | 42 | 113 | 237 | -------------------------------------------------------------------------------- /.nuxt/App.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { getMatchedComponentsInstances, promisify, globalHandleError } from './utils' 3 | import NuxtLoading from './components/nuxt-loading.vue' 4 | 5 | import '../assets/css/init.css' 6 | 7 | import '../assets/css/codeStyle.css' 8 | 9 | import _7c6a36a0 from '../layouts/layout.vue' 10 | import _40930270 from '../layouts/music-layout.vue' 11 | import _6f6c098b from './layouts/default.vue' 12 | 13 | const layouts = { "_layout": _7c6a36a0,"_music-layout": _40930270,"_default": _6f6c098b } 14 | 15 | export default { 16 | head: {"title":"Naice","meta":[{"charset":"utf-8"},{"http-equiv":"cleartype","content":"on"},{"http-equiv":"Cache-Control"},{"name":"viewport","content":"width=device-width, initial-scale=1, user-scalable=no"},{"hid":"description","name":"description","content":"Naice, 前端, blog"},{"hid":"keywords","name":"keywords","content":"前端开发,JavaScript, Node, Vue,nuxt"},{"name":"author","content":"370215230@qq.com"}],"link":[{"rel":"icon","type":"image\u002Fx-icon","href":"\u002Ffavicon.ico"}],"script":[{"src":"https:\u002F\u002Fcdn.bootcss.com\u002Fjquery\u002F3.3.1\u002Fjquery.min.js"},{"src":"https:\u002F\u002Fcdn.bootcss.com\u002Fhighlight.js\u002F9.12.0\u002Fhighlight.min.js"}],"style":[]}, 17 | 18 | render(h, props) { 19 | const loadingEl = h('NuxtLoading', { ref: 'loading' }) 20 | const layoutEl = h(this.layout || 'nuxt') 21 | const templateEl = h('div', { 22 | domProps: { 23 | id: '__layout' 24 | }, 25 | key: this.layoutName 26 | }, [ layoutEl ]) 27 | 28 | const transitionEl = h('transition', { 29 | props: { 30 | name: 'layout', 31 | mode: 'out-in' 32 | }, 33 | on: { 34 | beforeEnter(el) { 35 | // Ensure to trigger scroll event after calling scrollBehavior 36 | window.$nuxt.$nextTick(() => { 37 | window.$nuxt.$emit('triggerScroll') 38 | }) 39 | } 40 | } 41 | }, [ templateEl ]) 42 | 43 | return h('div', { 44 | domProps: { 45 | id: '__nuxt' 46 | } 47 | }, [loadingEl, transitionEl]) 48 | }, 49 | data: () => ({ 50 | isOnline: true, 51 | layout: null, 52 | layoutName: '' 53 | }), 54 | beforeCreate() { 55 | Vue.util.defineReactive(this, 'nuxt', this.$options.nuxt) 56 | }, 57 | created() { 58 | // Add this.$nuxt in child instances 59 | Vue.prototype.$nuxt = this 60 | // add to window so we can listen when ready 61 | if (process.client) { 62 | window.$nuxt = this 63 | this.refreshOnlineStatus() 64 | // Setup the listeners 65 | window.addEventListener('online', this.refreshOnlineStatus) 66 | window.addEventListener('offline', this.refreshOnlineStatus) 67 | } 68 | // Add $nuxt.error() 69 | this.error = this.nuxt.error 70 | // Add $nuxt.context 71 | this.context = this.$options.context 72 | }, 73 | 74 | mounted() { 75 | this.$loading = this.$refs.loading 76 | }, 77 | watch: { 78 | 'nuxt.err': 'errorChanged' 79 | }, 80 | 81 | computed: { 82 | isOffline() { 83 | return !this.isOnline 84 | } 85 | }, 86 | methods: { 87 | refreshOnlineStatus() { 88 | if (process.client) { 89 | if (typeof window.navigator.onLine === 'undefined') { 90 | // If the browser doesn't support connection status reports 91 | // assume that we are online because most apps' only react 92 | // when they now that the connection has been interrupted 93 | this.isOnline = true 94 | } else { 95 | this.isOnline = window.navigator.onLine 96 | } 97 | } 98 | }, 99 | async refresh() { 100 | const pages = getMatchedComponentsInstances(this.$route) 101 | 102 | if (!pages.length) { 103 | return 104 | } 105 | this.$loading.start() 106 | const promises = pages.map(async (page) => { 107 | const p = [] 108 | 109 | if (page.$options.fetch) { 110 | p.push(promisify(page.$options.fetch, this.context)) 111 | } 112 | if (page.$options.asyncData) { 113 | p.push( 114 | promisify(page.$options.asyncData, this.context) 115 | .then((newData) => { 116 | for (const key in newData) { 117 | Vue.set(page.$data, key, newData[key]) 118 | } 119 | }) 120 | ) 121 | } 122 | return Promise.all(p) 123 | }) 124 | try { 125 | await Promise.all(promises) 126 | } catch (error) { 127 | this.$loading.fail() 128 | globalHandleError(error) 129 | this.error(error) 130 | } 131 | this.$loading.finish() 132 | }, 133 | 134 | errorChanged() { 135 | if (this.nuxt.err && this.$loading) { 136 | if (this.$loading.fail) this.$loading.fail() 137 | if (this.$loading.finish) this.$loading.finish() 138 | } 139 | }, 140 | 141 | setLayout(layout) { 142 | if(layout && typeof layout !== 'string') throw new Error('[nuxt] Avoid using non-string value as layout property.') 143 | 144 | if (!layout || !layouts['_' + layout]) { 145 | layout = 'default' 146 | } 147 | this.layoutName = layout 148 | this.layout = layouts['_' + layout] 149 | return this.layout 150 | }, 151 | loadLayout(layout) { 152 | if (!layout || !layouts['_' + layout]) { 153 | layout = 'default' 154 | } 155 | return Promise.resolve(layouts['_' + layout]) 156 | } 157 | }, 158 | components: { 159 | NuxtLoading 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 71 | 72 | 238 | -------------------------------------------------------------------------------- /.nuxt/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Meta from 'vue-meta' 3 | import ClientOnly from 'vue-client-only' 4 | import NoSsr from 'vue-no-ssr' 5 | import { createRouter } from './router.js' 6 | import NuxtChild from './components/nuxt-child.js' 7 | import NuxtError from './components/nuxt-error.vue' 8 | import Nuxt from './components/nuxt.js' 9 | import App from './App.js' 10 | import { setContext, getLocation, getRouteData, normalizeError } from './utils' 11 | import { createStore } from './store.js' 12 | 13 | /* Plugins */ 14 | 15 | // Component: 16 | Vue.component(ClientOnly.name, ClientOnly) 17 | // TODO: Remove in Nuxt 3: 18 | Vue.component(NoSsr.name, { 19 | ...NoSsr, 20 | render(h, ctx) { 21 | if (process.client && !NoSsr._warned) { 22 | NoSsr._warned = true 23 | console.warn(` has been deprecated and will be removed in Nuxt 3, please use instead`) 24 | } 25 | return NoSsr.render(h, ctx) 26 | } 27 | }) 28 | 29 | // Component: 30 | Vue.component(NuxtChild.name, NuxtChild) 31 | Vue.component('NChild', NuxtChild) 32 | 33 | // Component NuxtLink is imported in server.js or client.js 34 | 35 | // Component: ` 36 | Vue.component(Nuxt.name, Nuxt) 37 | 38 | // vue-meta configuration 39 | Vue.use(Meta, { 40 | keyName: 'head', // the component option name that vue-meta looks for meta info on. 41 | attribute: 'data-n-head', // the attribute name vue-meta adds to the tags it observes 42 | ssrAttribute: 'data-n-head-ssr', // the attribute name that lets vue-meta know that meta info has already been server-rendered 43 | tagIDKeyName: 'hid' // the property name that vue-meta uses to determine whether to overwrite or append a tag 44 | }) 45 | 46 | const defaultTransition = {"name":"page","mode":"out-in","appear":false,"appearClass":"appear","appearActiveClass":"appear-active","appearToClass":"appear-to"} 47 | 48 | async function createApp(ssrContext) { 49 | const router = await createRouter(ssrContext) 50 | 51 | const store = createStore(ssrContext) 52 | // Add this.$router into store actions/mutations 53 | store.$router = router 54 | 55 | // Fix SSR caveat https://github.com/nuxt/nuxt.js/issues/3757#issuecomment-414689141 56 | const registerModule = store.registerModule 57 | store.registerModule = (path, rawModule, options) => registerModule.call(store, path, rawModule, Object.assign({ preserveState: process.client }, options)) 58 | 59 | // Create Root instance 60 | 61 | // here we inject the router and store to all child components, 62 | // making them available everywhere as `this.$router` and `this.$store`. 63 | const app = { 64 | router, 65 | store, 66 | nuxt: { 67 | defaultTransition, 68 | transitions: [ defaultTransition ], 69 | setTransitions(transitions) { 70 | if (!Array.isArray(transitions)) { 71 | transitions = [ transitions ] 72 | } 73 | transitions = transitions.map((transition) => { 74 | if (!transition) { 75 | transition = defaultTransition 76 | } else if (typeof transition === 'string') { 77 | transition = Object.assign({}, defaultTransition, { name: transition }) 78 | } else { 79 | transition = Object.assign({}, defaultTransition, transition) 80 | } 81 | return transition 82 | }) 83 | this.$options.nuxt.transitions = transitions 84 | return transitions 85 | }, 86 | err: null, 87 | dateErr: null, 88 | error(err) { 89 | err = err || null 90 | app.context._errored = Boolean(err) 91 | err = err ? normalizeError(err) : null 92 | const nuxt = this.nuxt || this.$options.nuxt 93 | nuxt.dateErr = Date.now() 94 | nuxt.err = err 95 | // Used in src/server.js 96 | if (ssrContext) ssrContext.nuxt.error = err 97 | return err 98 | } 99 | }, 100 | ...App 101 | } 102 | 103 | // Make app available into store via this.app 104 | store.app = app 105 | 106 | const next = ssrContext ? ssrContext.next : location => app.router.push(location) 107 | // Resolve route 108 | let route 109 | if (ssrContext) { 110 | route = router.resolve(ssrContext.url).route 111 | } else { 112 | const path = getLocation(router.options.base) 113 | route = router.resolve(path).route 114 | } 115 | 116 | // Set context to app.context 117 | await setContext(app, { 118 | route, 119 | next, 120 | error: app.nuxt.error.bind(app), 121 | store, 122 | payload: ssrContext ? ssrContext.payload : undefined, 123 | req: ssrContext ? ssrContext.req : undefined, 124 | res: ssrContext ? ssrContext.res : undefined, 125 | beforeRenderFns: ssrContext ? ssrContext.beforeRenderFns : undefined, 126 | ssrContext 127 | }) 128 | 129 | if (process.client) { 130 | // Replace store state before plugins execution 131 | if (window.__NUXT__ && window.__NUXT__.state) { 132 | store.replaceState(window.__NUXT__.state) 133 | } 134 | } 135 | 136 | // Plugin execution 137 | 138 | // If server-side, wait for async component to be resolved first 139 | if (process.server && ssrContext && ssrContext.url) { 140 | await new Promise((resolve, reject) => { 141 | router.push(ssrContext.url, resolve, () => { 142 | // navigated to a different route in router guard 143 | const unregister = router.afterEach(async (to, from, next) => { 144 | ssrContext.url = to.fullPath 145 | app.context.route = await getRouteData(to) 146 | app.context.params = to.params || {} 147 | app.context.query = to.query || {} 148 | unregister() 149 | resolve() 150 | }) 151 | }) 152 | }) 153 | } 154 | 155 | return { 156 | app, 157 | store, 158 | router 159 | } 160 | } 161 | 162 | export { createApp, NuxtError } 163 | -------------------------------------------------------------------------------- /components/comment.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 182 | 183 | 320 | 321 | -------------------------------------------------------------------------------- /pages/hero.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 107 | 108 | 295 | -------------------------------------------------------------------------------- /.nuxt/server.js: -------------------------------------------------------------------------------- 1 | import { stringify } from 'querystring' 2 | import Vue from 'vue' 3 | import fetch from 'node-fetch' 4 | import middleware from './middleware.js' 5 | import { applyAsyncData, getMatchedComponents, middlewareSeries, promisify, urlJoin, sanitizeComponent } from './utils.js' 6 | import { createApp, NuxtError } from './index.js' 7 | import NuxtLink from './components/nuxt-link.server.js' // should be included after ./index.js 8 | 9 | // Component: 10 | Vue.component(NuxtLink.name, NuxtLink) 11 | Vue.component('NLink', NuxtLink) 12 | 13 | if (!global.fetch) { global.fetch = fetch } 14 | 15 | const noopApp = () => new Vue({ render: h => h('div') }) 16 | 17 | const createNext = ssrContext => (opts) => { 18 | ssrContext.redirected = opts 19 | // If nuxt generate 20 | if (!ssrContext.res) { 21 | ssrContext.nuxt.serverRendered = false 22 | return 23 | } 24 | opts.query = stringify(opts.query) 25 | opts.path = opts.path + (opts.query ? '?' + opts.query : '') 26 | const routerBase = '/' 27 | if (!opts.path.startsWith('http') && (routerBase !== '/' && !opts.path.startsWith(routerBase))) { 28 | opts.path = urlJoin(routerBase, opts.path) 29 | } 30 | // Avoid loop redirect 31 | if (opts.path === ssrContext.url) { 32 | ssrContext.redirected = false 33 | return 34 | } 35 | ssrContext.res.writeHead(opts.status, { 36 | 'Location': opts.path 37 | }) 38 | ssrContext.res.end() 39 | } 40 | 41 | // This exported function will be called by `bundleRenderer`. 42 | // This is where we perform data-prefetching to determine the 43 | // state of our application before actually rendering it. 44 | // Since data fetching is async, this function is expected to 45 | // return a Promise that resolves to the app instance. 46 | export default async (ssrContext) => { 47 | // Create ssrContext.next for simulate next() of beforeEach() when wanted to redirect 48 | ssrContext.redirected = false 49 | ssrContext.next = createNext(ssrContext) 50 | // Used for beforeNuxtRender({ Components, nuxtState }) 51 | ssrContext.beforeRenderFns = [] 52 | // Nuxt object (window{{globals.context}}, defaults to window.__NUXT__) 53 | ssrContext.nuxt = { layout: 'default', data: [], error: null, state: null, serverRendered: true } 54 | // Create the app definition and the instance (created for each request) 55 | const { app, router, store } = await createApp(ssrContext) 56 | const _app = new Vue(app) 57 | 58 | // Add meta infos (used in renderer.js) 59 | ssrContext.meta = _app.$meta() 60 | // Keep asyncData for each matched component in ssrContext (used in app/utils.js via this.$ssrContext) 61 | ssrContext.asyncData = {} 62 | 63 | const beforeRender = async () => { 64 | // Call beforeNuxtRender() methods 65 | await Promise.all(ssrContext.beforeRenderFns.map(fn => promisify(fn, { Components, nuxtState: ssrContext.nuxt }))) 66 | ssrContext.rendered = () => { 67 | // Add the state from the vuex store 68 | ssrContext.nuxt.state = store.state 69 | } 70 | } 71 | const renderErrorPage = async () => { 72 | // Load layout for error page 73 | const errLayout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(app.context) : NuxtError.layout) 74 | ssrContext.nuxt.layout = errLayout || 'default' 75 | await _app.loadLayout(errLayout) 76 | _app.setLayout(errLayout) 77 | await beforeRender() 78 | return _app 79 | } 80 | const render404Page = () => { 81 | app.context.error({ statusCode: 404, path: ssrContext.url, message: `This page could not be found` }) 82 | return renderErrorPage() 83 | } 84 | 85 | const s = Date.now() 86 | 87 | // Components are already resolved by setContext -> getRouteData (app/utils.js) 88 | const Components = getMatchedComponents(router.match(ssrContext.url)) 89 | 90 | /* 91 | ** Dispatch store nuxtServerInit 92 | */ 93 | if (store._actions && store._actions.nuxtServerInit) { 94 | try { 95 | await store.dispatch('nuxtServerInit', app.context) 96 | } catch (err) { 97 | console.debug('Error occurred when calling nuxtServerInit: ', err.message) 98 | throw err 99 | } 100 | } 101 | // ...If there is a redirect or an error, stop the process 102 | if (ssrContext.redirected) return noopApp() 103 | if (ssrContext.nuxt.error) return renderErrorPage() 104 | 105 | /* 106 | ** Call global middleware (nuxt.config.js) 107 | */ 108 | let midd = [] 109 | midd = midd.map((name) => { 110 | if (typeof name === 'function') return name 111 | if (typeof middleware[name] !== 'function') { 112 | app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 113 | } 114 | return middleware[name] 115 | }) 116 | await middlewareSeries(midd, app.context) 117 | // ...If there is a redirect or an error, stop the process 118 | if (ssrContext.redirected) return noopApp() 119 | if (ssrContext.nuxt.error) return renderErrorPage() 120 | 121 | /* 122 | ** Set layout 123 | */ 124 | let layout = Components.length ? Components[0].options.layout : NuxtError.layout 125 | if (typeof layout === 'function') layout = layout(app.context) 126 | await _app.loadLayout(layout) 127 | if (ssrContext.nuxt.error) return renderErrorPage() 128 | layout = _app.setLayout(layout) 129 | ssrContext.nuxt.layout = _app.layoutName 130 | 131 | /* 132 | ** Call middleware (layout + pages) 133 | */ 134 | midd = [] 135 | layout = sanitizeComponent(layout) 136 | if (layout.options.middleware) midd = midd.concat(layout.options.middleware) 137 | Components.forEach((Component) => { 138 | if (Component.options.middleware) { 139 | midd = midd.concat(Component.options.middleware) 140 | } 141 | }) 142 | midd = midd.map((name) => { 143 | if (typeof name === 'function') return name 144 | if (typeof middleware[name] !== 'function') { 145 | app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 146 | } 147 | return middleware[name] 148 | }) 149 | await middlewareSeries(midd, app.context) 150 | // ...If there is a redirect or an error, stop the process 151 | if (ssrContext.redirected) return noopApp() 152 | if (ssrContext.nuxt.error) return renderErrorPage() 153 | 154 | /* 155 | ** Call .validate() 156 | */ 157 | let isValid = true 158 | try { 159 | for (const Component of Components) { 160 | if (typeof Component.options.validate !== 'function') { 161 | continue 162 | } 163 | 164 | isValid = await Component.options.validate(app.context) 165 | 166 | if (!isValid) { 167 | break 168 | } 169 | } 170 | } catch (validationError) { 171 | // ...If .validate() threw an error 172 | app.context.error({ 173 | statusCode: validationError.statusCode || '500', 174 | message: validationError.message 175 | }) 176 | return renderErrorPage() 177 | } 178 | 179 | // ...If .validate() returned false 180 | if (!isValid) { 181 | // Don't server-render the page in generate mode 182 | if (ssrContext._generate) ssrContext.nuxt.serverRendered = false 183 | // Render a 404 error page 184 | return render404Page() 185 | } 186 | 187 | // If no Components found, returns 404 188 | if (!Components.length) return render404Page() 189 | 190 | // Call asyncData & fetch hooks on components matched by the route. 191 | const asyncDatas = await Promise.all(Components.map((Component) => { 192 | const promises = [] 193 | 194 | // Call asyncData(context) 195 | if (Component.options.asyncData && typeof Component.options.asyncData === 'function') { 196 | const promise = promisify(Component.options.asyncData, app.context) 197 | promise.then((asyncDataResult) => { 198 | ssrContext.asyncData[Component.cid] = asyncDataResult 199 | applyAsyncData(Component) 200 | return asyncDataResult 201 | }) 202 | promises.push(promise) 203 | } else { 204 | promises.push(null) 205 | } 206 | 207 | // Call fetch(context) 208 | if (Component.options.fetch) { 209 | promises.push(Component.options.fetch(app.context)) 210 | } else { 211 | promises.push(null) 212 | } 213 | 214 | return Promise.all(promises) 215 | })) 216 | 217 | if (process.env.DEBUG && asyncDatas.length)console.debug('Data fetching ' + ssrContext.url + ': ' + (Date.now() - s) + 'ms') 218 | 219 | // datas are the first row of each 220 | ssrContext.nuxt.data = asyncDatas.map(r => r[0] || {}) 221 | 222 | // ...If there is a redirect or an error, stop the process 223 | if (ssrContext.redirected) return noopApp() 224 | if (ssrContext.nuxt.error) return renderErrorPage() 225 | 226 | // Call beforeNuxtRender methods & add store state 227 | await beforeRender() 228 | 229 | return _app 230 | } 231 | -------------------------------------------------------------------------------- /assets/css/init.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | word-spacing: 1px; 4 | -ms-text-size-adjust: 100%; 5 | -webkit-text-size-adjust: 100%; 6 | -moz-osx-font-smoothing: grayscale; 7 | box-sizing: border-box; 8 | margin: 0; 9 | color: #555; 10 | letter-spacing: .5px; 11 | font-family: Futura, -apple-system, SF UI Text, Arial, PingFang SC, Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, sans-serif; 12 | -webkit-font-smoothing: antialiased; 13 | } 14 | 15 | *, 16 | *:before, 17 | *:after { 18 | box-sizing: border-box; 19 | margin: 0; 20 | padding: 0; 21 | } 22 | 23 | 24 | /** 清除内外边距 **/ 25 | 26 | body, 27 | h1, 28 | h2, 29 | h3, 30 | h4, 31 | h5, 32 | h6, 33 | hr, 34 | p, 35 | blockquote, 36 | 37 | /* structural elements 结构元素 */ 38 | 39 | dl, 40 | dt, 41 | dd, 42 | ul, 43 | ol, 44 | li, 45 | 46 | /* list elements 列表元素 */ 47 | 48 | pre, 49 | 50 | /* text formatting elements 文本格式元素 */ 51 | 52 | form, 53 | fieldset, 54 | legend, 55 | button, 56 | input, 57 | textarea, 58 | 59 | /* form elements 表单元素 */ 60 | 61 | th, 62 | td 63 | /* table elements 表格元素 */ 64 | 65 | { 66 | margin: 0; 67 | padding: 0; 68 | } 69 | 70 | 71 | /** 设置默认字体 **/ 72 | 73 | button, 74 | input, 75 | select, 76 | textarea 77 | /* for ie */ 78 | 79 | { 80 | font: 14px/1.5 tahoma, arial, \5b8b\4f53, sans-serif; 81 | } 82 | 83 | h1, 84 | h2, 85 | h3, 86 | h4, 87 | h5, 88 | h6 { 89 | font-size: 100%; 90 | } 91 | 92 | address, 93 | cite, 94 | dfn, 95 | em, 96 | var { 97 | font-style: normal; 98 | } 99 | 100 | 101 | /* 将斜体扶正 */ 102 | 103 | code, 104 | kbd, 105 | pre, 106 | samp { 107 | font-family: courier new, courier, monospace; 108 | } 109 | 110 | 111 | /* 统一等宽字体 */ 112 | 113 | small { 114 | font-size: 12px; 115 | } 116 | 117 | 118 | /* 小于 12px 的中文很难阅读,让 small 正常化 */ 119 | 120 | 121 | /** 重置列表元素 **/ 122 | 123 | ul, 124 | ol { 125 | list-style: none; 126 | } 127 | 128 | 129 | /** 重置文本格式元素 **/ 130 | 131 | a { 132 | text-decoration: none; 133 | } 134 | 135 | a:hover { 136 | text-decoration: underline; 137 | } 138 | 139 | a { 140 | color: #555; 141 | text-decoration: none; 142 | } 143 | 144 | strong { 145 | font-weight: normal; 146 | } 147 | 148 | 149 | /** 重置表单元素 **/ 150 | 151 | legend { 152 | color: #000; 153 | } 154 | 155 | 156 | /* for ie6 */ 157 | 158 | fieldset, 159 | img { 160 | border: 0; 161 | } 162 | 163 | 164 | /* img 搭车:让链接里的 img 无边框 */ 165 | 166 | button, 167 | input, 168 | select, 169 | textarea { 170 | font-size: 100%; 171 | } 172 | 173 | 174 | /* 使得表单元素在 ie 下能继承字体大小 */ 175 | 176 | 177 | /* 注:optgroup 无法扶正 */ 178 | 179 | 180 | /** 重置表格元素 **/ 181 | 182 | table { 183 | border-collapse: collapse; 184 | border-spacing: 0; 185 | } 186 | 187 | i { 188 | font-style: normal 189 | } 190 | 191 | @font-face { 192 | font-family: 'iconfont'; /* project id 358841 */ 193 | src: url('//at.alicdn.com/t/font_358841_9aw2a59f7pp.eot'); 194 | src: url('//at.alicdn.com/t/font_358841_9aw2a59f7pp.eot?#iefix') format('embedded-opentype'), 195 | url('//at.alicdn.com/t/font_358841_9aw2a59f7pp.woff2') format('woff2'), 196 | url('//at.alicdn.com/t/font_358841_9aw2a59f7pp.woff') format('woff'), 197 | url('//at.alicdn.com/t/font_358841_9aw2a59f7pp.ttf') format('truetype'), 198 | url('//at.alicdn.com/t/font_358841_9aw2a59f7pp.svg#iconfont') format('svg'); 199 | } 200 | 201 | .iconfont { 202 | font-family: "iconfont" !important; 203 | font-size: 16px; 204 | font-style: normal; 205 | -webkit-font-smoothing: antialiased; 206 | -moz-osx-font-smoothing: grayscale; 207 | } 208 | 209 | ::selection { 210 | background: #3fb76c; 211 | color: #fff; 212 | } 213 | 214 | ::-moz-selection { 215 | background: #3fb76c; 216 | color: #fff; 217 | } 218 | 219 | ::-webkit-selection { 220 | background: #3fb76c; 221 | color: #fff; 222 | } 223 | 224 | .fade-enter-active, 225 | .fade-leave-active { 226 | transition: opacity .5s; 227 | } 228 | 229 | .fade-enter, 230 | .fade-leave-to { 231 | opacity: 0; 232 | } 233 | 234 | .icon-T:before { 235 | content:"\e607"; 236 | } 237 | .icon-yingdaicon04:before { 238 | content:"\e643"; 239 | } 240 | .icon-icon8:before { 241 | content:"\e608"; 242 | } 243 | .icon-qqkongjian1:before { 244 | content:"\e7dc"; 245 | } 246 | .icon-weibiaoti1:before { 247 | content:"\e637"; 248 | } 249 | .icon-lianjie2:before { 250 | content:"\e665"; 251 | } 252 | .icon-4:before { 253 | content:"\e601"; 254 | } 255 | .icon-shu:before { 256 | content:"\e604"; 257 | } 258 | .icon-home:before { 259 | content:"\e73a"; 260 | } 261 | .icon-douban:before { 262 | content:"\e663"; 263 | } 264 | .icon-xihuan:before { 265 | content:"\e63b"; 266 | } 267 | .icon-windows:before { 268 | content:"\ec83"; 269 | } 270 | .icon-twitter:before { 271 | content:"\ec9c"; 272 | } 273 | .icon-douban1:before { 274 | content:"\e649"; 275 | } 276 | .icon-02:before { 277 | content:"\e64a"; 278 | } 279 | .icon-daima:before { 280 | content:"\e605"; 281 | } 282 | .icon-tupian:before { 283 | content:"\e626"; 284 | } 285 | .icon-code:before { 286 | content:"\e725"; 287 | } 288 | .icon-iconsf-copy:before { 289 | content:"\e610"; 290 | } 291 | .icon-shang:before { 292 | content:"\e614"; 293 | } 294 | .icon-iconfont21:before { 295 | content:"\e613"; 296 | } 297 | .icon-fenxiang:before { 298 | content:"\e6a5"; 299 | } 300 | .icon-bianji1:before { 301 | content:"\e654"; 302 | } 303 | .icon-nodejs:before { 304 | content:"\e989"; 305 | } 306 | .icon-dingwei1:before { 307 | content:"\e615"; 308 | } 309 | .icon-lianjie:before { 310 | content:"\e609"; 311 | } 312 | .icon-jiacu:before { 313 | content:"\e61e"; 314 | } 315 | .icon-mac:before { 316 | content:"\e61b"; 317 | } 318 | .icon-icon:before { 319 | content:"\e65b"; 320 | } 321 | .icon-dingwei:before { 322 | content:"\e662"; 323 | } 324 | .icon-qq:before { 325 | content:"\e602"; 326 | } 327 | .icon-sanjiaotop:before { 328 | content:"\e6b4"; 329 | } 330 | .icon-jingyin:before { 331 | content:"\e65e"; 332 | } 333 | .icon-bofang:before { 334 | content:"\e60d"; 335 | } 336 | .icon-youxiang:before { 337 | content:"\e64d"; 338 | } 339 | .icon-like:before { 340 | content:"\e65c"; 341 | } 342 | .icon-code1:before { 343 | content:"\e69f"; 344 | } 345 | .icon-facebookf:before { 346 | content:"\e6e2"; 347 | } 348 | .icon-duigou:before { 349 | content:"\e640"; 350 | } 351 | .icon-bianji:before { 352 | content:"\e636"; 353 | } 354 | .icon-weibo:before { 355 | content:"\e63d"; 356 | } 357 | .icon-iconstop:before { 358 | content:"\e69d"; 359 | } 360 | .icon-ai09:before { 361 | content:"\e67d"; 362 | } 363 | .icon-ai10:before { 364 | content:"\e67f"; 365 | } 366 | .icon-google:before { 367 | content:"\e6b2"; 368 | } 369 | .icon-iconfontyouxihudong:before { 370 | content:"\e639"; 371 | } 372 | .icon-bushu:before { 373 | content:"\e77f"; 374 | } 375 | .icon-yulanguanbi:before { 376 | content:"\e650"; 377 | } 378 | .icon-704bianjiqi_Ixieti:before { 379 | content:"\e65a"; 380 | } 381 | .icon-434-npm:before { 382 | content:"\e6d9"; 383 | } 384 | .icon-shezhi:before { 385 | content:"\e60e"; 386 | } 387 | .icon-quotation_marks:before { 388 | content:"\e715"; 389 | } 390 | .icon-sousuo:before { 391 | content:"\e603"; 392 | } 393 | .icon-qqkongjian:before { 394 | content:"\e624"; 395 | } 396 | .icon-danjiantoushang:before { 397 | content:"\e635"; 398 | } 399 | .icon-danjiantouxia:before { 400 | content:"\e638"; 401 | } 402 | .icon-wuxuliebiao:before { 403 | content:"\e645"; 404 | } 405 | .icon-youxuliebiao:before { 406 | content:"\e67e"; 407 | } 408 | .icon-ic_code_px:before { 409 | content:"\e8b6"; 410 | } 411 | .icon-yulan:before { 412 | content:"\e7b9"; 413 | } 414 | .icon-zhixiangzuo:before { 415 | content:"\e8f4"; 416 | } 417 | .icon-zhixiangyou:before { 418 | content:"\e8f5"; 419 | } 420 | .icon-lianjie1:before { 421 | content:"\e60b"; 422 | } 423 | .icon-biaoqing:before { 424 | content:"\e60c"; 425 | } 426 | .icon-fabu:before { 427 | content:"\e62a"; 428 | } 429 | .icon-youjian:before { 430 | content:"\e60a"; 431 | } 432 | .icon-xiangmu:before { 433 | content:"\e671"; 434 | } 435 | .icon-xiaoxi:before { 436 | content:"\e65d"; 437 | } 438 | .icon-xinlang:before { 439 | content:"\e832"; 440 | } 441 | .icon-weixin:before { 442 | content:"\e62e"; 443 | } 444 | .icon-ios:before { 445 | content:"\e64b"; 446 | } 447 | .icon-yinxiangbiji:before { 448 | content:"\e764"; 449 | } 450 | .icon-github:before { 451 | content:"\e621"; 452 | } 453 | .icon-hebingfenping:before { 454 | content:"\e612"; 455 | } 456 | .icon-yonghu-tianchong:before { 457 | content:"\e670"; 458 | } 459 | .icon-linkedin:before { 460 | content:"\e627"; 461 | } 462 | .icon-biaoqian:before { 463 | content:"\e8cb"; 464 | } 465 | .icon-dian1:before { 466 | content:"\e600"; 467 | } 468 | .icon-icon-test:before { 469 | content:"\e617"; 470 | } 471 | .icon-gengduo:before { 472 | content:"\e606"; 473 | } 474 | .icon-liulanqi:before { 475 | content:"\e68f"; 476 | } 477 | .icon-icon-:before { 478 | content:"\e611"; 479 | } 480 | .icon-mingxingxianchang:before { 481 | content:"\e6c3"; 482 | } 483 | .icon-benzihebi:before { 484 | content:"\e60f"; 485 | } 486 | .icon-angular:before { 487 | content:"\e616"; 488 | } 489 | .icon-nginx:before { 490 | content:"\e63c"; 491 | } 492 | .icon-vuejs:before { 493 | content:"\e641"; 494 | } 495 | .icon-react:before { 496 | content:"\e64c"; 497 | } 498 | .icon-zanting:before { 499 | content:"\e618"; 500 | } 501 | .icon-prev:before { 502 | content:"\e619"; 503 | } 504 | .icon--liebiao:before { 505 | content:"\e628"; 506 | } 507 | .icon-xiayiqu101:before { 508 | content:"\e61a"; 509 | } 510 | .icon-jilugeci:before { 511 | content:"\e70f"; 512 | } -------------------------------------------------------------------------------- /pages/article/index.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 174 | 175 | 325 | -------------------------------------------------------------------------------- /layouts/layout.vue: -------------------------------------------------------------------------------- 1 | 43 | 290 | 419 | -------------------------------------------------------------------------------- /utils/draw.js: -------------------------------------------------------------------------------- 1 | var fftSize = 1024, 2 | background_gradient_color_1 = '#000011', 3 | background_gradient_color_2 = '#060D1F', 4 | background_gradient_color_3 = '#02243F', 5 | 6 | stars_color = '#465677', 7 | stars_color_2 = '#B5BFD4', 8 | stars_color_special = '#F451BA', 9 | TOTAL_STARS = 1500, 10 | STARS_BREAK_POINT = 140, 11 | 12 | waveform_color = 'rgba(29, 36, 57, 0.05)', 13 | waveform_color_2 = 'rgba(0,0,0,0)', 14 | waveform_line_color = 'rgba(157, 242, 157, 0.11)', 15 | waveform_line_color_2 = 'rgba(157, 242, 157, 0.8)', 16 | waveform_tick = 0.05, 17 | TOTAL_POINTS = fftSize / 2, 18 | 19 | bubble_avg_color = 'rgba(29, 36, 57, 0.1)', 20 | bubble_avg_color_2 = 'rgba(29, 36, 57, 0.05)', 21 | bubble_avg_line_color = 'rgba(77, 218, 248, 1)', 22 | bubble_avg_line_color_2 = 'rgba(77, 218, 248, 1)', 23 | bubble_avg_tick = 0.001, 24 | TOTAL_AVG_POINTS = 64, 25 | AVG_BREAK_POINT = 100, 26 | 27 | floor = Math.floor, 28 | round = Math.round, 29 | random = Math.random, 30 | sin = Math.sin, 31 | cos = Math.cos, 32 | PI = Math.PI, 33 | PI_TWO = PI * 2, 34 | PI_HALF = PI / 180, 35 | cx = 0, 36 | cy = 0, 37 | rotation = 0; 38 | 39 | class Star { 40 | constructor (avg, w, h) { 41 | let xc, yc; 42 | this.x = Math.random() * w - cx 43 | this.y = Math.random() * h - cy 44 | this.z = this.max_depth = Math.max(w / h) 45 | this.radius = 0.2 46 | xc = this.x > 0 ? 1 : -1 47 | yc = this.y > 0 ? 1 : -1 48 | 49 | if (Math.abs(this.x) > Math.abs(this.y)) { 50 | this.dx = 1.0 51 | this.dy = Math.abs(this.y / this.x) 52 | } else { 53 | this.dx = Math.abs(this.x / this.y) 54 | this.dy = 1.0 55 | } 56 | 57 | this.dx *= xc 58 | this.dy *= yc 59 | this.dz = -0.1 60 | 61 | this.ddx = .001 * this.dx 62 | this.ddy = .001 * this.dy 63 | 64 | if (this.y > (cy / 2)) { 65 | this.color = stars_color_2 66 | } else { 67 | if (avg > AVG_BREAK_POINT + 10) { 68 | this.color = stars_color_2 69 | } else if (avg > STARS_BREAK_POINT) { 70 | this.color = stars_color_special 71 | } else { 72 | this.color = stars_color 73 | } 74 | } 75 | xc = yc = null 76 | } 77 | } 78 | 79 | class Point { 80 | constructor (config) { 81 | this.index = config.index 82 | this.angle = (this.index * 360) / TOTAL_POINTS 83 | this.updateDynamics(config.w, config.h) 84 | this.value = Math.random() * 256 85 | this.dx = this.x + this.value * sin(PI_HALF * this.angle) 86 | this.dy = this.y + this.value * cos(PI_HALF * this.angle) 87 | } 88 | updateDynamics (w, h) { 89 | this.radius = Math.abs(w, h) / 10 90 | this.x = cx + this.radius * sin(PI_HALF * this.angle) 91 | this.y = cy + this.radius * cos(PI_HALF * this.angle) 92 | } 93 | } 94 | 95 | class AvgPoint { 96 | constructor (config) { 97 | this.index = config.index 98 | this.angle = (this.index * 360) / TOTAL_AVG_POINTS 99 | this.updateDynamics(config.w, config.h) 100 | this.value = Math.random() * 256 101 | this.dx = this.x + this.value * sin(PI_HALF * this.angle) 102 | this.dy = this.y + this.value * cos(PI_HALF * this.angle) 103 | } 104 | updateDynamics (w, h) { 105 | this.radius = Math.abs(w, h) / 10 106 | this.x = cx + this.radius * sin(PI_HALF * this.angle) 107 | this.y = cy + this.radius * cos(PI_HALF * this.angle) 108 | } 109 | } 110 | 111 | class MusicDraw { 112 | constructor (ctx, analyser, w, h, cb) { 113 | this.analyser = analyser 114 | this.ctx = ctx 115 | this.stars = [] 116 | this.points = [] 117 | this.avg_points = [] 118 | this.w = w 119 | this.h = h 120 | this.playing = false 121 | this.frequencyDataLength = analyser.frequencyBinCount; 122 | this.frequencyData = new Uint8Array(this.frequencyDataLength); 123 | this.timeData = new Uint8Array(this.frequencyDataLength); 124 | this.cb = cb 125 | this.init() 126 | } 127 | init () { 128 | this.resizeHandler() 129 | this.createStarField() 130 | this.createPoints() 131 | this.clearCanvas() 132 | this.drawStarField() 133 | this.drawAverageCircle() 134 | this.drawWaveform() 135 | } 136 | updatePlaying (b) { 137 | this.playing = b 138 | } 139 | createStarField () { 140 | let i = -1; 141 | while (++i < TOTAL_STARS) { 142 | this.stars.push(new Star(this.avg, this.w, this.h)) 143 | } 144 | this.catchOriginStart = JSON.parse(JSON.stringify(this.stars)) 145 | i = null 146 | } 147 | createPoints() { 148 | const { w, h } = this 149 | let i = -1; 150 | while (++i < TOTAL_POINTS) { 151 | this.points.push(new Point({ 152 | index: i + 1, 153 | w, h 154 | })) 155 | } 156 | i = -1 157 | while (++i < TOTAL_AVG_POINTS) { 158 | this.avg_points.push(new AvgPoint({ 159 | index: i + 1, 160 | w, h 161 | })) 162 | } 163 | i = null 164 | } 165 | getAvg(values) { 166 | let value = 0 167 | values.forEach(v => { 168 | value += v 169 | }) 170 | return value / values.length 171 | } 172 | animate () { 173 | if (!this.playing) return 174 | const { analyser, getAvg, frequencyData, timeData } = this 175 | requestAnimationFrame(this.animate.bind(this)) 176 | 177 | analyser.getByteFrequencyData(frequencyData) 178 | analyser.getByteTimeDomainData(timeData) 179 | this.avg = getAvg([].slice.call(frequencyData)) 180 | this.clearCanvas() 181 | this.drawStarField() 182 | this.drawAverageCircle() 183 | this.drawWaveform() 184 | this.cb && this.cb() 185 | } 186 | clearCanvas () { 187 | const { ctx, w, h } = this 188 | let gradient = ctx.createLinearGradient(0, 0, 0, h) 189 | gradient.addColorStop(0, background_gradient_color_1) 190 | gradient.addColorStop(0.96, background_gradient_color_2) 191 | gradient.addColorStop(1, background_gradient_color_3) 192 | 193 | ctx.beginPath() 194 | ctx.globalCompositeOperation = 'source-over' 195 | ctx.fillStyle = gradient 196 | ctx.fillRect(0, 0, w, h) 197 | ctx.fill() 198 | ctx.closePath() 199 | gradient = null 200 | } 201 | drawStarField() { 202 | let { stars, avg, ctx, catchOriginStart } = this 203 | let w = this.w 204 | let h = this.h 205 | let i, len, p, tick 206 | for (i = 0, len = stars.length; i < len; i++) { 207 | p = stars[i] 208 | p.x = p.x || catchOriginStart[i].x 209 | p.y = p.y || catchOriginStart[i].y 210 | tick = (avg > AVG_BREAK_POINT) ? (avg / 20) : (avg / 50) 211 | p.x += p.dx * tick 212 | p.y += p.dy * tick 213 | p.z += p.dz 214 | p.dx += p.ddx 215 | p.dy += p.ddy 216 | p.radius = 0.2 + ((p.max_depth - p.z) * .1) 217 | if (p.x < -cx || p.x > cx || p.y < -cy || p.y > cy) { 218 | stars[i] = new Star(avg, w, h) 219 | continue 220 | } 221 | ctx.beginPath() 222 | ctx.globalCompositeOperation = 'lighter' 223 | ctx.fillStyle = p.color 224 | ctx.arc(p.x + cx, p.y + cy, p.radius, PI_TWO, false) 225 | ctx.fill() 226 | ctx.closePath() 227 | } 228 | i = len = p = tick = null 229 | } 230 | drawAverageCircle() { 231 | let { avg, ctx, avg_points } = this 232 | let i, len, p, value, xc, yc; 233 | if (avg > AVG_BREAK_POINT) { 234 | rotation += -bubble_avg_tick 235 | value = avg + random() * 10 236 | ctx.strokeStyle = bubble_avg_line_color_2 237 | ctx.fillStyle = bubble_avg_color_2 238 | } else { 239 | rotation += bubble_avg_tick 240 | value = avg 241 | ctx.strokeStyle = bubble_avg_line_color 242 | ctx.fillStyle = bubble_avg_color 243 | } 244 | ctx.beginPath() 245 | ctx.lineWidth = 1 246 | ctx.lineCap = "round" 247 | 248 | ctx.save() 249 | ctx.translate(cx, cy) 250 | ctx.rotate(rotation) 251 | ctx.translate(-cx, -cy) 252 | 253 | ctx.moveTo(avg_points[0].dx, avg_points[0].dy) 254 | 255 | for (i = 0, len = TOTAL_AVG_POINTS; i < len - 1; i++) { 256 | p = avg_points[i] 257 | p.dx = p.x + value * sin(PI_HALF * p.angle) 258 | p.dy = p.y + value * cos(PI_HALF * p.angle) 259 | xc = (p.dx + avg_points[i + 1].dx) / 2 260 | yc = (p.dy + avg_points[i + 1].dy) / 2 261 | ctx.quadraticCurveTo(p.dx, p.dy, xc, yc) 262 | } 263 | 264 | p = avg_points[i] 265 | p.dx = p.x + value * sin(PI_HALF * p.angle) 266 | p.dy = p.y + value * cos(PI_HALF * p.angle) 267 | xc = (p.dx + avg_points[0].dx) / 2 268 | yc = (p.dy + avg_points[0].dy) / 2 269 | 270 | ctx.quadraticCurveTo(p.dx, p.dy, xc, yc) 271 | ctx.quadraticCurveTo(xc, yc, avg_points[0].dx, avg_points[0].dy) 272 | 273 | ctx.stroke() 274 | ctx.fill() 275 | ctx.restore() 276 | ctx.closePath() 277 | i = len = p = value = xc = yc = null 278 | } 279 | drawWaveform() { 280 | let { avg, ctx, points, timeData } = this 281 | let i, len, p, value, xc, yc; 282 | 283 | if (avg > AVG_BREAK_POINT) { 284 | rotation += waveform_tick 285 | ctx.strokeStyle = waveform_line_color_2 286 | ctx.fillStyle = waveform_color_2 287 | } else { 288 | rotation += -waveform_tick 289 | ctx.strokeStyle = waveform_line_color 290 | ctx.fillStyle = waveform_color 291 | } 292 | 293 | ctx.beginPath() 294 | ctx.lineWidth = 1 295 | ctx.lineCap = "round" 296 | 297 | ctx.save() 298 | ctx.translate(cx, cy) 299 | ctx.rotate(rotation) 300 | ctx.translate(-cx, -cy) 301 | 302 | ctx.moveTo(points[0].dx, points[0].dy) 303 | 304 | for (i = 0, len = TOTAL_POINTS; i < len - 1; i++) { 305 | p = points[i] 306 | value = timeData[i] 307 | p.dx = p.x + value * sin(PI_HALF * p.angle) 308 | p.dy = p.y + value * cos(PI_HALF * p.angle) 309 | xc = (p.dx + points[i + 1].dx) / 2 310 | yc = (p.dy + points[i + 1].dy) / 2 311 | 312 | ctx.quadraticCurveTo(p.dx, p.dy, xc, yc) 313 | } 314 | 315 | value = timeData[i] 316 | p = points[i] 317 | p.dx = p.x + value * sin(PI_HALF * p.angle) 318 | p.dy = p.y + value * cos(PI_HALF * p.angle) 319 | xc = (p.dx + points[0].dx) / 2 320 | yc = (p.dy + points[0].dy) / 2 321 | 322 | ctx.quadraticCurveTo(p.dx, p.dy, xc, yc) 323 | ctx.quadraticCurveTo(xc, yc, points[0].dx, points[0].dy) 324 | 325 | ctx.stroke() 326 | ctx.fill() 327 | ctx.restore() 328 | ctx.closePath() 329 | 330 | i = len = p = value = xc = yc = null 331 | } 332 | resizeHandler() { 333 | let { ctx, w, h, avg_points, points } = this 334 | cx = w / 2 335 | cy = h / 2 336 | ctx.canvas.width = w 337 | ctx.canvas.height = h 338 | points.forEach(p => { 339 | p.updateDynamics() 340 | }) 341 | avg_points.forEach(p => { 342 | p.updateDynamics() 343 | }) 344 | } 345 | } 346 | 347 | export default MusicDraw -------------------------------------------------------------------------------- /pages/about.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 213 | 214 | 472 | -------------------------------------------------------------------------------- /pages/music.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 277 | 278 | 500 | -------------------------------------------------------------------------------- /.nuxt/utils.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | // window.{{globals.loadedCallback}} hook 4 | // Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading) 5 | if (process.client) { 6 | window.onNuxtReadyCbs = [] 7 | window.onNuxtReady = (cb) => { 8 | window.onNuxtReadyCbs.push(cb) 9 | } 10 | } 11 | 12 | export function empty() {} 13 | 14 | export function globalHandleError(error) { 15 | if (Vue.config.errorHandler) { 16 | Vue.config.errorHandler(error) 17 | } 18 | } 19 | 20 | export function interopDefault(promise) { 21 | return promise.then(m => m.default || m) 22 | } 23 | 24 | export function applyAsyncData(Component, asyncData) { 25 | if ( 26 | // For SSR, we once all this function without second param to just apply asyncData 27 | // Prevent doing this for each SSR request 28 | !asyncData && Component.options.__hasNuxtData 29 | ) { 30 | return 31 | } 32 | 33 | const ComponentData = Component.options._originDataFn || Component.options.data || function () { return {} } 34 | Component.options._originDataFn = ComponentData 35 | 36 | Component.options.data = function () { 37 | const data = ComponentData.call(this) 38 | if (this.$ssrContext) { 39 | asyncData = this.$ssrContext.asyncData[Component.cid] 40 | } 41 | return { ...data, ...asyncData } 42 | } 43 | 44 | Component.options.__hasNuxtData = true 45 | 46 | if (Component._Ctor && Component._Ctor.options) { 47 | Component._Ctor.options.data = Component.options.data 48 | } 49 | } 50 | 51 | export function sanitizeComponent(Component) { 52 | // If Component already sanitized 53 | if (Component.options && Component._Ctor === Component) { 54 | return Component 55 | } 56 | if (!Component.options) { 57 | Component = Vue.extend(Component) // fix issue #6 58 | Component._Ctor = Component 59 | } else { 60 | Component._Ctor = Component 61 | Component.extendOptions = Component.options 62 | } 63 | // For debugging purpose 64 | if (!Component.options.name && Component.options.__file) { 65 | Component.options.name = Component.options.__file 66 | } 67 | return Component 68 | } 69 | 70 | export function getMatchedComponents(route, matches = false) { 71 | return Array.prototype.concat.apply([], route.matched.map((m, index) => { 72 | return Object.keys(m.components).map((key) => { 73 | matches && matches.push(index) 74 | return m.components[key] 75 | }) 76 | })) 77 | } 78 | 79 | export function getMatchedComponentsInstances(route, matches = false) { 80 | return Array.prototype.concat.apply([], route.matched.map((m, index) => { 81 | return Object.keys(m.instances).map((key) => { 82 | matches && matches.push(index) 83 | return m.instances[key] 84 | }) 85 | })) 86 | } 87 | 88 | export function flatMapComponents(route, fn) { 89 | return Array.prototype.concat.apply([], route.matched.map((m, index) => { 90 | return Object.keys(m.components).reduce((promises, key) => { 91 | if (m.components[key]) { 92 | promises.push(fn(m.components[key], m.instances[key], m, key, index)) 93 | } else { 94 | delete m.components[key] 95 | } 96 | return promises 97 | }, []) 98 | })) 99 | } 100 | 101 | export function resolveRouteComponents(route, fn) { 102 | return Promise.all( 103 | flatMapComponents(route, async (Component, instance, match, key) => { 104 | // If component is a function, resolve it 105 | if (typeof Component === 'function' && !Component.options) { 106 | Component = await Component() 107 | } 108 | match.components[key] = Component = sanitizeComponent(Component) 109 | return typeof fn === 'function' ? fn(Component, instance, match, key) : Component 110 | }) 111 | ) 112 | } 113 | 114 | export async function getRouteData(route) { 115 | if (!route) { 116 | return 117 | } 118 | // Make sure the components are resolved (code-splitting) 119 | await resolveRouteComponents(route) 120 | // Send back a copy of route with meta based on Component definition 121 | return { 122 | ...route, 123 | meta: getMatchedComponents(route).map((Component, index) => { 124 | return { ...Component.options.meta, ...(route.matched[index] || {}).meta } 125 | }) 126 | } 127 | } 128 | 129 | export async function setContext(app, context) { 130 | // If context not defined, create it 131 | if (!app.context) { 132 | app.context = { 133 | isStatic: process.static, 134 | isDev: true, 135 | isHMR: false, 136 | app, 137 | store: app.store, 138 | payload: context.payload, 139 | error: context.error, 140 | base: '/', 141 | env: {} 142 | } 143 | // Only set once 144 | if (context.req) { 145 | app.context.req = context.req 146 | } 147 | if (context.res) { 148 | app.context.res = context.res 149 | } 150 | if (context.ssrContext) { 151 | app.context.ssrContext = context.ssrContext 152 | } 153 | app.context.redirect = (status, path, query) => { 154 | if (!status) { 155 | return 156 | } 157 | app.context._redirected = true 158 | // if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' }) 159 | let pathType = typeof path 160 | if (typeof status !== 'number' && (pathType === 'undefined' || pathType === 'object')) { 161 | query = path || {} 162 | path = status 163 | pathType = typeof path 164 | status = 302 165 | } 166 | if (pathType === 'object') { 167 | path = app.router.resolve(path).route.fullPath 168 | } 169 | // "/absolute/route", "./relative/route" or "../relative/route" 170 | if (/(^[.]{1,2}\/)|(^\/(?!\/))/.test(path)) { 171 | app.context.next({ 172 | path, 173 | query, 174 | status 175 | }) 176 | } else { 177 | path = formatUrl(path, query) 178 | if (process.server) { 179 | app.context.next({ 180 | path, 181 | status 182 | }) 183 | } 184 | if (process.client) { 185 | // https://developer.mozilla.org/en-US/docs/Web/API/Location/replace 186 | window.location.replace(path) 187 | 188 | // Throw a redirect error 189 | throw new Error('ERR_REDIRECT') 190 | } 191 | } 192 | } 193 | if (process.server) { 194 | app.context.beforeNuxtRender = fn => context.beforeRenderFns.push(fn) 195 | } 196 | if (process.client) { 197 | app.context.nuxtState = window.__NUXT__ 198 | } 199 | } 200 | 201 | // Dynamic keys 202 | const [currentRouteData, fromRouteData] = await Promise.all([ 203 | getRouteData(context.route), 204 | getRouteData(context.from) 205 | ]) 206 | 207 | if (context.route) { 208 | app.context.route = currentRouteData 209 | } 210 | 211 | if (context.from) { 212 | app.context.from = fromRouteData 213 | } 214 | 215 | app.context.next = context.next 216 | app.context._redirected = false 217 | app.context._errored = false 218 | app.context.isHMR = Boolean(context.isHMR) 219 | app.context.params = app.context.route.params || {} 220 | app.context.query = app.context.route.query || {} 221 | } 222 | 223 | export function middlewareSeries(promises, appContext) { 224 | if (!promises.length || appContext._redirected || appContext._errored) { 225 | return Promise.resolve() 226 | } 227 | return promisify(promises[0], appContext) 228 | .then(() => { 229 | return middlewareSeries(promises.slice(1), appContext) 230 | }) 231 | } 232 | 233 | export function promisify(fn, context) { 234 | let promise 235 | if (fn.length === 2) { 236 | console.warn('Callback-based asyncData, fetch or middleware calls are deprecated. ' + 237 | 'Please switch to promises or async/await syntax') 238 | 239 | // fn(context, callback) 240 | promise = new Promise((resolve) => { 241 | fn(context, function (err, data) { 242 | if (err) { 243 | context.error(err) 244 | } 245 | data = data || {} 246 | resolve(data) 247 | }) 248 | }) 249 | } else { 250 | promise = fn(context) 251 | } 252 | if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) { 253 | promise = Promise.resolve(promise) 254 | } 255 | return promise 256 | } 257 | 258 | // Imported from vue-router 259 | export function getLocation(base, mode) { 260 | let path = decodeURI(window.location.pathname) 261 | if (mode === 'hash') { 262 | return window.location.hash.replace(/^#\//, '') 263 | } 264 | if (base && path.indexOf(base) === 0) { 265 | path = path.slice(base.length) 266 | } 267 | return (path || '/') + window.location.search + window.location.hash 268 | } 269 | 270 | export function urlJoin() { 271 | return Array.prototype.slice.call(arguments).join('/').replace(/\/+/g, '/') 272 | } 273 | 274 | // Imported from path-to-regexp 275 | 276 | /** 277 | * Compile a string to a template function for the path. 278 | * 279 | * @param {string} str 280 | * @param {Object=} options 281 | * @return {!function(Object=, Object=)} 282 | */ 283 | export function compile(str, options) { 284 | return tokensToFunction(parse(str, options)) 285 | } 286 | 287 | export function getQueryDiff(toQuery, fromQuery) { 288 | const diff = {} 289 | const queries = { ...toQuery, ...fromQuery } 290 | for (const k in queries) { 291 | if (String(toQuery[k]) !== String(fromQuery[k])) { 292 | diff[k] = true 293 | } 294 | } 295 | return diff 296 | } 297 | 298 | export function normalizeError(err) { 299 | let message 300 | if (!(err.message || typeof err === 'string')) { 301 | try { 302 | message = JSON.stringify(err, null, 2) 303 | } catch (e) { 304 | message = `[${err.constructor.name}]` 305 | } 306 | } else { 307 | message = err.message || err 308 | } 309 | return { 310 | ...err, 311 | message, 312 | statusCode: (err.statusCode || err.status || (err.response && err.response.status) || 500) 313 | } 314 | } 315 | 316 | /** 317 | * The main path matching regexp utility. 318 | * 319 | * @type {RegExp} 320 | */ 321 | const PATH_REGEXP = new RegExp([ 322 | // Match escaped characters that would otherwise appear in future matches. 323 | // This allows the user to escape special characters that won't transform. 324 | '(\\\\.)', 325 | // Match Express-style parameters and un-named parameters with a prefix 326 | // and optional suffixes. Matches appear as: 327 | // 328 | // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] 329 | // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] 330 | // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] 331 | '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' 332 | ].join('|'), 'g') 333 | 334 | /** 335 | * Parse a string for the raw tokens. 336 | * 337 | * @param {string} str 338 | * @param {Object=} options 339 | * @return {!Array} 340 | */ 341 | function parse(str, options) { 342 | const tokens = [] 343 | let key = 0 344 | let index = 0 345 | let path = '' 346 | const defaultDelimiter = (options && options.delimiter) || '/' 347 | let res 348 | 349 | while ((res = PATH_REGEXP.exec(str)) != null) { 350 | const m = res[0] 351 | const escaped = res[1] 352 | const offset = res.index 353 | path += str.slice(index, offset) 354 | index = offset + m.length 355 | 356 | // Ignore already escaped sequences. 357 | if (escaped) { 358 | path += escaped[1] 359 | continue 360 | } 361 | 362 | const next = str[index] 363 | const prefix = res[2] 364 | const name = res[3] 365 | const capture = res[4] 366 | const group = res[5] 367 | const modifier = res[6] 368 | const asterisk = res[7] 369 | 370 | // Push the current path onto the tokens. 371 | if (path) { 372 | tokens.push(path) 373 | path = '' 374 | } 375 | 376 | const partial = prefix != null && next != null && next !== prefix 377 | const repeat = modifier === '+' || modifier === '*' 378 | const optional = modifier === '?' || modifier === '*' 379 | const delimiter = res[2] || defaultDelimiter 380 | const pattern = capture || group 381 | 382 | tokens.push({ 383 | name: name || key++, 384 | prefix: prefix || '', 385 | delimiter, 386 | optional, 387 | repeat, 388 | partial, 389 | asterisk: Boolean(asterisk), 390 | pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?') 391 | }) 392 | } 393 | 394 | // Match any characters still remaining. 395 | if (index < str.length) { 396 | path += str.substr(index) 397 | } 398 | 399 | // If the path exists, push it onto the end. 400 | if (path) { 401 | tokens.push(path) 402 | } 403 | 404 | return tokens 405 | } 406 | 407 | /** 408 | * Prettier encoding of URI path segments. 409 | * 410 | * @param {string} 411 | * @return {string} 412 | */ 413 | function encodeURIComponentPretty(str) { 414 | return encodeURI(str).replace(/[/?#]/g, (c) => { 415 | return '%' + c.charCodeAt(0).toString(16).toUpperCase() 416 | }) 417 | } 418 | 419 | /** 420 | * Encode the asterisk parameter. Similar to `pretty`, but allows slashes. 421 | * 422 | * @param {string} 423 | * @return {string} 424 | */ 425 | function encodeAsterisk(str) { 426 | return encodeURI(str).replace(/[?#]/g, (c) => { 427 | return '%' + c.charCodeAt(0).toString(16).toUpperCase() 428 | }) 429 | } 430 | 431 | /** 432 | * Expose a method for transforming tokens into the path function. 433 | */ 434 | function tokensToFunction(tokens) { 435 | // Compile all the tokens into regexps. 436 | const matches = new Array(tokens.length) 437 | 438 | // Compile all the patterns before compilation. 439 | for (let i = 0; i < tokens.length; i++) { 440 | if (typeof tokens[i] === 'object') { 441 | matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$') 442 | } 443 | } 444 | 445 | return function (obj, opts) { 446 | let path = '' 447 | const data = obj || {} 448 | const options = opts || {} 449 | const encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent 450 | 451 | for (let i = 0; i < tokens.length; i++) { 452 | const token = tokens[i] 453 | 454 | if (typeof token === 'string') { 455 | path += token 456 | 457 | continue 458 | } 459 | 460 | const value = data[token.name || 'pathMatch'] 461 | let segment 462 | 463 | if (value == null) { 464 | if (token.optional) { 465 | // Prepend partial segment prefixes. 466 | if (token.partial) { 467 | path += token.prefix 468 | } 469 | 470 | continue 471 | } else { 472 | throw new TypeError('Expected "' + token.name + '" to be defined') 473 | } 474 | } 475 | 476 | if (Array.isArray(value)) { 477 | if (!token.repeat) { 478 | throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`') 479 | } 480 | 481 | if (value.length === 0) { 482 | if (token.optional) { 483 | continue 484 | } else { 485 | throw new TypeError('Expected "' + token.name + '" to not be empty') 486 | } 487 | } 488 | 489 | for (let j = 0; j < value.length; j++) { 490 | segment = encode(value[j]) 491 | 492 | if (!matches[i].test(segment)) { 493 | throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`') 494 | } 495 | 496 | path += (j === 0 ? token.prefix : token.delimiter) + segment 497 | } 498 | 499 | continue 500 | } 501 | 502 | segment = token.asterisk ? encodeAsterisk(value) : encode(value) 503 | 504 | if (!matches[i].test(segment)) { 505 | throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"') 506 | } 507 | 508 | path += token.prefix + segment 509 | } 510 | 511 | return path 512 | } 513 | } 514 | 515 | /** 516 | * Escape a regular expression string. 517 | * 518 | * @param {string} str 519 | * @return {string} 520 | */ 521 | function escapeString(str) { 522 | return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1') 523 | } 524 | 525 | /** 526 | * Escape the capturing group by escaping special characters and meaning. 527 | * 528 | * @param {string} group 529 | * @return {string} 530 | */ 531 | function escapeGroup(group) { 532 | return group.replace(/([=!:$/()])/g, '\\$1') 533 | } 534 | 535 | /** 536 | * Format given url, append query to url query string 537 | * 538 | * @param {string} url 539 | * @param {string} query 540 | * @return {string} 541 | */ 542 | function formatUrl(url, query) { 543 | let protocol 544 | const index = url.indexOf('://') 545 | if (index !== -1) { 546 | protocol = url.substring(0, index) 547 | url = url.substring(index + 3) 548 | } else if (url.startsWith('//')) { 549 | url = url.substring(2) 550 | } 551 | 552 | let parts = url.split('/') 553 | let result = (protocol ? protocol + '://' : '//') + parts.shift() 554 | 555 | let path = parts.filter(Boolean).join('/') 556 | let hash 557 | parts = path.split('#') 558 | if (parts.length === 2) { 559 | [path, hash] = parts 560 | } 561 | 562 | result += path ? '/' + path : '' 563 | 564 | if (query && JSON.stringify(query) !== '{}') { 565 | result += (url.split('?').length === 2 ? '&' : '?') + formatQuery(query) 566 | } 567 | result += hash ? '#' + hash : '' 568 | 569 | return result 570 | } 571 | 572 | /** 573 | * Transform data object to query string 574 | * 575 | * @param {object} query 576 | * @return {string} 577 | */ 578 | function formatQuery(query) { 579 | return Object.keys(query).sort().map((key) => { 580 | const val = query[key] 581 | if (val == null) { 582 | return '' 583 | } 584 | if (Array.isArray(val)) { 585 | return val.slice().map(val2 => [key, '=', val2].join('')).join('&') 586 | } 587 | return key + '=' + val 588 | }).filter(Boolean).join('&') 589 | } 590 | -------------------------------------------------------------------------------- /pages/article/_id.vue: -------------------------------------------------------------------------------- 1 | 116 | 117 | 345 | 346 | 717 | --------------------------------------------------------------------------------