初步完成页面搭建
This commit is contained in:
commit
aefd2850e6
4
.env.development
Normal file
4
.env.development
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
NODE_ENV = development
|
||||||
|
VITE_APP_TITLE = DEV标题
|
||||||
|
VITE_BASE_URL = /
|
||||||
|
VITE_API_BASE_URL = /api
|
||||||
4
.env.production
Normal file
4
.env.production
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
NODE_ENV = production
|
||||||
|
VITE_APP_TITLE = PROD标题
|
||||||
|
VITE_BASE_URL = ./
|
||||||
|
VITE_API_BASE_URL = /api
|
||||||
15
.eslintrc.cjs
Normal file
15
.eslintrc.cjs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-prettier/skip-formatting'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest'
|
||||||
|
},
|
||||||
|
rules: {}
|
||||||
|
}
|
||||||
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
8
.prettierrc.json
Normal file
8
.prettierrc.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
||||||
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"Vue.vscode-typescript-vue-plugin",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
44
README.md
Normal file
44
README.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# vue3-template
|
||||||
|
|
||||||
|
基于vite的vue3 项目基础模板
|
||||||
|
|
||||||
|
vue3:3.3.11
|
||||||
|
|
||||||
|
vue-router:4.2.5
|
||||||
|
|
||||||
|
pinia:2.1.7
|
||||||
|
|
||||||
|
element-plus:2.4.4
|
||||||
|
|
||||||
|
store持久化存储:pinia-plugin-persistedstate 3.2.1
|
||||||
|
https://prazdevs.github.io/pinia-plugin-persistedstate/guide/config.html
|
||||||
|
|
||||||
|
mitt:3.0.1
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
vite配置 [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||||
|
|
||||||
|
## 项目初始化
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行dev服务
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 构建项目
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### eslint检测 [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-hans">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
jsconfig.json
Normal file
8
jsconfig.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
6059
package-lock.json
generated
Normal file
6059
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
package.json
Normal file
36
package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "vue3-blog-frontend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --mode development",
|
||||||
|
"serve": "vite --mode development",
|
||||||
|
"build": "vite build --mode production",
|
||||||
|
"preview": "vite preview --mode development",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "~1.6.2",
|
||||||
|
"element-plus": "~2.4.4",
|
||||||
|
"mitt": "~3.0.1",
|
||||||
|
"pinia": "~2.1.7",
|
||||||
|
"pinia-plugin-persistedstate": "~3.2.1",
|
||||||
|
"qs": "~6.11.2",
|
||||||
|
"vue": "~3.3.11",
|
||||||
|
"vue-router": "~4.2.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rushstack/eslint-patch": "^1.3.3",
|
||||||
|
"@vitejs/plugin-vue": "^4.5.2",
|
||||||
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"sass": "^1.69.5",
|
||||||
|
"unplugin-auto-import": "^0.17.2",
|
||||||
|
"unplugin-vue-components": "^0.26.0",
|
||||||
|
"vite": "^5.0.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
45
public/json/pageList.json
Normal file
45
public/json/pageList.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "成功",
|
||||||
|
"data": {
|
||||||
|
"total": 4,
|
||||||
|
"pageList": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "测试123标题1231",
|
||||||
|
"author": "lau",
|
||||||
|
"createTime": "2024-01-21",
|
||||||
|
"category": "分类",
|
||||||
|
"abstract": "e是一种现代化的前端开发工具,其工作原理主要分为以下几个步骤:基于ESM构建:Vite作为一款基于ESM的前端构建工具,通过ES模块提供的动态导入功能来实现快速的开发和构建。零配置开发:Vite允许开发者在不需要任何配置的情况下启动一个服务器进行开发,通过对文件的即时编译和缓存,来提高开源代码加载模块,而不是打包后的文件,从而可以避免打包的过程带来的性能损失按需编译和缓存:Vite会按需编译和缓存",
|
||||||
|
"tags": ["标签1", "标签2123"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"title": "测试标题12321321",
|
||||||
|
"author": "lau",
|
||||||
|
"createTime": "2024-01-22",
|
||||||
|
"category": "分类",
|
||||||
|
"abstract": "e是一种现代化的前端开发工具,其工作原理主要分为以下几个步骤:基于ESM构建:Vite作为一款基于ESM的前端构建工具,通过ES模块提供的动态导入功能来实现快速的开发和构建。零配置开发:Vite允许开发者在不需要任何配置的情况下启动一个服务器进行开发,通过对文件的即时编译和缓存,来提高开发效率。基于浏览器原生的ESM加载:Vite将所有文件视为ES模块,并且在开发时会直接从源代码加载模块,而不是打包后的文件,从而可以避免打包的过程带来的性能损失按需编译和缓存:Vite会按需编译和缓存",
|
||||||
|
"tags": ["标签1", "标签2123"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"title": "测试标题123123323",
|
||||||
|
"author": "lau",
|
||||||
|
"createTime": "2024-01-24",
|
||||||
|
"category": "分类",
|
||||||
|
"abstract": "e是一种现代化的前端开发工具,其工作原理主要分为以下几个步骤:基于ESM构建:Vite作为一款基于ESM的前端构建工具,通过ES模块提供的动态导入功能来实现快速的开发和构建。零配置开发:Vite允许开发者在不需要任何配置的情况下启动一个服务器进行开发,通过对文件的即时编译和缓存,来提高开发效率。基于浏览器原生的ESM加载:Vite将所有文件视为ES模块,并且在开发时会直接从源代码加载模块,而不是打包后的文件,从而可以避免打包的过程带来的性能损失按需编译和缓存:Vite会按需编译和缓存",
|
||||||
|
"tags": ["标签1", "标签2123"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 44,
|
||||||
|
"title": "测试标题1234211234",
|
||||||
|
"author": "lau",
|
||||||
|
"createTime": "2024-01-24",
|
||||||
|
"category": "分类",
|
||||||
|
"abstract": "e是一种现代化的前端开发工具,其工作原理主要分为以下几个步骤:基于ESM构建:Vite作为一款基于ESM的前端构建工具,通过ES模块提供的动态导入功能来实现快速的开发和构建。零配置开发:Vite允许开发者在不需要任何配置的情况下启动一个服务器进行开发,通过对文件的即时编译和缓存,来提高开发效率。基于浏览器原生的ESM加载:Vite将所有文件视为ES模块,并且在开发时会直接从源代码加载模块,而不是打包后的文件,从而可以避免打包的过程带来的性能损失按需编译和缓存:Vite会按需编译和缓存",
|
||||||
|
"tags": ["标签1", "标签2123"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/App.vue
Normal file
7
src/App.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
17
src/api/home.js
Normal file
17
src/api/home.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import http from '@/utils/http'
|
||||||
|
|
||||||
|
/** 登录接口
|
||||||
|
* @function getPageList 获取文章列表
|
||||||
|
* @param {Number} currentPage 当前分页
|
||||||
|
* @param {Number} pageSize 每次获取数量
|
||||||
|
* */
|
||||||
|
export const getPageList = (currentPage = 1, pageSize = 10) => {
|
||||||
|
return http({
|
||||||
|
url: '/page-list',
|
||||||
|
method: 'post',
|
||||||
|
data: {
|
||||||
|
currentPage,
|
||||||
|
pageSize
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
15
src/api/login.js
Normal file
15
src/api/login.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import http from '@/utils/http'
|
||||||
|
|
||||||
|
/** 登录接口
|
||||||
|
* @function login 登录接口
|
||||||
|
* @param {Object} data 登录数据
|
||||||
|
* @param {String} data.account 账号
|
||||||
|
* @param {String} data.password 密码
|
||||||
|
* */
|
||||||
|
export const login = (data) => {
|
||||||
|
return http({
|
||||||
|
url: '/login',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
5
src/assets/base.css
Normal file
5
src/assets/base.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
body {
|
||||||
|
font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
|
||||||
|
'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
1
src/assets/logo.svg
Normal file
1
src/assets/logo.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||||
|
After Width: | Height: | Size: 276 B |
21
src/assets/reset.css
Normal file
21
src/assets/reset.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
html,
|
||||||
|
body,
|
||||||
|
ul,
|
||||||
|
ol,
|
||||||
|
li,
|
||||||
|
img,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
div {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul{
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
86
src/components/Button/TagCom.vue
Normal file
86
src/components/Button/TagCom.vue
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
type: String,
|
||||||
|
default: '1px solid #ddd'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#777'
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
type: String,
|
||||||
|
default: '14px'
|
||||||
|
},
|
||||||
|
fontWeight: {
|
||||||
|
type: String,
|
||||||
|
default: '400'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'normal'
|
||||||
|
},
|
||||||
|
cursor: {
|
||||||
|
type: String,
|
||||||
|
default: 'pointer'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const style = computed(() => {
|
||||||
|
const obj = {}
|
||||||
|
obj['width'] = props.width
|
||||||
|
obj['height'] = props.height
|
||||||
|
obj['color'] = props.color
|
||||||
|
obj['background-color'] = props.backgroundColor
|
||||||
|
obj['font-size'] = props.fontSize
|
||||||
|
obj['font-weight'] = props.fontWeight
|
||||||
|
obj['border'] = props.border
|
||||||
|
obj['cursor'] = props.cursor
|
||||||
|
switch (props.size) {
|
||||||
|
case 'normal':
|
||||||
|
obj['padding'] = '12px 20px'
|
||||||
|
break
|
||||||
|
case 'small':
|
||||||
|
obj['padding'] = '8px 14px'
|
||||||
|
break
|
||||||
|
case 'large':
|
||||||
|
obj['padding'] = '16px 24px'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!--
|
||||||
|
宽高、背景颜色、字体颜色、字体大小、字体粗细、边框大小和颜色、大小
|
||||||
|
-->
|
||||||
|
<button class="tag-item" :style="style">
|
||||||
|
<span>
|
||||||
|
<slot name="default"></slot>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tag-item {
|
||||||
|
border-radius: 8px;
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
45
src/components/Footer/FooterCom.vue
Normal file
45
src/components/Footer/FooterCom.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<script setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="footer">
|
||||||
|
<div class="copyright">
|
||||||
|
<p>
|
||||||
|
Copyright © 2016-{{ new Date().getFullYear() }}
|
||||||
|
<a href="https://git.6yim.com" target="_blank">Lauym</a>. All Rights Reserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="beian">
|
||||||
|
<a href="http://beian.miit.gov.cn/" target="_blank">蜀ICP备19012284号</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
padding: 24px 0;
|
||||||
|
background-color: #444;
|
||||||
|
color: #aaa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
// justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
> div {
|
||||||
|
a {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #777;
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover{
|
||||||
|
text-decoration: solid;
|
||||||
|
}
|
||||||
|
&:active{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .copyright {
|
||||||
|
// text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
146
src/components/Header/HeaderCom.vue
Normal file
146
src/components/Header/HeaderCom.vue
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import menuIcon from '@/components/Icon/btn/menuIcon.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
menuList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ label: '所有文章', path: '/pages', menuId: 1 },
|
||||||
|
{ label: '分类', path: '/categories', menuId: 2 },
|
||||||
|
{ label: '标签', path: '/tags', menuId: 3 },
|
||||||
|
{ label: '关于我', path: '/about-me', menuId: 4 },
|
||||||
|
{ label: '杂记', path: '/', menuId: 5 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const isMobileMenuShow = ref(false)
|
||||||
|
const handleShowMobileMenu = (value) => {
|
||||||
|
isMobileMenuShow.value = value
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="header">
|
||||||
|
<el-row>
|
||||||
|
<el-col :lg="{ span: 3, offset: 4 }">
|
||||||
|
<div class="logo-and-title">
|
||||||
|
<img src="@/assets/logo.png" alt="" />
|
||||||
|
<h1 @click="$router.push('/')">YM Blog</h1>
|
||||||
|
<div class="mobile-menu-btn hidden-lg-only">
|
||||||
|
<menuIcon @click="handleShowMobileMenu" :open="isMobileMenuShow"></menuIcon>
|
||||||
|
</div>
|
||||||
|
<ul class="mobile-menu hidden-lg-only" v-show="isMobileMenuShow">
|
||||||
|
<li
|
||||||
|
v-for="item in props.menuList"
|
||||||
|
:key="item.menuId"
|
||||||
|
@click="handleShowMobileMenu(false)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :lg="{ span: 8, offset: 2 }" class="hidden-md-and-down">
|
||||||
|
<ul class="menu">
|
||||||
|
<li v-for="item in props.menuList" :key="item.path" @click="$router.push(item.path)">
|
||||||
|
{{ item.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'element-plus/theme-chalk/display.css';
|
||||||
|
|
||||||
|
$heaher-height: 60px;
|
||||||
|
.header {
|
||||||
|
height: $heaher-height;
|
||||||
|
background-color: #fafafa;
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 4px 10px 0 rgba($color: #777, $alpha: 0.5);
|
||||||
|
}
|
||||||
|
.logo-and-title {
|
||||||
|
height: $heaher-height;
|
||||||
|
padding: 0 0 0 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 40px;
|
||||||
|
// height: 30px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-left: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: #2376b7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .mobile-menu-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: calc($heaher-height - 50px);
|
||||||
|
right: 10px;
|
||||||
|
z-index: 101;
|
||||||
|
}
|
||||||
|
> .mobile-menu {
|
||||||
|
width: 200px;
|
||||||
|
background-color: #fff;
|
||||||
|
// border: 1px solid red;
|
||||||
|
position: absolute;
|
||||||
|
top: $heaher-height;
|
||||||
|
right: 0;
|
||||||
|
z-index: 101;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
> li {
|
||||||
|
width: inherit;
|
||||||
|
height: 34px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
border-left: 1px solid #eee;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menu {
|
||||||
|
height: $heaher-height;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
> li {
|
||||||
|
min-width: 80px;
|
||||||
|
height: inherit;
|
||||||
|
padding: 0 12px 0 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #2376b7;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
&:nth-child(n + 2) {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
66
src/components/Icon/btn/menuIcon.vue
Normal file
66
src/components/Icon/btn/menuIcon.vue
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
const props = defineProps({
|
||||||
|
open: { type: Boolean, default: false }
|
||||||
|
})
|
||||||
|
const emits = defineEmits(['click'])
|
||||||
|
const active = ref(false)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.open,
|
||||||
|
() => {
|
||||||
|
active.value = props.open
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const onClick = () => {
|
||||||
|
active.value = !active.value
|
||||||
|
emits('click', active.value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="-menu-icon" :class="{ active: active }" @click="onClick">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.-menu-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
position: relative;
|
||||||
|
> div {
|
||||||
|
width: inherit;
|
||||||
|
height: 4px;
|
||||||
|
background-color: #444;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
transition: all 0.2s linear;
|
||||||
|
&:nth-child(1) {
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
&:nth-child(2) {
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
&:nth-child(3) {
|
||||||
|
top: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.-menu-icon.active {
|
||||||
|
> div {
|
||||||
|
&:nth-child(1) {
|
||||||
|
transform: rotate(-45deg) translate(-4px, 7px);
|
||||||
|
}
|
||||||
|
&:nth-child(2) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
&:nth-child(3) {
|
||||||
|
transform: rotate(45deg) translate(-4px, -7px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
105
src/components/List/PageList.vue
Normal file
105
src/components/List/PageList.vue
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<script setup>
|
||||||
|
// import { ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
list: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ul class="page-list">
|
||||||
|
<li v-for="item in props.list" :key="item.id">
|
||||||
|
<h2>
|
||||||
|
<router-link :to="`/detail/${item.title}`">{{ item.title }}</router-link>
|
||||||
|
</h2>
|
||||||
|
<div class="info">
|
||||||
|
<span>作者: {{ item.author }}</span>
|
||||||
|
<span>发布于: {{ item.createTime }}</span>
|
||||||
|
<span class="category"
|
||||||
|
>分类:
|
||||||
|
<router-link :to="`/categories/${item.category}`"> {{ item.category }}</router-link></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="abstract">{{ item.abstract }}...</div>
|
||||||
|
<div class="read">
|
||||||
|
<router-link :to="`/detail/${item.title}`">阅读全文</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="tags">
|
||||||
|
<span v-for="tag in item.tags" :key="tag">
|
||||||
|
<router-link :to="`/tags/${tag}`">{{ tag }}</router-link
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-list {
|
||||||
|
> li {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 1px 6px 0 rgba(177, 177, 177, 0.3);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 6px 12px 0 rgba(177, 177, 177, 0.7);
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #444;
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover {
|
||||||
|
color: #2376b7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> h2 {
|
||||||
|
padding: 10px 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
> .info {
|
||||||
|
> span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
> .category {
|
||||||
|
a {
|
||||||
|
color: #2376b7;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.abstract {
|
||||||
|
padding: 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #aaa;
|
||||||
|
line-height: 22px;
|
||||||
|
text-indent: 2em;
|
||||||
|
}
|
||||||
|
> .read {
|
||||||
|
text-indent: 2em;
|
||||||
|
> a {
|
||||||
|
color: #2376b7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .tags {
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
> span {
|
||||||
|
margin-left: 10px;
|
||||||
|
position: relative;
|
||||||
|
top: -20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
59
src/components/List/PaginationCom.vue
Normal file
59
src/components/List/PaginationCom.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
currentPage: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 10
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: String,
|
||||||
|
default: 'prev, pager, next'
|
||||||
|
},
|
||||||
|
pagerCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 5
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['currentChange'])
|
||||||
|
|
||||||
|
const handleCurrentChange = (val) => {
|
||||||
|
console.log(val)
|
||||||
|
emits('currentChange', val)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="custom-pagination">
|
||||||
|
<el-pagination
|
||||||
|
:current-page="props.currentPage"
|
||||||
|
:page-size="props.pageSize"
|
||||||
|
:total="props.total"
|
||||||
|
:layout="props.layout"
|
||||||
|
:pager-count="props.pagerCount"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.custom-pagination {
|
||||||
|
:deep(.el-pagination) {
|
||||||
|
> .el-pager {
|
||||||
|
> .is-active {
|
||||||
|
color: #2376b7;
|
||||||
|
}
|
||||||
|
> li:hover {
|
||||||
|
color: #2376b7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
64
src/layout/LayoutView.vue
Normal file
64
src/layout/LayoutView.vue
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
import HeaderCom from '@/components/Header/HeaderCom.vue'
|
||||||
|
import FooterCom from '@/components/Footer/FooterCom.vue'
|
||||||
|
|
||||||
|
const minHeight = ref(200)
|
||||||
|
onMounted(() => {
|
||||||
|
minHeight.value = document.documentElement.clientHeight - 144 + 'px'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<slot name="header">
|
||||||
|
<HeaderCom />
|
||||||
|
</slot>
|
||||||
|
<div class="body">
|
||||||
|
<el-row>
|
||||||
|
<el-col :lg="{ span: 4 }" class="hidden-md-and-down">
|
||||||
|
<div class="left-side">
|
||||||
|
<slot name="left"></slot>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :lg="{ span: 12, offset: 1 }">
|
||||||
|
<div class="center">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :lg="{ span: 5, offset: 1 }">
|
||||||
|
<div class="right-side">
|
||||||
|
<slot name="right"></slot>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
<slot name="footer">
|
||||||
|
<FooterCom />
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'element-plus/theme-chalk/display.css';
|
||||||
|
|
||||||
|
.body {
|
||||||
|
// margin-top: 20px;
|
||||||
|
.left-side {
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
// background-color: red;
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
width: 100%;
|
||||||
|
min-height: v-bind(minHeight);
|
||||||
|
padding: 20px 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// background-color:;
|
||||||
|
}
|
||||||
|
.right-side {
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
// background-color: blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
26
src/main.js
Normal file
26
src/main.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import './assets/reset.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import pinia from './stores/pinia'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
// 全局自定义指令
|
||||||
|
app.directive('permission', {
|
||||||
|
mounted(el, binding) {
|
||||||
|
const { value } = binding
|
||||||
|
if (value.includes(1)) {
|
||||||
|
el['hidden'] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use(pinia)
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
17
src/router/index.js
Normal file
17
src/router/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import routes from './routes'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes,
|
||||||
|
scrollBehavior: () => ({ left: 0, top: 0, behavior: 'smooth' })
|
||||||
|
})
|
||||||
|
|
||||||
|
// 路由守卫
|
||||||
|
router.beforeEach(() => {})
|
||||||
|
|
||||||
|
router.afterEach((to) => {
|
||||||
|
document.title = import.meta.env.VITE_APP_TITLE + ` | ${to.meta.title || ''}`
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
84
src/router/routes.js
Normal file
84
src/router/routes.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import HomePage from '@/views/HomePage/HomeView.vue'
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'index',
|
||||||
|
meta: {
|
||||||
|
title: '首页'
|
||||||
|
},
|
||||||
|
component: HomePage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/detail/:title',
|
||||||
|
name: 'detail',
|
||||||
|
meta: {
|
||||||
|
title: '详情',
|
||||||
|
keepAlive: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/DetailPage/DetailView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/pages',
|
||||||
|
name: 'pages',
|
||||||
|
meta: {
|
||||||
|
title: '所有内容',
|
||||||
|
keepAlive: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/ListPage/ListView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/categories',
|
||||||
|
name: 'categories',
|
||||||
|
meta: {
|
||||||
|
title: '分类列表',
|
||||||
|
keepAlive: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/CategoriesPage/CategoriesView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/categories/:categoryName',
|
||||||
|
name: 'category',
|
||||||
|
meta: {
|
||||||
|
title: '分类',
|
||||||
|
keepAlive: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/ListPage/ListView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/tags',
|
||||||
|
name: 'tags',
|
||||||
|
meta: {
|
||||||
|
title: '标签列表',
|
||||||
|
keepAlive: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/TagsPage/TagsView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/tags/:tagName',
|
||||||
|
name: 'tag',
|
||||||
|
meta: {
|
||||||
|
title: '标签',
|
||||||
|
keepAlive: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/ListPage/ListView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about-me',
|
||||||
|
name: 'about',
|
||||||
|
meta: {
|
||||||
|
title: '关于我',
|
||||||
|
keepAlive: true
|
||||||
|
},
|
||||||
|
component: () => import('@/views/AboutPage/AboutMe.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
name: '404',
|
||||||
|
meta: {
|
||||||
|
title: '404 Not Found'
|
||||||
|
},
|
||||||
|
component: () => import('@/views/404.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default routes
|
||||||
7
src/stores/pinia.js
Normal file
7
src/stores/pinia.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
|
|
||||||
|
const store = createPinia()
|
||||||
|
store.use(piniaPluginPersistedstate)
|
||||||
|
|
||||||
|
export default store
|
||||||
29
src/stores/user.js
Normal file
29
src/stores/user.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
|
||||||
|
const useUser = defineStore(
|
||||||
|
'user',
|
||||||
|
() => {
|
||||||
|
const name = ref('张三')
|
||||||
|
const age = ref(18)
|
||||||
|
const loginInfo = reactive({
|
||||||
|
token: '123',
|
||||||
|
lastLoginTime: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const modifyLoginInfo = (data) => {
|
||||||
|
loginInfo.token = data.token
|
||||||
|
loginInfo.lastLoginTime = data.lastLoginTime
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name, age, loginInfo, modifyLoginInfo }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 持久化存储
|
||||||
|
persist: {
|
||||||
|
storage: window.localStorage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default useUser
|
||||||
61
src/utils/http.js
Normal file
61
src/utils/http.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import router from '@/router'
|
||||||
|
import { ElMessage, ElNotification } from 'element-plus'
|
||||||
|
|
||||||
|
const API_URL = import.meta.env.VITE_API_BASE_URL
|
||||||
|
|
||||||
|
// console.log(API_URL)
|
||||||
|
|
||||||
|
const http = axios.create({
|
||||||
|
baseURL: API_URL,
|
||||||
|
timeout: 30 * 1000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json;charset=utf-8'
|
||||||
|
},
|
||||||
|
// 请求不携带cookie
|
||||||
|
withCredentials: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求拦截
|
||||||
|
http.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
config.headers.Authorization = `Bearer ${localStorage.getItem('token') || 'NoToken'}`
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 响应拦截
|
||||||
|
http.interceptors.response.use(
|
||||||
|
(res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
const code = res.data.code || 0
|
||||||
|
// 根据返回的自定义code来做不同的处理
|
||||||
|
if (code === 401) {
|
||||||
|
ElMessage({
|
||||||
|
type: 'warning',
|
||||||
|
message: '登录已过期 或 未登录,请重新登录',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
router.push({ path: '/login' })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElNotification({
|
||||||
|
title: '错误',
|
||||||
|
message: res.data.message,
|
||||||
|
type: 'error',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.data
|
||||||
|
},
|
||||||
|
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default http
|
||||||
23
src/utils/ls.js
Normal file
23
src/utils/ls.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const getItem = (itemName) => {
|
||||||
|
return window.localStorage.getItem(itemName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setItem = (itemName, itemValue) => {
|
||||||
|
return window.localStorage.setItem(itemName, itemValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeItem = (itemName) => {
|
||||||
|
return window.localStorage.removeItem(itemName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
return window.localStorage.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getToken = () => getItem('token')
|
||||||
|
|
||||||
|
export const setToken = (token) => setItem('token', token)
|
||||||
|
|
||||||
|
export const clearToken = () => removeItem('token')
|
||||||
|
|
||||||
|
export const clearAll = () => clear()
|
||||||
33
src/views/404.vue
Normal file
33
src/views/404.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script setup>
|
||||||
|
import LayoutView from '@/layout/LayoutView.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LayoutView>
|
||||||
|
<template #content>
|
||||||
|
<div class="not-found">
|
||||||
|
<el-card class="box-card">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1>404</h1>
|
||||||
|
<p>抱歉,访问的页面不存在</p>
|
||||||
|
<p>
|
||||||
|
<el-button type="" @click="$router.push('/')">返回首页</el-button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</LayoutView>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.not-found {
|
||||||
|
text-align: center;
|
||||||
|
h1 {
|
||||||
|
margin: 40px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
13
src/views/AboutPage/AboutMe.vue
Normal file
13
src/views/AboutPage/AboutMe.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script setup>
|
||||||
|
import LayoutView from '@/layout/LayoutView.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LayoutView>
|
||||||
|
<template #content>
|
||||||
|
about me
|
||||||
|
</template>
|
||||||
|
</LayoutView>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
95
src/views/CategoriesPage/CategoriesView.vue
Normal file
95
src/views/CategoriesPage/CategoriesView.vue
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<script setup>
|
||||||
|
import LayoutView from '@/layout/LayoutView.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LayoutView>
|
||||||
|
<template #content>
|
||||||
|
<div class="categories">
|
||||||
|
<div class="title">所有分类<span>4</span></div>
|
||||||
|
<div class="category-items">
|
||||||
|
<el-row>
|
||||||
|
<el-col :lg="{ span: 8 }" :md="{ span: 12 }">
|
||||||
|
<div class="category-title">Vue <span>12</span></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<router-link :to="`/detail/test似懂非懂`"
|
||||||
|
>te吃啥地啥的神盾局t似懂非懂</router-link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<router-link :to="`/detail/test似懂非懂`">test似懂非懂</router-link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<router-link :to="`/detail/test似懂非懂`">test似懂非懂</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</el-col>
|
||||||
|
<el-col :lg="{ span: 8 }" :md="{ span: 12 }">
|
||||||
|
<div class="category-title">Vue <span>12</span></div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<router-link :to="`/detail/test似懂非懂`"
|
||||||
|
>te吃啥地啥的神盾局t似懂非懂</router-link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<router-link :to="`/detail/test似懂非懂`">test似懂非懂</router-link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<router-link :to="`/detail/test似懂非懂`">test似懂非懂</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</LayoutView>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.categories {
|
||||||
|
> .title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
> span {
|
||||||
|
text-indent: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .category-items {
|
||||||
|
padding: 20px 0;
|
||||||
|
.category-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
> span {
|
||||||
|
font-size: 14px;
|
||||||
|
position: relative;
|
||||||
|
top: -6px;
|
||||||
|
left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
padding: 10px 14px;
|
||||||
|
> li {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
>a{
|
||||||
|
color: #444;
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover{
|
||||||
|
color: #2376b7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
30
src/views/DetailPage/DetailView.vue
Normal file
30
src/views/DetailPage/DetailView.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<script setup>
|
||||||
|
import LayoutView from '@/layout/LayoutView.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LayoutView>
|
||||||
|
<template #content>
|
||||||
|
<div class="detail-page">
|
||||||
|
<h2 class="title">title</h2>
|
||||||
|
<div class="info">
|
||||||
|
<div class="author">
|
||||||
|
<span>lau</span>发布在
|
||||||
|
<span>
|
||||||
|
<router-link to="/categories/vue">detail</router-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="time">发布时间<span>2023-12-21</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-content">内容</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</LayoutView>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.detail-page {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
54
src/views/HomePage/HomeView.vue
Normal file
54
src/views/HomePage/HomeView.vue
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import LayoutView from '@/layout/LayoutView.vue'
|
||||||
|
import PageList from '@/components/List/PageList.vue'
|
||||||
|
import PaginationCom from '@/components/List/PaginationCom.vue'
|
||||||
|
|
||||||
|
const pageList = ref([])
|
||||||
|
const currentPage = ref(1)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetch('/json/pageList.json')
|
||||||
|
.then((res) => {
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
pageList.value.splice(0, pageList.value.length)
|
||||||
|
res.data.pageList.forEach((item) => {
|
||||||
|
pageList.value.push(item)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleCurrentPageChange = (val) => {
|
||||||
|
currentPage.value = val
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LayoutView>
|
||||||
|
<template #content>
|
||||||
|
<div class="page-list">
|
||||||
|
<PageList :list="pageList"></PageList>
|
||||||
|
</div>
|
||||||
|
<div class="pagination">
|
||||||
|
<PaginationCom
|
||||||
|
:currentPage="currentPage"
|
||||||
|
:total="14"
|
||||||
|
@currentChange="handleCurrentPageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</LayoutView>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-list {
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
padding: 10px 20px 50px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
101
src/views/ListPage/ListView.vue
Normal file
101
src/views/ListPage/ListView.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<script setup>
|
||||||
|
import LayoutView from '@/layout/LayoutView.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
list: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: 'Vue 3',
|
||||||
|
description: 'Vue 3 is a progressive framework for building user interfaces.',
|
||||||
|
url: 'https://v3.vuejs.org/',
|
||||||
|
createTime: '2023-12-21'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Vue1大山',
|
||||||
|
description: 'Vue 3 is a progressive framework for building user interfaces.',
|
||||||
|
url: 'https://v3.vuejs.org/',
|
||||||
|
createTime: '2023-12-21'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Vue saDv',
|
||||||
|
description: 'Vue 3 is a progressive framework for building user interfaces.',
|
||||||
|
url: 'https://v3.vuejs.org/',
|
||||||
|
createTime: '2023-12-21'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '123123十多个十多个',
|
||||||
|
description: 'Vue 3 is a progressive framework for building user interfaces.',
|
||||||
|
url: 'https://v3.vuejs.org/',
|
||||||
|
createTime: '2023-12-21'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '列表'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LayoutView>
|
||||||
|
<template #content>
|
||||||
|
<div class="page-list">
|
||||||
|
<div class="title">{{ props.type }}</div>
|
||||||
|
<div class="total">{{ props.total }} 篇</div>
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in props.list" :key="item.title">
|
||||||
|
<router-link :to="`/detail/${item.title}`">{{ item.title }}</router-link>
|
||||||
|
<span>{{ item.createTime }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</LayoutView>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-list {
|
||||||
|
> .title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #444;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
>.total{
|
||||||
|
padding: 10px 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
> ul {
|
||||||
|
padding: 20px 40px;
|
||||||
|
> li {
|
||||||
|
height: 26px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
&:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
> a {
|
||||||
|
color: #444;
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover {
|
||||||
|
color: #2376b7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>span{
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
30
src/views/TagsPage/TagsView.vue
Normal file
30
src/views/TagsPage/TagsView.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<script setup>
|
||||||
|
import LayoutView from '@/layout/LayoutView.vue'
|
||||||
|
import TagCom from '@/components/Button/TagCom.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LayoutView>
|
||||||
|
<template #content>
|
||||||
|
<div class="tags">
|
||||||
|
<ul>
|
||||||
|
<li><TagCom @click="$router.push('/detail/title')">测试阿斯达</TagCom></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</LayoutView>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tags {
|
||||||
|
> ul {
|
||||||
|
padding: 20px 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
> li {
|
||||||
|
margin: 20px 0 0 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
36
vite.config.js
Normal file
36
vite.config.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
import process from 'node:process'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// 按需导入 Element-plus 模块
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
console.log(mode)
|
||||||
|
return {
|
||||||
|
server: {
|
||||||
|
port: 3002,
|
||||||
|
proxy: {}
|
||||||
|
},
|
||||||
|
base: loadEnv(mode, process.cwd()).VITE_BASE_URL,
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
// Element-plus 按需导入模块
|
||||||
|
AutoImport({
|
||||||
|
resolvers: [ElementPlusResolver()]
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [ElementPlusResolver()]
|
||||||
|
})
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user