初步完成页面搭建
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