1
0

初始框架(1.0.0)

This commit is contained in:
2022-03-01 23:42:21 +08:00
parent 2a70a2bcee
commit ca2151429f
28 changed files with 21194 additions and 0 deletions

3
.browserslistrc Normal file
View File

@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

19
.eslintrc.js Normal file
View 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
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
};

24
jsconfig.json Normal file
View 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

File diff suppressed because it is too large Load Diff

45
package.json Normal file
View 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
View File

@@ -0,0 +1,5 @@
var appConfig = {
contentList: [],
};

View File

@@ -0,0 +1 @@
请在左侧的文章目录选择内容。

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

16
public/index.html Normal file
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

43
src/assets/js/utils.js Normal file
View 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,
});
};

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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,
},
});