feat:初始化仓库并完成部分功能
This commit is contained in:
commit
1525f0abad
4
.env.development
Normal file
4
.env.development
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
VITE_BASE_URL = './'
|
||||||
|
VITE_APP_TITLE = 'light blog admin'
|
||||||
|
VITE_APP_BASE_URL = '/'
|
||||||
|
VITE_APP_API_BASE_URL = '/api'
|
||||||
3
.env.production
Normal file
3
.env.production
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
VITE_APP_TITLE = 'light blog admin'
|
||||||
|
VITE_APP_BASE_URL = './'
|
||||||
|
VITE_APP_API_BASE_URL = './api'
|
||||||
17
.eslintrc.cjs
Normal file
17
.eslintrc.cjs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/* 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: {
|
||||||
|
'vue/multi-word-component-names': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
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"
|
||||||
|
}
|
||||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# light-blog-admin
|
||||||
|
|
||||||
|
light-blog后端管理界面
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [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"]
|
||||||
|
}
|
||||||
3130
package-lock.json
generated
Normal file
3130
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
package.json
Normal file
41
package.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "light-blog-admin",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "~1.7.2",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"element-plus": "~2.7.3",
|
||||||
|
"mitt": "~3.0.1",
|
||||||
|
"pinia": "~2.1.7",
|
||||||
|
"pinia-plugin-persistedstate": "~3.2.1",
|
||||||
|
"vue": "~3.4.21",
|
||||||
|
"vue-router": "~4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rushstack/eslint-patch": "~1.8.0",
|
||||||
|
"@vitejs/plugin-vue": "~5.0.4",
|
||||||
|
"@vue/eslint-config-prettier": "~9.0.0",
|
||||||
|
"eslint": "~8.57.0",
|
||||||
|
"eslint-plugin-vue": "~9.23.0",
|
||||||
|
"prettier": "~3.2.5",
|
||||||
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
|
"sass": "^1.77.2",
|
||||||
|
"vite": "~5.2.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0",
|
||||||
|
"npm": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "Yimou Lau"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
9
src/App.vue
Normal file
9
src/App.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup>
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
45
src/api/content/content.js
Normal file
45
src/api/content/content.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import http from '@/utils/request'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内容列表
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Number} data.page 第几页
|
||||||
|
* @param {Number} data.size 每页多少条
|
||||||
|
* @param {String} data.sort 排序方式
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getContentList = (data = { page: 1, size: 10, sort: 'asc' }) => {
|
||||||
|
return http({
|
||||||
|
url: '/admin/content',
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除此 id 的内容
|
||||||
|
* @param {String} id
|
||||||
|
*/
|
||||||
|
export const deleteOneById = (id = '') => {
|
||||||
|
if (!id) return false
|
||||||
|
return http({
|
||||||
|
url: '/admin/content',
|
||||||
|
method: 'delete',
|
||||||
|
data: {
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内容详情
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getContentDetailBy_uidOrTitle = (data = {}) => {
|
||||||
|
if (!data._uid && !data.title) return false
|
||||||
|
return http({
|
||||||
|
url: '/admin/content',
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
12
src/api/login/login.js
Normal file
12
src/api/login/login.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import http from '@/utils/request'
|
||||||
|
|
||||||
|
export const login = (data) => {
|
||||||
|
return http({
|
||||||
|
url: '/admin/login',
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
23
src/assets/css/base.css
Normal file
23
src/assets/css/base.css
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
html,
|
||||||
|
body,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
ul,
|
||||||
|
ol,
|
||||||
|
li,
|
||||||
|
div,
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
image {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
10
src/assets/css/theme.scss
Normal file
10
src/assets/css/theme.scss
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
$theme-color: #77f4d5;
|
||||||
|
$theme-color-dark: #a1fba8;
|
||||||
|
|
||||||
|
$border-color: #28b0b6;
|
||||||
|
$button-background-color: #4acbf4;
|
||||||
|
|
||||||
|
$font-color-1: #222;
|
||||||
|
$font-color-2: #555;
|
||||||
|
$font-color-3: #777;
|
||||||
|
$font-color-4: #aaa;
|
||||||
BIN
src/assets/images/logo.png
Normal file
BIN
src/assets/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
3
src/components/Editor/index.js
Normal file
3
src/components/Editor/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Editor from './src/Editor.vue'
|
||||||
|
|
||||||
|
export { Editor }
|
||||||
0
src/components/Editor/src/Editor.vue
Normal file
0
src/components/Editor/src/Editor.vue
Normal file
3
src/components/my-button/index.js
Normal file
3
src/components/my-button/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import myButton from './src/my-button.vue'
|
||||||
|
|
||||||
|
export { myButton }
|
||||||
36
src/components/my-button/src/my-button.vue
Normal file
36
src/components/my-button/src/my-button.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<script setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-button>
|
||||||
|
<slot name="default"></slot>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/assets/css/theme.scss';
|
||||||
|
|
||||||
|
.el-button:hover {
|
||||||
|
background-color: rgba($color: #fff, $alpha: 0.4);
|
||||||
|
:deep() {
|
||||||
|
span {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-button--primary {
|
||||||
|
background-color: rgba($color: $button-background-color, $alpha: 1);
|
||||||
|
:deep() {
|
||||||
|
span {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba($color: $button-background-color, $alpha: 0.8);
|
||||||
|
:deep() {
|
||||||
|
span {
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
3
src/components/my-input/index.js
Normal file
3
src/components/my-input/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import myInput from './src/my-input.vue'
|
||||||
|
|
||||||
|
export { myInput }
|
||||||
14
src/components/my-input/src/my-input.vue
Normal file
14
src/components/my-input/src/my-input.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-input />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/assets/css/theme.scss';
|
||||||
|
.el-input {
|
||||||
|
:deep(.is-focus) {
|
||||||
|
box-shadow: 0 0 0 1px $border-color inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
47
src/layout/baseLayout/baseLayout.vue
Normal file
47
src/layout/baseLayout/baseLayout.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<script setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="common-layout">
|
||||||
|
<el-container>
|
||||||
|
<el-header class="header">
|
||||||
|
<slot name="header"></slot>
|
||||||
|
</el-header>
|
||||||
|
|
||||||
|
<el-container>
|
||||||
|
<el-aside width="240px" class="aside">
|
||||||
|
<slot name="aside"></slot>
|
||||||
|
</el-aside>
|
||||||
|
<el-container>
|
||||||
|
<el-main>
|
||||||
|
<slot name="default"></slot>
|
||||||
|
</el-main>
|
||||||
|
<el-footer>
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</el-footer>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/assets/css/theme.scss';
|
||||||
|
.common-layout {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
.el-header {
|
||||||
|
padding: 0;
|
||||||
|
height: auto;
|
||||||
|
background-color: $theme-color-dark;
|
||||||
|
color: $font-color-1;
|
||||||
|
}
|
||||||
|
.el-aside {
|
||||||
|
height: calc(100vh - 60px);
|
||||||
|
background-color: $theme-color;
|
||||||
|
}
|
||||||
|
.el-footer {
|
||||||
|
padding: 0;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
22
src/main.js
Normal file
22
src/main.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import '@/assets/css/base.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
import { useStore } from './stores'
|
||||||
|
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
app.use(router) // vue-router
|
||||||
|
useStore(app) // pinia
|
||||||
|
app.use(ElementPlus) // element-plus
|
||||||
|
app.mount('#app') // 挂载dom
|
||||||
|
}
|
||||||
|
|
||||||
|
init()
|
||||||
74
src/router/authRoute/authRoute.js
Normal file
74
src/router/authRoute/authRoute.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import homePage from '@/views/home/home.vue'
|
||||||
|
|
||||||
|
const children = [
|
||||||
|
{
|
||||||
|
path: 'dashboard',
|
||||||
|
name: 'dashboard',
|
||||||
|
meta: {
|
||||||
|
title: '概览',
|
||||||
|
permission: 1
|
||||||
|
},
|
||||||
|
component: () => import('@/views/dashboard/dashboard.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'content',
|
||||||
|
name: 'content',
|
||||||
|
meta: {
|
||||||
|
title: '内容管理',
|
||||||
|
permission: 2
|
||||||
|
},
|
||||||
|
component: () => import('@/views/content/content.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'category',
|
||||||
|
name: 'category',
|
||||||
|
meta: {
|
||||||
|
title: '分类管理',
|
||||||
|
permission: 3
|
||||||
|
},
|
||||||
|
component: () => import('@/views/category/category.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'user',
|
||||||
|
name: 'user',
|
||||||
|
meta: {
|
||||||
|
title: '用户管理',
|
||||||
|
permission: 4
|
||||||
|
},
|
||||||
|
component: () => import('@/views/user/user.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'settings',
|
||||||
|
name: 'settings',
|
||||||
|
meta: {
|
||||||
|
title: '系统设置',
|
||||||
|
permission: 5
|
||||||
|
},
|
||||||
|
component: () => import('@/views/settings/settings.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const authRoute = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
meta: {
|
||||||
|
title: '首页',
|
||||||
|
permission: 1
|
||||||
|
},
|
||||||
|
component: homePage,
|
||||||
|
redirect: '/dashboard',
|
||||||
|
children: [...children]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const menuList = children.map((item) => {
|
||||||
|
return {
|
||||||
|
label: item.meta.title || '',
|
||||||
|
path: '/' + item.path,
|
||||||
|
key: item.name,
|
||||||
|
permission: item.meta.permission || 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export { authRoute, menuList }
|
||||||
14
src/router/baseRoute/baseRoute.js
Normal file
14
src/router/baseRoute/baseRoute.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import loginPage from '@/views/login/login.vue'
|
||||||
|
const loginRoute = [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
meta: {
|
||||||
|
title: '登录',
|
||||||
|
permission: 0
|
||||||
|
},
|
||||||
|
component: loginPage
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export { loginRoute }
|
||||||
14
src/router/index.js
Normal file
14
src/router/index.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
|
||||||
|
import { loginRoute } from './baseRoute/baseRoute'
|
||||||
|
import { authRoute } from './authRoute/authRoute'
|
||||||
|
import { useRouterGuard } from './routerGuard/routerGuard'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory('/'),
|
||||||
|
routes: [...loginRoute, ...authRoute]
|
||||||
|
})
|
||||||
|
|
||||||
|
useRouterGuard(router)
|
||||||
|
|
||||||
|
export default router
|
||||||
21
src/router/routerGuard/routerGuard.js
Normal file
21
src/router/routerGuard/routerGuard.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { useLoginStore } from '@/stores/user/loginStore'
|
||||||
|
|
||||||
|
const useRouterGuard = (router) => {
|
||||||
|
router.beforeEach((to) => {
|
||||||
|
const loginStore = useLoginStore()
|
||||||
|
if (!loginStore.token && to.path !== '/login') {
|
||||||
|
return '/login'
|
||||||
|
}
|
||||||
|
if (!!loginStore.token && to.path === '/login') {
|
||||||
|
return '/dashboard'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.afterEach((to) => {
|
||||||
|
document.title = !to.meta.title
|
||||||
|
? import.meta.env.VITE_APP_TITLE
|
||||||
|
: `${import.meta.env.VITE_APP_TITLE} | ${to.meta.title}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useRouterGuard }
|
||||||
13
src/stores/index.js
Normal file
13
src/stores/index.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
|
|
||||||
|
const store = createPinia()
|
||||||
|
|
||||||
|
// 持久化
|
||||||
|
store.use(piniaPluginPersistedstate)
|
||||||
|
|
||||||
|
const useStore = (app) => {
|
||||||
|
app.use(store)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { store, useStore }
|
||||||
35
src/stores/user/loginStore.js
Normal file
35
src/stores/user/loginStore.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
const useLoginStore = defineStore('loginInfo', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
account: 'test',
|
||||||
|
username: '',
|
||||||
|
role: '',
|
||||||
|
permission: [],
|
||||||
|
token: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
setLoginInfo(data) {
|
||||||
|
this.account = data.account
|
||||||
|
this.username = data.username
|
||||||
|
this.role = data.role
|
||||||
|
this.permission = data.permission
|
||||||
|
this.token = data.token
|
||||||
|
},
|
||||||
|
clearLoginInfo() {
|
||||||
|
this.account = ''
|
||||||
|
this.username = ''
|
||||||
|
this.role = ''
|
||||||
|
this.permission = []
|
||||||
|
this.token = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
persist: {
|
||||||
|
storage: window.localStorage
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export { useLoginStore }
|
||||||
70
src/utils/request.js
Normal file
70
src/utils/request.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import { ElMessage, ElNotification } from 'element-plus'
|
||||||
|
const API_URL = import.meta.env.VITE_APP_API_BASE_URL || '/api'
|
||||||
|
|
||||||
|
import { useLoginStore } from '@/stores/user/loginStore'
|
||||||
|
|
||||||
|
const http = axios.create({
|
||||||
|
baseURL: API_URL,
|
||||||
|
timeout: 1000 * 15,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
// 请求不携带cookie
|
||||||
|
withCredentials: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求拦截
|
||||||
|
http.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const { token } = useLoginStore()
|
||||||
|
config.headers.Authorization = !token ? '' : `Bearer ${token}`
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 响应拦截
|
||||||
|
http.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
const code = response.data?.code || 0
|
||||||
|
if (code === 40400) {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: response.data?.msg || '请求失败',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
} else if (code === 50000) {
|
||||||
|
console.error(new Error(response.data?.msg || '请求失败'))
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: response.data?.msg || '请求失败',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElNotification({
|
||||||
|
title: '错误',
|
||||||
|
message: response.data.msg,
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
ElNotification({
|
||||||
|
title: '请求错误',
|
||||||
|
message: `${error.response.status}-${!error.response.data.msg ? error.response.statusText : error.response.data.msg}`,
|
||||||
|
type: 'error',
|
||||||
|
durtion: 2000
|
||||||
|
})
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default http
|
||||||
7
src/utils/sha.js
Normal file
7
src/utils/sha.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import SHA256 from 'crypto-js/sha256'
|
||||||
|
|
||||||
|
const sha256 = (str = '', salt = '') => {
|
||||||
|
return SHA256(`${str}${salt}`).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export { sha256 }
|
||||||
51
src/views/category/category.vue
Normal file
51
src/views/category/category.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
// import { myButton } from '@/components/my-button'
|
||||||
|
/* const COLUMN = [
|
||||||
|
{ label: '序号', type: 'index', prop: '', width: '120', align: 'center' },
|
||||||
|
{ label: '分类', type: 'default', prop: 'category', width: '200', align: 'center' },
|
||||||
|
{ label: '分类', type: 'default', prop: 'children', width: '', align: 'center' },
|
||||||
|
{ label: '操作', type: 'default', prop: '', width: '140', align: 'center' }
|
||||||
|
] */
|
||||||
|
const categoryList = ref([
|
||||||
|
{ category: '前端', children: ['HTML', 'CSS', 'JavaScript'] },
|
||||||
|
{ category: '后端', children: ['Nginx', 'Nodejs', 'PM2'] }
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="category-page">
|
||||||
|
<div class="search-box"></div>
|
||||||
|
<div class="content">
|
||||||
|
<el-table :data="categoryList" border>
|
||||||
|
<el-table-column label="序号" type="index" align="center" width="100"></el-table-column>
|
||||||
|
<el-table-column label="分类" prop="category" align="center" width="120"></el-table-column>
|
||||||
|
<el-table-column label="分类" prop="children" header-align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag
|
||||||
|
v-for="(item, index) in row.children"
|
||||||
|
:key="item + index"
|
||||||
|
style="margin-right: 10px"
|
||||||
|
>{{ item }}</el-tag
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" width="120">
|
||||||
|
<template #default>
|
||||||
|
<el-button type="warning" size="small">编辑</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.category-page {
|
||||||
|
> .search-box {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
> .content {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
97
src/views/content/content.vue
Normal file
97
src/views/content/content.vue
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
import { getContentList, deleteOneById, getContentDetailBy_uidOrTitle } from '@/api/content/content'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
// 获取列表
|
||||||
|
const blogList = ref([])
|
||||||
|
const getContentListData = () => {
|
||||||
|
getContentList({
|
||||||
|
page: 1,
|
||||||
|
size: 10,
|
||||||
|
sort: 'asc'
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.code === 20200) {
|
||||||
|
blogList.value = res.data.list
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
getContentListData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = (id) => {
|
||||||
|
deleteOneById(id).then((res) => {
|
||||||
|
if (res.code === 20200) {
|
||||||
|
blogList.value.splice(
|
||||||
|
blogList.value.findIndex((item) => {
|
||||||
|
return item.id === id
|
||||||
|
}),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
ElMessage({
|
||||||
|
message: '删除成功',
|
||||||
|
type: 'success',
|
||||||
|
duration: 1200
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取详情并编辑
|
||||||
|
const handleEdit = (id) => {
|
||||||
|
getContentDetailBy_uidOrTitle({ _uid: id }).then((res) => {
|
||||||
|
console.log(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="blog-content">
|
||||||
|
<div class="search-box"></div>
|
||||||
|
<div class="content-list">
|
||||||
|
<el-table :data="blogList" border>
|
||||||
|
<el-table-column label="序号" type="index" align="center" width="90"></el-table-column>
|
||||||
|
<el-table-column label="标题" prop="title" align="center" width="140"></el-table-column>
|
||||||
|
<el-table-column label="作者" prop="author" align="center" width="100"></el-table-column>
|
||||||
|
<el-table-column label="摘要" prop="summary" header-align="center"></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="发布日期"
|
||||||
|
prop="created_at"
|
||||||
|
align="center"
|
||||||
|
width="120"
|
||||||
|
sortable
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="修改日期"
|
||||||
|
prop="updated_at"
|
||||||
|
align="center"
|
||||||
|
width="120"
|
||||||
|
sortable
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column label="状态" prop="is_show" align="center" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.is_show ? '展示' : '不展示' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" width="140">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="warning" size="small" @click="handleEdit(row._uid)">编辑</el-button>
|
||||||
|
<el-button type="danger" size="small" @click="handleDelete(row._uid)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.blog-content {
|
||||||
|
> .search-box {
|
||||||
|
}
|
||||||
|
> .content-list {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
src/views/dashboard/dashboard.vue
Normal file
7
src/views/dashboard/dashboard.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>dashboard</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
30
src/views/home/components/asideMenu.vue
Normal file
30
src/views/home/components/asideMenu.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<script setup>
|
||||||
|
import { menuList } from '@/router/authRoute/authRoute'
|
||||||
|
import { useLoginStore } from '@/stores/user/loginStore'
|
||||||
|
const loginStore = useLoginStore()
|
||||||
|
|
||||||
|
const availableMenu = menuList.filter((item) => {
|
||||||
|
return loginStore.permission.includes(item.permission)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-menu
|
||||||
|
background-color="#77f4d5"
|
||||||
|
text-color="#888"
|
||||||
|
active-text-color="#222"
|
||||||
|
router
|
||||||
|
default-active="dashboard"
|
||||||
|
>
|
||||||
|
<el-menu-item
|
||||||
|
v-for="item in availableMenu"
|
||||||
|
:index="item.path"
|
||||||
|
:key="item.key"
|
||||||
|
:route="item.path"
|
||||||
|
>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
27
src/views/home/components/footerBar.vue
Normal file
27
src/views/home/components/footerBar.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script setup>
|
||||||
|
const startYear = 2017
|
||||||
|
const siteAddress = 'blog.6yim.com'
|
||||||
|
const beian = '蜀ICP备2023007248号-2'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="footer-bar">
|
||||||
|
<div class="copyright">
|
||||||
|
Copyright © {{ startYear }} - {{ new Date().getFullYear() }} {{ siteAddress }} 版权所有
|
||||||
|
</div>
|
||||||
|
<div class="beian">{{ beian }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.footer-bar {
|
||||||
|
height: 50px;
|
||||||
|
background-color: #ddd;
|
||||||
|
color: #555;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
88
src/views/home/components/headerBar.vue
Normal file
88
src/views/home/components/headerBar.vue
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
username: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emits = defineEmits(['logout'])
|
||||||
|
const handleLogout = () => {
|
||||||
|
emits('logout')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="header-bar">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="@/assets/images/logo.png" alt="" />
|
||||||
|
<h2>Light Blog 后台管理系统</h2>
|
||||||
|
</div>
|
||||||
|
<div class="user">
|
||||||
|
<ul>
|
||||||
|
<li>{{ props.username }}</li>
|
||||||
|
<li @click="handleLogout">退出登录</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/assets/css/theme.scss';
|
||||||
|
|
||||||
|
.header-bar {
|
||||||
|
height: 60px;
|
||||||
|
padding: 0 40px 0 50px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: default;
|
||||||
|
> .logo {
|
||||||
|
width: 300px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
> img {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
> h2 {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .user {
|
||||||
|
position: relative;
|
||||||
|
> ul {
|
||||||
|
position: absolute;
|
||||||
|
top: -30px;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 120px;
|
||||||
|
height: 60px;
|
||||||
|
background-color: rgba($color: $theme-color, $alpha: 0.6);
|
||||||
|
transition: all 0.5s easy-in-out;
|
||||||
|
&:hover {
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
> li {
|
||||||
|
height: 60px;
|
||||||
|
border: 1px solid rgba($color: $theme-color-dark, $alpha: 0.6);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: $font-color-2;
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
&:nth-child(n + 2) {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: $font-color-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
42
src/views/home/home.vue
Normal file
42
src/views/home/home.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<script setup>
|
||||||
|
import baseLayout from '@/layout/baseLayout/baseLayout.vue'
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { useLoginStore } from '@/stores/user/loginStore'
|
||||||
|
const loginStore = useLoginStore()
|
||||||
|
|
||||||
|
let username = computed(() => loginStore.username)
|
||||||
|
|
||||||
|
import headerBar from './components/headerBar.vue'
|
||||||
|
import footerBar from './components/footerBar.vue'
|
||||||
|
import asideMenu from './components/asideMenu.vue'
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
loginStore.clearLoginInfo()
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<baseLayout>
|
||||||
|
<template #header>
|
||||||
|
<headerBar @logout="handleLogout" :username="username"></headerBar>
|
||||||
|
</template>
|
||||||
|
<template #aside>
|
||||||
|
<asideMenu></asideMenu>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<RouterView></RouterView>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<footerBar></footerBar>
|
||||||
|
</template>
|
||||||
|
</baseLayout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
./components/asideMenu.vue
|
||||||
128
src/views/login/login.vue
Normal file
128
src/views/login/login.vue
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
import { sha256 } from '@/utils/sha'
|
||||||
|
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { myInput } from '@c/my-input'
|
||||||
|
import { myButton } from '@c/my-button'
|
||||||
|
|
||||||
|
import { login } from '@/api/login/login'
|
||||||
|
|
||||||
|
import { useLoginStore } from '@/stores/user/loginStore'
|
||||||
|
const loginStore = useLoginStore()
|
||||||
|
|
||||||
|
const loginForm = ref({
|
||||||
|
account: '',
|
||||||
|
password: '',
|
||||||
|
loginBtnLoading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleLoginSubmit = () => {
|
||||||
|
loginForm.value.loginBtnLoading = true
|
||||||
|
login({
|
||||||
|
account: loginForm.value.account,
|
||||||
|
password: sha256(loginForm.value.password)
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.code === 20200) {
|
||||||
|
loginStore.setLoginInfo({
|
||||||
|
account: res.data.account,
|
||||||
|
username: res.data.username,
|
||||||
|
role: res.data.role,
|
||||||
|
token: res.data.token,
|
||||||
|
permission: res.data.permission
|
||||||
|
})
|
||||||
|
router.replace('/')
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: '登录成功',
|
||||||
|
durtion: 1200
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loginForm.value.loginBtnLoading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="login">
|
||||||
|
<div class="login-box">
|
||||||
|
<h2>Light Blog</h2>
|
||||||
|
<el-form :model="loginForm">
|
||||||
|
<div class="label-text">
|
||||||
|
<span>账号:</span>
|
||||||
|
</div>
|
||||||
|
<el-form-item>
|
||||||
|
<my-input v-model="loginForm.account" placeholder="请输入账号" maxlength="20" />
|
||||||
|
</el-form-item>
|
||||||
|
<div class="label-text">
|
||||||
|
<span>密码:</span>
|
||||||
|
</div>
|
||||||
|
<el-form-item>
|
||||||
|
<my-input
|
||||||
|
v-model="loginForm.password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
type="password"
|
||||||
|
maxlength="20"
|
||||||
|
@keydown.enter="handleLoginSubmit"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<div class="login-btn">
|
||||||
|
<my-button type="primary" @click="handleLoginSubmit" :loading="loginForm.loginBtnLoading"
|
||||||
|
>登录</my-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/assets/css/theme.scss';
|
||||||
|
.login {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
min-width: 300px;
|
||||||
|
min-height: 500px;
|
||||||
|
background: linear-gradient(to right top, #6fe8ee, #6bf1de, #7ff7c6, #a1fba8);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
.login-box {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
margin: -120px 0 0 0;
|
||||||
|
padding: 20px 30px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
> h2 {
|
||||||
|
padding: 0 0 14px 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: $font-color-2;
|
||||||
|
font-weight: 400;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.label-text {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: $font-color-3;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.login-btn {
|
||||||
|
margin: 30px 0 0 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
> :first-child {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
src/views/settings/settings.vue
Normal file
7
src/views/settings/settings.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>系统设置</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
7
src/views/user/user.vue
Normal file
7
src/views/user/user.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>用户管理</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
66
vite.config.js
Normal file
66
vite.config.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
|
import process from 'node:process'
|
||||||
|
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// 打包大小分析
|
||||||
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
console.log(loadEnv(mode, process.cwd()).VITE_APP_BASE_URL)
|
||||||
|
return {
|
||||||
|
base: loadEnv(mode, process.cwd()).VITE_APP_BASE_URL,
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
visualizer({
|
||||||
|
open: true,
|
||||||
|
filename: 'stats.html',
|
||||||
|
gzipSize: true,
|
||||||
|
brotliSize: true
|
||||||
|
})
|
||||||
|
],
|
||||||
|
esbuild: {
|
||||||
|
// 去除项目中的console和debugger
|
||||||
|
drop: ['console', 'debugger']
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
// 打包后目录结构
|
||||||
|
output: {
|
||||||
|
entryFileNames: 'js/[name]-[hash].js',
|
||||||
|
chunkFileNames: 'js/[name]-chunk-[hash].js',
|
||||||
|
assetFileNames: (assetInfo) => {
|
||||||
|
if (assetInfo.name.endsWith('.css')) {
|
||||||
|
return 'css/[name]-[hash].css'
|
||||||
|
}
|
||||||
|
const imgExts = ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.svg', '.ico']
|
||||||
|
if (imgExts.some((ext) => assetInfo.name.endsWith(ext))) {
|
||||||
|
return 'img/[name]-[hash].[ext]'
|
||||||
|
}
|
||||||
|
return 'assets/[name]-[hash].[ext]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
'@c': fileURLToPath(new URL('./src/components', import.meta.url)),
|
||||||
|
'@v': fileURLToPath(new URL('./src/views', import.meta.url))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: '20011',
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8081/',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user