初始框架(1.0.0)
This commit is contained in:
3
.browserslistrc
Normal file
3
.browserslistrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
|
not dead
|
19
.eslintrc.js
Normal file
19
.eslintrc.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// const isProduction = (process.env.NODE_ENV === 'production');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
'extends': [
|
||||||
|
'plugin:vue/essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@babel/eslint-parser'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// 'no-console': (isProduction ? 'warn' : 'off'),
|
||||||
|
// 'no-debugger': (isProduction ? 'warn' : 'off'),
|
||||||
|
}
|
||||||
|
};
|
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
};
|
24
jsconfig.json
Normal file
24
jsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
],
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sourceMap": false,
|
||||||
|
"target": "es5"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.vue",
|
||||||
|
"src/**/*.js"
|
||||||
|
]
|
||||||
|
}
|
19861
package-lock.json
generated
Normal file
19861
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
package.json
Normal file
45
package.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "frost-zx-blog",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve --mode development",
|
||||||
|
"serve-production": "vue-cli-service serve --mode production",
|
||||||
|
"serve-all": "vue-cli-service serve --mode development --host 0.0.0.0 ",
|
||||||
|
"serve-local": "vue-cli-service serve --mode development --host 127.0.0.1",
|
||||||
|
"build": "vue-cli-service build --mode production",
|
||||||
|
"build-modern": "vue-cli-service build --mode production --modern",
|
||||||
|
"lint-check": "vue-cli-service lint --no-fix",
|
||||||
|
"lint-fix": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mdi/font": "^6.5.95",
|
||||||
|
"axios": "^0.26.0",
|
||||||
|
"buefy": "^0.9.17",
|
||||||
|
"core-js": "^3.8.3",
|
||||||
|
"github-markdown-css": "^5.1.0",
|
||||||
|
"highlight.js": "^11.4.0",
|
||||||
|
"markdown-it": "^12.3.2",
|
||||||
|
"ress": "^4.0.0",
|
||||||
|
"vue": "^2.6.14",
|
||||||
|
"vue-router": "^3.5.1",
|
||||||
|
"vuex": "^3.6.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.12.16",
|
||||||
|
"@babel/eslint-parser": "^7.12.16",
|
||||||
|
"@types/markdown-it": "^12.2.3",
|
||||||
|
"@vue/cli-plugin-babel": "~5.0.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||||
|
"@vue/cli-plugin-router": "~5.0.0",
|
||||||
|
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||||
|
"@vue/cli-service": "~5.0.0",
|
||||||
|
"eslint": "^7.32.0",
|
||||||
|
"eslint-plugin-vue": "^8.0.3",
|
||||||
|
"less": "^4.0.0",
|
||||||
|
"less-loader": "^8.0.0",
|
||||||
|
"sass": "^1.32.7",
|
||||||
|
"sass-loader": "^12.0.0",
|
||||||
|
"vue-template-compiler": "^2.6.14"
|
||||||
|
}
|
||||||
|
}
|
5
public/config.js
Normal file
5
public/config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
var appConfig = {
|
||||||
|
|
||||||
|
contentList: [],
|
||||||
|
|
||||||
|
};
|
1
public/contents/_index/contents.md
Normal file
1
public/contents/_index/contents.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
请在左侧的文章目录选择内容。
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
16
public/index.html
Normal file
16
public/index.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<meta name="description" content="一个使用 Vue + markdown-it 开发实现的静态博客。">
|
||||||
|
<meta name="keywords" content="blog,frost-zx,markdown,博客,静态博客">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title>Frost-ZX 的主页</title>
|
||||||
|
<script src="./config.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
86
src/App.vue
Normal file
86
src/App.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
|
||||||
|
<!-- 导航栏 -->
|
||||||
|
<b-navbar class="app-header">
|
||||||
|
|
||||||
|
<template #brand>
|
||||||
|
<b-navbar-item tag="router-link" :to="{ name: 'Home' }">
|
||||||
|
<img src="./assets/image/avatar.png" alt="Avatar" />
|
||||||
|
</b-navbar-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #start>
|
||||||
|
<b-navbar-item
|
||||||
|
tag="router-link"
|
||||||
|
:to="{ name: 'Home' }"
|
||||||
|
:active="routeName === 'Home'"
|
||||||
|
>主页</b-navbar-item>
|
||||||
|
<b-navbar-item
|
||||||
|
tag="router-link"
|
||||||
|
:to="{ name: 'ContentIndex' }"
|
||||||
|
:active="['Content','ContentIndex'].includes(routeName)"
|
||||||
|
>文章</b-navbar-item>
|
||||||
|
<b-navbar-item
|
||||||
|
tag="router-link"
|
||||||
|
:to="{ name: 'About' }"
|
||||||
|
:active="routeName === 'About'"
|
||||||
|
>关于</b-navbar-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #end>
|
||||||
|
<b-navbar-item tag="div">
|
||||||
|
<a
|
||||||
|
class="button is-light"
|
||||||
|
href="https://github.com/Frost-ZX"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<b-icon icon="github"></b-icon>
|
||||||
|
<span>GitHub</span>
|
||||||
|
</a>
|
||||||
|
</b-navbar-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</b-navbar>
|
||||||
|
|
||||||
|
<!-- 页面内容 -->
|
||||||
|
<router-view class="app-content" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
/** 当前路由名称 */
|
||||||
|
routeName(vm) {
|
||||||
|
return vm.$route.name;
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
#app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 0 1rem 0.25rem rgba(10, 10, 10, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
</style>
|
27
src/assets/css/buefy.scss
Normal file
27
src/assets/css/buefy.scss
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
@charset "utf-8";
|
||||||
|
|
||||||
|
// https://buefy.org/documentation/customization
|
||||||
|
// https://bulma.io/documentation/customize/variables/
|
||||||
|
|
||||||
|
// 导入 Bulma 的核心样式
|
||||||
|
@import "~bulma/sass/utilities/_all";
|
||||||
|
|
||||||
|
// 自定义颜色
|
||||||
|
$primary: #1976D2;
|
||||||
|
|
||||||
|
// 设置链接颜色
|
||||||
|
$link: $primary;
|
||||||
|
$link-invert: $primary-invert;
|
||||||
|
$link-focus-border: $primary;
|
||||||
|
|
||||||
|
// 更新 map(与 Bulma 的默认值相同)
|
||||||
|
// $colors
|
||||||
|
$colors: mergeColorMaps(("white": ($white, $black), "black": ($black, $white), "light": ($light, $light-invert), "dark": ($dark, $dark-invert), "primary": ($primary, $primary-invert, $primary-light, $primary-dark), "link": ($link, $link-invert, $link-light, $link-dark), "info": ($info, $info-invert, $info-light, $info-dark), "success": ($success, $success-invert, $success-light, $success-dark), "warning": ($warning, $warning-invert, $warning-light, $warning-dark), "danger": ($danger, $danger-invert, $danger-light, $danger-dark)), $custom-colors);
|
||||||
|
// $shades
|
||||||
|
$shades: mergeColorMaps(("black-bis": $black-bis, "black-ter": $black-ter, "grey-darker": $grey-darker, "grey-dark": $grey-dark, "grey": $grey, "grey-light": $grey-light, "grey-lighter": $grey-lighter, "white-ter": $white-ter, "white-bis": $white-bis), $custom-shades);
|
||||||
|
// $sizes
|
||||||
|
$sizes: $size-1 $size-2 $size-3 $size-4 $size-5 $size-6 $size-7;
|
||||||
|
|
||||||
|
// 导入 Bulma 和 Buefy 的样式
|
||||||
|
@import "~bulma/bulma.sass";
|
||||||
|
@import "~buefy/src/scss/buefy";
|
42
src/assets/css/main.less
Normal file
42
src/assets/css/main.less
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
:root {
|
||||||
|
font-size: 16px;
|
||||||
|
--color-primary: #1976D2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动条
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 1rem;
|
||||||
|
background-color: #DFDFDF;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #CCCCCC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, #app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #FFF;
|
||||||
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buefy */
|
||||||
|
|
||||||
|
// 导航栏
|
||||||
|
.navbar-item.is-active {
|
||||||
|
color: inherit !important;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 侧边栏
|
||||||
|
.sidebar-content {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
BIN
src/assets/image/avatar.png
Normal file
BIN
src/assets/image/avatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
BIN
src/assets/image/home-bg-full.jpg
Normal file
BIN
src/assets/image/home-bg-full.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 MiB |
BIN
src/assets/image/home-bg.jpg
Normal file
BIN
src/assets/image/home-bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 225 KiB |
43
src/assets/js/utils.js
Normal file
43
src/assets/js/utils.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { ToastProgrammatic as Toast } from 'buefy';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef ColorType
|
||||||
|
* @type {'is-white'|'is-black'|'is-light'|'is-dark'|'is-primary'|'is-info'|'is-success'|'is-warning'|'is-danger'}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 设置标题
|
||||||
|
* @param {string} [title] 标题内容
|
||||||
|
*/
|
||||||
|
export const setTitle = (title) => {
|
||||||
|
if (title) {
|
||||||
|
document.title = `${title} - Frost-ZX`;
|
||||||
|
} else {
|
||||||
|
document.title = 'Frost-ZX 的主页';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Toast
|
||||||
|
* @param {object} options
|
||||||
|
* @param {number} [options.duration] 时长(毫秒)
|
||||||
|
* @param {boolean} [options.indefinite] 无限时长
|
||||||
|
* @param {string} options.message 消息内容
|
||||||
|
* @param {ColorType} [options.type] 消息类型
|
||||||
|
*/
|
||||||
|
export const toast = (options) => {
|
||||||
|
const {
|
||||||
|
duration = 2000,
|
||||||
|
indefinite = false,
|
||||||
|
message = '',
|
||||||
|
type = 'is-info',
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
return Toast.open({
|
||||||
|
duration,
|
||||||
|
indefinite,
|
||||||
|
message,
|
||||||
|
pauseOnHover: true,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
};
|
265
src/components/MarkdownParser.vue
Normal file
265
src/components/MarkdownParser.vue
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
<template>
|
||||||
|
<div v-html="mdResult" class="markdown-body"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// https://www.npmjs.com/package/github-markdown-css
|
||||||
|
// https://www.npmjs.com/package/highlight.js
|
||||||
|
// https://www.npmjs.com/package/markdown-it
|
||||||
|
|
||||||
|
import MarkdownIt from 'markdown-it';
|
||||||
|
import hljs from 'highlight.js/lib/common';
|
||||||
|
|
||||||
|
import 'github-markdown-css/github-markdown-light.css';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MarkdownParser',
|
||||||
|
props: {
|
||||||
|
|
||||||
|
/** Markdown 源内容 */
|
||||||
|
mdSrc: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
/** @type {markdownit} */
|
||||||
|
mdInstance: null,
|
||||||
|
|
||||||
|
/** Markdown 转换结果 */
|
||||||
|
mdResult: '',
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
|
||||||
|
// 自动渲染
|
||||||
|
mdSrc: {
|
||||||
|
handler(value) {
|
||||||
|
this.render(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initMD();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
/** 初始化实例 */
|
||||||
|
initMD() {
|
||||||
|
|
||||||
|
const vm = this;
|
||||||
|
|
||||||
|
/** @type {markdownit.Options} */
|
||||||
|
const options = {
|
||||||
|
html: true,
|
||||||
|
xhtmlOut: true,
|
||||||
|
breaks: true,
|
||||||
|
langPrefix: 'language-',
|
||||||
|
linkify: false,
|
||||||
|
highlight: (str, lang) => {
|
||||||
|
|
||||||
|
const tagPrefix = `<pre><code class="hljs language-${lang}">`;
|
||||||
|
const tagSuffix = '</code></pre>';
|
||||||
|
|
||||||
|
let content = '';
|
||||||
|
let highlighted = false;
|
||||||
|
|
||||||
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
|
try {
|
||||||
|
// 高亮成功
|
||||||
|
content = hljs.highlight(str, {
|
||||||
|
language: lang,
|
||||||
|
ignoreIllegals: true,
|
||||||
|
}).value;
|
||||||
|
highlighted = true;
|
||||||
|
} catch (error) {
|
||||||
|
// 高亮失败
|
||||||
|
highlighted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!highlighted) {
|
||||||
|
const md = vm.mdInstance;
|
||||||
|
content = (md ? md.utils.escapeHtml(str) : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${tagPrefix}${content}${tagSuffix}`;
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {markdownit} */
|
||||||
|
const md = new MarkdownIt(options);
|
||||||
|
|
||||||
|
this.mdInstance = md;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 渲染内容
|
||||||
|
* @param {string} [mdSrc]
|
||||||
|
*/
|
||||||
|
render(mdSrc) {
|
||||||
|
|
||||||
|
const { mdInstance: md } = this;
|
||||||
|
|
||||||
|
if (md) {
|
||||||
|
this.mdResult = md.render(mdSrc || this.mdSrc);
|
||||||
|
} else {
|
||||||
|
console.warn('实例不存在,取消渲染。');
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.markdown-body {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2.5rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 12.5rem;
|
||||||
|
max-width: 60rem;
|
||||||
|
user-select: text;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
box-shadow: 0 0 0.75rem rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/*
|
||||||
|
https://highlightjs.org/static/demo/
|
||||||
|
https://highlightjs.org/static/demo/styles/github.css
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Theme: GitHub
|
||||||
|
Description: Light theme as seen on github.com
|
||||||
|
Author: github.com
|
||||||
|
Maintainer: @Hirse
|
||||||
|
Updated: 2021-05-15
|
||||||
|
|
||||||
|
Outdated base version: https://github.com/primer/github-syntax-light
|
||||||
|
Current colors taken from GitHub's CSS
|
||||||
|
*/
|
||||||
|
|
||||||
|
pre code.hljs {
|
||||||
|
display: block;
|
||||||
|
/* padding: 1em; */
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
code.hljs {
|
||||||
|
padding: 3px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
color: #24292E;
|
||||||
|
background: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-doctag,
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-meta .hljs-keyword,
|
||||||
|
.hljs-template-tag,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-variable.language_ {
|
||||||
|
color: #D73A49;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-title.class_,
|
||||||
|
.hljs-title.class_.inherited__,
|
||||||
|
.hljs-title.function_ {
|
||||||
|
color: #6F42C1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-attr,
|
||||||
|
.hljs-attribute,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-operator,
|
||||||
|
.hljs-selector-attr,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-variable {
|
||||||
|
color: #005CC5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-meta .hljs-string,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-string {
|
||||||
|
color: #032F62;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-symbol {
|
||||||
|
color: #E36209;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-code,
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-formula {
|
||||||
|
color: #6A737D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-quote,
|
||||||
|
.hljs-selector-pseudo,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
color: #22863A;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-subst {
|
||||||
|
color: #24292E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-section {
|
||||||
|
color: #005CC5;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-bullet {
|
||||||
|
color: #735C0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
color: #24292E;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
color: #24292E;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-addition {
|
||||||
|
color: #22863A;
|
||||||
|
background-color: #F0FFF4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-deletion {
|
||||||
|
color: #B31D28;
|
||||||
|
background-color: #FFEEF0;
|
||||||
|
}
|
||||||
|
</style>
|
25
src/main.js
Normal file
25
src/main.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import Buefy from 'buefy'
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import App from './App.vue';
|
||||||
|
import router from './router';
|
||||||
|
// import store from './store';
|
||||||
|
|
||||||
|
import 'ress/ress.css';
|
||||||
|
import '@mdi/font/css/materialdesignicons.css';
|
||||||
|
import '@/assets/css/buefy.scss';
|
||||||
|
import '@/assets/css/main.less';
|
||||||
|
|
||||||
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
Vue.use(Buefy, {
|
||||||
|
defaultIconPack: 'mdi',
|
||||||
|
defaultDialogConfirmText: '确定',
|
||||||
|
defaultDialogCancelText: '取消',
|
||||||
|
})
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
router,
|
||||||
|
// store,
|
||||||
|
render: h => h(App)
|
||||||
|
}).$mount('#app');
|
25
src/request/index.js
Normal file
25
src/request/index.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import request from './request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取正文内容文件
|
||||||
|
* @param {object} options
|
||||||
|
* @param {string} options.category 分类名称
|
||||||
|
* @param {string} options.itemName 内容名称
|
||||||
|
*/
|
||||||
|
export const getContentFile = (options) => {
|
||||||
|
|
||||||
|
const {
|
||||||
|
category,
|
||||||
|
itemName,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
if (category && itemName) {
|
||||||
|
return request({
|
||||||
|
url: `/contents/${category}/${itemName}.md`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
72
src/request/request.js
Normal file
72
src/request/request.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef RequestParams
|
||||||
|
* @type {Object.<string, (number|string|number[]|string[])}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL: '',
|
||||||
|
timeout: 10000,
|
||||||
|
validateStatus: function (status) {
|
||||||
|
return ((status >= 200 && status < 300) || status === 404);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拦截请求
|
||||||
|
instance.interceptors.request.use((config) => {
|
||||||
|
return config;
|
||||||
|
}, (error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拦截响应
|
||||||
|
instance.interceptors.response.use((response) => {
|
||||||
|
return response;
|
||||||
|
}, (error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 处理请求
|
||||||
|
* @param {object} options 配置选项
|
||||||
|
* @param {('get'|'post')} [options.method] 请求方式,默认 GET
|
||||||
|
* @param {string} options.url 请求地址
|
||||||
|
* @param {RequestParams} [options.datas] 请求体(请求方式为 POST 时可用)
|
||||||
|
* @param {RequestParams} [options.query] 查询参数
|
||||||
|
*/
|
||||||
|
const request = function (options = {}) {
|
||||||
|
|
||||||
|
const method = String(options.method || 'get').toLowerCase();
|
||||||
|
const {
|
||||||
|
url = null,
|
||||||
|
datas = null,
|
||||||
|
query = null,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
if (url === null) {
|
||||||
|
console.error('请求地址为空。');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === 'get') {
|
||||||
|
return instance({
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
} else if (method === 'post') {
|
||||||
|
return instance({
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
data: datas,
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('请求方式错误。');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default request;
|
39
src/router/index.js
Normal file
39
src/router/index.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
import VueRouter from 'vue-router';
|
||||||
|
|
||||||
|
import routes from './routes';
|
||||||
|
|
||||||
|
import { toast } from '@/assets/js/utils';
|
||||||
|
|
||||||
|
const {
|
||||||
|
push: routerPush,
|
||||||
|
replace: routerReplace,
|
||||||
|
} = VueRouter.prototype;
|
||||||
|
|
||||||
|
const errorHandler = function (error) {
|
||||||
|
console.warn(String(error));
|
||||||
|
};
|
||||||
|
|
||||||
|
VueRouter.prototype.push = function (location) {
|
||||||
|
return routerPush.call(this, location).catch(errorHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
VueRouter.prototype.replace = function (location) {
|
||||||
|
return routerReplace.call(this, location).catch(errorHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
routes
|
||||||
|
});
|
||||||
|
|
||||||
|
router.onError(() => {
|
||||||
|
toast({
|
||||||
|
duration: 5000,
|
||||||
|
message: '页面跳转失败,请检查网络连接情况。',
|
||||||
|
type: 'is-danger',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
33
src/router/routes.js
Normal file
33
src/router/routes.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import AboutView from '@/views/AboutView.vue';
|
||||||
|
import ContentView from '@/views/ContentView.vue';
|
||||||
|
import HomeView from '@/views/HomeView.vue';
|
||||||
|
|
||||||
|
/** @type { import('vue-router').RouteConfig[] } */
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Home',
|
||||||
|
component: HomeView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/content',
|
||||||
|
name: 'ContentIndex',
|
||||||
|
component: ContentView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/content/:category/:name',
|
||||||
|
name: 'Content',
|
||||||
|
component: ContentView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'About',
|
||||||
|
component: AboutView,
|
||||||
|
// component: () => import(
|
||||||
|
// /* webpackChunkName: 'about' */
|
||||||
|
// '@/views/About.vue'
|
||||||
|
// ),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
17
src/store/index.js
Normal file
17
src/store/index.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
import Vuex from 'vuex';
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
export default new Vuex.Store({
|
||||||
|
state: {
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
}
|
||||||
|
});
|
35
src/views/AboutView.vue
Normal file
35
src/views/AboutView.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about-view">
|
||||||
|
<div class="about-wrapper markdown-body">
|
||||||
|
|
||||||
|
<h2>关于本站</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>使用 Vue CLI 5.0 搭建,基于 Vue 2.X,集成 Vue Router 前端路由。</li>
|
||||||
|
<li>采用了基于 Bulma CSS 框架的 Buefy 组件库。</li>
|
||||||
|
<li>使用了 axios,用于读取 Markdown 文件。</li>
|
||||||
|
<li>使用了 markdown-it,用于将 Markdown 转换为 HTML。</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import 'github-markdown-css/github-markdown-light.css';
|
||||||
|
|
||||||
|
import { setTitle } from '@/assets/js/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AboutView',
|
||||||
|
created() {
|
||||||
|
setTitle('关于');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.about-wrapper {
|
||||||
|
padding: 2.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
387
src/views/ContentView.vue
Normal file
387
src/views/ContentView.vue
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
<template>
|
||||||
|
<div class="content-view" :class="{ 'show-sidebar': isShowSidebar }">
|
||||||
|
|
||||||
|
<!-- 侧边栏 -->
|
||||||
|
<b-sidebar
|
||||||
|
class="content-sidebar"
|
||||||
|
mobile="hide"
|
||||||
|
position="static"
|
||||||
|
type="is-light"
|
||||||
|
:open="true"
|
||||||
|
:reduce="false"
|
||||||
|
fullheight
|
||||||
|
>
|
||||||
|
<b-menu :accordion="false" :activable="false">
|
||||||
|
|
||||||
|
<b-menu-list label="文章信息">
|
||||||
|
<!-- 创建日期 -->
|
||||||
|
<b-menu-item
|
||||||
|
icon="creation"
|
||||||
|
:label="'创建:' + contentInfo.createdAt"
|
||||||
|
></b-menu-item>
|
||||||
|
<!-- 更新日期 -->
|
||||||
|
<b-menu-item
|
||||||
|
icon="update"
|
||||||
|
:label="'更新:' + contentInfo.updatedAt"
|
||||||
|
></b-menu-item>
|
||||||
|
</b-menu-list>
|
||||||
|
|
||||||
|
<b-menu-list label="文章目录">
|
||||||
|
<!-- 分类项 -->
|
||||||
|
<b-menu-item
|
||||||
|
v-for="category in contentList"
|
||||||
|
v-show="!category.isHide"
|
||||||
|
:key="category.name"
|
||||||
|
:active="category.name === contentInfo.category"
|
||||||
|
:expanded="Boolean(category.isExpanded)"
|
||||||
|
:label="category.label"
|
||||||
|
icon="format-list-bulleted-square"
|
||||||
|
>
|
||||||
|
<!-- 内容项 -->
|
||||||
|
<b-menu-item
|
||||||
|
v-for="item in category.items"
|
||||||
|
:key="item.name"
|
||||||
|
:active="(
|
||||||
|
category.name === contentInfo.category && item.name === contentInfo.itemName
|
||||||
|
)"
|
||||||
|
:label="item.title"
|
||||||
|
icon="file-document-outline"
|
||||||
|
@click="changePage(category.name, item.name)"
|
||||||
|
></b-menu-item>
|
||||||
|
</b-menu-item>
|
||||||
|
</b-menu-list>
|
||||||
|
|
||||||
|
</b-menu>
|
||||||
|
</b-sidebar>
|
||||||
|
|
||||||
|
<div class="sidebar-toggle" @click="toggleSidebar()">
|
||||||
|
<b-icon v-if="isShowSidebar" icon="chevron-left" size="is-small" />
|
||||||
|
<b-icon v-else icon="chevron-right" size="is-small" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<markdown-parser :md-src="contentData" />
|
||||||
|
<b-loading
|
||||||
|
:active="isLoading"
|
||||||
|
:can-cancel="false"
|
||||||
|
:is-full-page="false"
|
||||||
|
></b-loading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { setTitle, toast } from '@/assets/js/utils';
|
||||||
|
import { getContentFile } from '@/request/index';
|
||||||
|
|
||||||
|
import MarkdownParser from '@/components/MarkdownParser';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ContentView',
|
||||||
|
components: {
|
||||||
|
MarkdownParser,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
/** 是否正在载入内容 */
|
||||||
|
isLoading: false,
|
||||||
|
|
||||||
|
/** 是否在移动端显示侧边栏 */
|
||||||
|
isShowSidebar: false,
|
||||||
|
|
||||||
|
/** 内容正文 */
|
||||||
|
contentData: '',
|
||||||
|
|
||||||
|
/** 内容信息 */
|
||||||
|
contentInfo: {
|
||||||
|
category: '',
|
||||||
|
createdAt: '',
|
||||||
|
updatedAt: '',
|
||||||
|
itemName: '',
|
||||||
|
itemtitle: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 内容列表 */
|
||||||
|
contentList: [
|
||||||
|
{
|
||||||
|
name: '_index',
|
||||||
|
label: '索引页面',
|
||||||
|
isHide: true,
|
||||||
|
items: [{
|
||||||
|
name: 'contents',
|
||||||
|
title: '文章页面',
|
||||||
|
createdAt: '',
|
||||||
|
updatedAt: '',
|
||||||
|
}],
|
||||||
|
isExpanded: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
|
||||||
|
// 切换页面时刷新内容
|
||||||
|
'$route.path': {
|
||||||
|
handler() {
|
||||||
|
this.updateContents();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
init() {
|
||||||
|
|
||||||
|
const config = window['appConfig'];
|
||||||
|
|
||||||
|
if (config) {
|
||||||
|
|
||||||
|
const listCurr = JSON.parse(JSON.stringify(this.contentList));
|
||||||
|
const listConfig = (config.contentList || []);
|
||||||
|
|
||||||
|
listConfig.forEach((item) => {
|
||||||
|
listCurr.push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.contentList = listCurr;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
indefinite: true,
|
||||||
|
message: '读取配置文件失败!',
|
||||||
|
type: 'is-danger',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateContents();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 切换页面 */
|
||||||
|
changePage(category, itemName) {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'Content',
|
||||||
|
params: {
|
||||||
|
category: category,
|
||||||
|
name: itemName,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取正文内容文件
|
||||||
|
* @param {object} options
|
||||||
|
* @param {string} options.category 分类名称
|
||||||
|
* @param {string} options.itemName 内容名称
|
||||||
|
*/
|
||||||
|
getContentFile(options) {
|
||||||
|
|
||||||
|
this.setLoading(true);
|
||||||
|
this.updateContentInfo(options);
|
||||||
|
|
||||||
|
getContentFile(options).then((res) => {
|
||||||
|
|
||||||
|
this.setLoading(false);
|
||||||
|
|
||||||
|
const { status, data } = res;
|
||||||
|
|
||||||
|
if (status === 200) {
|
||||||
|
this.contentData = (data || '');
|
||||||
|
} else if (status === 404) {
|
||||||
|
toast({
|
||||||
|
duration: 3000,
|
||||||
|
message: `内容 /${options.category}/${options.itemName}.md 不存在。`,
|
||||||
|
type: 'is-warning',
|
||||||
|
});
|
||||||
|
this.contentData = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch((error) => {
|
||||||
|
|
||||||
|
console.error('[请求失败]', error);
|
||||||
|
toast({
|
||||||
|
duration: 3000,
|
||||||
|
message: '请求失败!',
|
||||||
|
type: 'is-danger',
|
||||||
|
});
|
||||||
|
this.setLoading(false);
|
||||||
|
this.contentData = '';
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 设置载入状态
|
||||||
|
* @param {boolean} isLoading
|
||||||
|
*/
|
||||||
|
setLoading(isLoading = false) {
|
||||||
|
this.isLoading = isLoading;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 切换侧边栏显示隐藏 */
|
||||||
|
toggleSidebar() {
|
||||||
|
this.isShowSidebar = !this.isShowSidebar;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 更新内容 */
|
||||||
|
updateContents() {
|
||||||
|
|
||||||
|
const {
|
||||||
|
name: routeName,
|
||||||
|
params: routeParams,
|
||||||
|
} = this.$route;
|
||||||
|
|
||||||
|
if (routeName === 'Content') {
|
||||||
|
this.getContentFile({
|
||||||
|
category: routeParams.category,
|
||||||
|
itemName: routeParams.name,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.getContentFile({
|
||||||
|
category: '_index',
|
||||||
|
itemName: 'contents',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 更新内容信息
|
||||||
|
* @param {object} options
|
||||||
|
* @param {string} options.category 分类名称
|
||||||
|
* @param {string} options.itemName 内容名称
|
||||||
|
*/
|
||||||
|
updateContentInfo(options) {
|
||||||
|
|
||||||
|
setTitle();
|
||||||
|
|
||||||
|
const {
|
||||||
|
category,
|
||||||
|
itemName,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
if (!(category && itemName)) {
|
||||||
|
console.error('更新内容信息失败!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { contentInfo, contentList } = this;
|
||||||
|
|
||||||
|
const categoryInfo = contentList.find((item) => {
|
||||||
|
return (item.name === category);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (categoryInfo) {
|
||||||
|
// 标记展开
|
||||||
|
categoryInfo.isExpanded = true;
|
||||||
|
} else {
|
||||||
|
console.error('获取分类信息失败!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemInfo = categoryInfo.items.find((item) => {
|
||||||
|
return (item.name === itemName);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemInfo) {
|
||||||
|
contentInfo.category = category;
|
||||||
|
contentInfo.createdAt = (itemInfo.createdAt || '无');
|
||||||
|
contentInfo.updatedAt = (itemInfo.updatedAt || '无');
|
||||||
|
contentInfo.itemName = (itemInfo.name || '');
|
||||||
|
contentInfo.itemtitle = (itemInfo.title || '无标题');
|
||||||
|
setTitle(itemInfo.title);
|
||||||
|
} else {
|
||||||
|
console.error('获取内容信息失败!');
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.content-view {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-sidebar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .sidebar-content {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
z-index: 11;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@sidebarBtnWidth: 1.25rem;
|
||||||
|
@sidebarBtnOffset: 1rem;
|
||||||
|
|
||||||
|
.sidebar-toggle {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0%;
|
||||||
|
z-index: 50;
|
||||||
|
width: @sidebarBtnWidth;
|
||||||
|
height: 4em;
|
||||||
|
box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 0 0.25rem 0.25rem 0;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: #FFF;
|
||||||
|
font-size: 1rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-sidebar {
|
||||||
|
.content-sidebar {
|
||||||
|
width: calc(100% - @sidebarBtnWidth - @sidebarBtnOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-toggle {
|
||||||
|
left: unset;
|
||||||
|
right: @sidebarBtnOffset;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .sidebar-content {
|
||||||
|
display: block !important;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
96
src/views/HomeView.vue
Normal file
96
src/views/HomeView.vue
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home-view">
|
||||||
|
|
||||||
|
<div class="bg-cover"></div>
|
||||||
|
|
||||||
|
<div class="center-content">
|
||||||
|
<div class="home-title">Frost-ZX 的主页</div>
|
||||||
|
<div class="home-desc">
|
||||||
|
<p>一个使用 Vue + Buefy + markdown-it 开发实现的静态博客。</p>
|
||||||
|
<p>
|
||||||
|
<span>背景图片来源:</span>
|
||||||
|
<a href="https://unsplash.com/photos/myq0GB_AAVU" target="_blank">Unsplash / Niels Cornet</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<b-button
|
||||||
|
type="is-light"
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
@click="toDetail()"
|
||||||
|
>了解更多</b-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { setTitle } from '@/assets/js/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HomeView',
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
toDetail() {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'ContentIndex'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
setTitle();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.home-view {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
background-color: #323232;
|
||||||
|
background-image: url("../assets/image/home-bg.jpg");
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-cover {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.35);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-content {
|
||||||
|
position: relative;
|
||||||
|
padding: 0 10%;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-title {
|
||||||
|
font-size: 2.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-desc {
|
||||||
|
margin: 0.75em 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
23
vue.config.js
Normal file
23
vue.config.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const { defineConfig } = require('@vue/cli-service');
|
||||||
|
|
||||||
|
const { npm_package_name: packageName } = process.env;
|
||||||
|
|
||||||
|
if (packageName) {
|
||||||
|
process.title = packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
|
||||||
|
assetsDir: 'static',
|
||||||
|
publicPath: './',
|
||||||
|
outputDir: 'dist',
|
||||||
|
|
||||||
|
productionSourceMap: false,
|
||||||
|
transpileDependencies: false,
|
||||||
|
|
||||||
|
devServer: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
port: 9000,
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
Reference in New Issue
Block a user