后台管理系统框架
This commit is contained in:
commit
b07157ad4d
|
@ -0,0 +1,36 @@
|
|||
// /* 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: {
|
||||
// 'prettier/prettier': [
|
||||
// 'warn',
|
||||
// {
|
||||
// singleQuote: true, // 单引号
|
||||
// semi: false, // 无分号
|
||||
// printWidth: 80, // 每行宽度至多80字符
|
||||
// trailingComma: 'none', // 不加对象|数组最后逗号
|
||||
// endOfLine: 'auto' // 换行符号不限制(win mac 不一致)
|
||||
// }
|
||||
// ],
|
||||
// 'vue/multi-word-component-names': [
|
||||
// 'warn',
|
||||
// {
|
||||
// ignores: ['index'] // vue组件名称多单词组成(忽略index.vue)
|
||||
// }
|
||||
// ],
|
||||
// 'vue/no-setup-props-destructure': ['off'], // 关闭 props 解构的校验
|
||||
// // 💡 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
|
||||
// 'no-undef': 'error'
|
||||
// }
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"printWidth": 80,
|
||||
"trailingComma": "none"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
# pnpm
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
pnpm lint
|
||||
```
|
|
@ -0,0 +1,39 @@
|
|||
.layout-container {
|
||||
height: 100vh;
|
||||
.el-aside {
|
||||
background-color: #232323;
|
||||
&__logo {
|
||||
height: 120px;
|
||||
background: url('https://fe-bigevent-web.itheima.net/assets/logo-d154372c.png')
|
||||
no-repeat center / 120px auto;
|
||||
}
|
||||
.el-menu {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
.el-header {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.el-dropdown__box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.el-icon {
|
||||
color: #999;
|
||||
margin-left: 10px;
|
||||
}
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>医路无忧</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "pnpm",
|
||||
"version": "0.0.0",
|
||||
"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": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"axios": "^1.7.2",
|
||||
"element-plus": "^2.7.6",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"pnpm": "^9.15.0",
|
||||
"vue": "^3.5.10",
|
||||
"vue-router": "^4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.8.0",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.77.6",
|
||||
"unplugin-auto-import": "^0.17.6",
|
||||
"unplugin-vue-components": "^0.27.2",
|
||||
"vite": "^5.3.1"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,7 @@
|
|||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,11 @@
|
|||
import instance from '@/utils/request'
|
||||
|
||||
// 获取验证码
|
||||
export const userGetVerificationService = () => instance.get('/login/captcha')
|
||||
|
||||
// 登录接口
|
||||
export const userLoginService = ({ username, password, captchaKey, captchaCode }) =>
|
||||
instance.post('/login', { username, password, captchaKey, captchaCode })
|
||||
|
||||
// 获取用户信息
|
||||
export const userGetInfoService = () => instance.get('/info')
|
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.4 MiB |
Binary file not shown.
After Width: | Height: | Size: 1016 KiB |
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,31 @@
|
|||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
Required: true,
|
||||
type: String,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card style="height: 100%">
|
||||
<template #header>
|
||||
<div class="header">
|
||||
<span>{{ title }}</span>
|
||||
<div>
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<slot></slot>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.header {
|
||||
height: 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,12 @@
|
|||
import { createApp } from 'vue'
|
||||
import pinia from './stores/index'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(pinia)
|
||||
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
|
@ -0,0 +1,54 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useUserStore } from '@/stores'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'LoginPage',
|
||||
component: () => import('../views/login/LoginPage.vue')
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'LayoutContainer',
|
||||
component: () => import('../views/layout/LayoutContainer.vue'),
|
||||
redirect: '/article/channel',
|
||||
children: [
|
||||
{
|
||||
path: '/article/manage',
|
||||
name: 'ArticleManage',
|
||||
component: () => import('../views/article/ArticleManage.vue')
|
||||
},
|
||||
{
|
||||
path: '/article/channel',
|
||||
name: 'ArticleChannel',
|
||||
component: () => import('../views/article/ArticleChannel.vue')
|
||||
},
|
||||
{
|
||||
path: '/user/profile',
|
||||
name: 'UserProfile',
|
||||
component: () => import('../views/user/UserProfile.vue')
|
||||
},
|
||||
{
|
||||
path: '/user/avatar',
|
||||
name: 'UserAvatar',
|
||||
component: () => import('../views/user/UserAvatar.vue')
|
||||
},
|
||||
{
|
||||
path: '/user/password',
|
||||
name: 'UserPassword',
|
||||
component: () => import('../views/user/UserPassword.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 登录访问拦截
|
||||
// router.beforeEach((to) => {
|
||||
// const userStore = useUserStore()
|
||||
// if (!userStore.token && to.path !== '/login') return '/login'
|
||||
// })
|
||||
|
||||
export default router
|
|
@ -0,0 +1,11 @@
|
|||
import { createPinia } from 'pinia'
|
||||
const pinia = createPinia()
|
||||
// 持久化插件
|
||||
import persist from 'pinia-plugin-persistedstate'
|
||||
pinia.use(persist)
|
||||
|
||||
export default pinia
|
||||
|
||||
// 统一导出
|
||||
export * from './modules/user'
|
||||
export * from './modules/counter'
|
|
@ -0,0 +1,18 @@
|
|||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
// 数字技术器模块
|
||||
export const useCountStore = defineStore(
|
||||
'big-user',
|
||||
() => {
|
||||
const count = ref(100)
|
||||
const add = (n) => {
|
||||
count.value = n
|
||||
}
|
||||
|
||||
return { count, add }
|
||||
},
|
||||
{
|
||||
persist: true
|
||||
}
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
import { ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { userGetInfoService } from '@/api/user'
|
||||
|
||||
export const useUserStore = defineStore(
|
||||
'big-user',
|
||||
() => {
|
||||
const token = ref('')
|
||||
const setToken = (newToken) => {
|
||||
token.value = newToken
|
||||
}
|
||||
const removeToken = () => {
|
||||
token.value = ''
|
||||
}
|
||||
const user = ref({})
|
||||
const getUser = async() => {
|
||||
const res = await userGetInfoService()
|
||||
user.value = res.data.data
|
||||
}
|
||||
const setUser = (newUser) => {
|
||||
user.value = newUser
|
||||
}
|
||||
|
||||
return { token, setToken, removeToken, user, getUser, setUser }
|
||||
},
|
||||
{
|
||||
persist: true
|
||||
}
|
||||
)
|
|
@ -0,0 +1,50 @@
|
|||
import axios from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import router from '@/router'
|
||||
import { useUserStore } from '@/stores'
|
||||
|
||||
// 基本配置
|
||||
const baseURL = '/admin'
|
||||
const instance = axios.create({
|
||||
baseURL,
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
token: ''
|
||||
}
|
||||
})
|
||||
|
||||
// 请求拦截器,发送给数据之前
|
||||
instance.interceptors.request.use(
|
||||
(config) => {
|
||||
// 添加token
|
||||
const userStore = useUserStore()
|
||||
const token = userStore.token
|
||||
if (token) {
|
||||
config.headers.token = token
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器,获取数据之前
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
if (response.data.status === 200) {
|
||||
return response
|
||||
}
|
||||
// 业务处理失败,给出错误提示,抛出错误
|
||||
ElMessage.error(response.data.message || '服务异常')
|
||||
return Promise.reject(response.data)
|
||||
},
|
||||
(error) => {
|
||||
if (error.response.status === 303) {
|
||||
router.push('/login')
|
||||
}
|
||||
ElMessage.error(error.response.message || '服务异常')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
export default instance
|
|
@ -0,0 +1,23 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
const cate_data = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<page-container title="文章分类">
|
||||
<template #extra>
|
||||
<el-button type="primary">添加分类</el-button>
|
||||
</template>
|
||||
<el-table :data="cate_data">
|
||||
<el-table-column prop="id" label="序号"></el-table-column>
|
||||
<el-table-column prop="cate_name" label="分类名称"></el-table-column>
|
||||
<el-table-column prop="cate_alias" label="分类别名"></el-table-column>
|
||||
<el-table-column prop="operate" label="操作"></el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="没有数据" />
|
||||
</template>
|
||||
</el-table>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,24 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
const article = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<page-container title="文章管理">
|
||||
<template #extra>
|
||||
<el-button type="primary">发布文章</el-button>
|
||||
</template>
|
||||
<el-table :data="article">
|
||||
<el-table-column prop="title" label="文章标题"></el-table-column>
|
||||
<el-table-column prop="cate" label="分类"></el-table-column>
|
||||
<el-table-column prop="time" label="发布时间"></el-table-column>
|
||||
<el-table-column prop="status" label="状态"></el-table-column>
|
||||
<el-table-column prop="operate" label="操作"></el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="没有数据" />
|
||||
</template>
|
||||
</el-table>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,183 @@
|
|||
<script setup>
|
||||
import {
|
||||
Management,
|
||||
Promotion,
|
||||
UserFilled,
|
||||
User,
|
||||
Crop,
|
||||
EditPen,
|
||||
SwitchButton,
|
||||
CaretBottom
|
||||
} from '@element-plus/icons-vue'
|
||||
import avatar from '@/assets/default.png'
|
||||
import { useUserStore } from '@/stores'
|
||||
import { onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 获取用户信息
|
||||
onMounted(() => {
|
||||
userStore.getUser()
|
||||
})
|
||||
|
||||
// 菜单操作
|
||||
const router = useRouter()
|
||||
const handleCommand = (key) => {
|
||||
if (key === 'logout') {
|
||||
// 退出弹窗
|
||||
ElMessageBox.confirm('确定要退出吗?', '温馨提示', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消'
|
||||
}).then(() => {
|
||||
// 清除本地数据
|
||||
userStore.removeToken()
|
||||
userStore.setUser({})
|
||||
router.push('/login')
|
||||
})
|
||||
} else {
|
||||
router.push(`/user/${key}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 1、el-container 布局容器
|
||||
2、el-menu 菜单组件
|
||||
:default-active="$route.path" 配置默认激活高亮的菜单
|
||||
router选项开启, el-menu-item 的 index 就是跳转的路径
|
||||
|
||||
el-menu-item 菜单项
|
||||
index="..." 配置的是访问的跳转路径, 配合 default-active 实现高亮
|
||||
-->
|
||||
<el-container class="layout-container">
|
||||
<el-aside width="200px">
|
||||
<div class="el-aside__logo"></div>
|
||||
<!-- 菜单组件 -->
|
||||
<el-menu
|
||||
active-text-color="#ffd04b"
|
||||
background-color="#232323"
|
||||
:default-active="$route.path"
|
||||
text-color="#fff"
|
||||
router
|
||||
>
|
||||
<!-- 一级菜单 -->
|
||||
<el-menu-item index="/article/channel">
|
||||
<el-icon><Management /></el-icon>
|
||||
<span>文章分类</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/article/manage">
|
||||
<el-icon><Promotion /></el-icon>
|
||||
<span>文章管理</span>
|
||||
</el-menu-item>
|
||||
<!-- 多级菜单 -->
|
||||
<el-sub-menu index="/user">
|
||||
<template #title>
|
||||
<el-icon><UserFilled /></el-icon>
|
||||
<span>个人中心</span>
|
||||
</template>
|
||||
<el-menu-item index="/user/profile">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>基本资料</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/user/avatar">
|
||||
<el-icon><Crop /></el-icon>
|
||||
<span>更换头像</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/user/password">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
<span>重置密码</span>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div>
|
||||
前端程序员:<strong>{{ userStore.user.name }}</strong>
|
||||
</div>
|
||||
<!-- 下拉菜单 -->
|
||||
<el-dropdown placement="bottom-end" @command="handleCommand">
|
||||
<span class="el-dropdown__box">
|
||||
<el-avatar :src="userStore.user.avatarUrl || avatar" />
|
||||
<el-icon><CaretBottom /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="profile" :icon="User"
|
||||
>基本资料</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item command="avatar" :icon="Crop"
|
||||
>更换头像</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item command="password" :icon="EditPen"
|
||||
>重置密码</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item command="logout" :icon="SwitchButton"
|
||||
>退出登录</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="scale" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-container {
|
||||
height: 100vh;
|
||||
.el-aside {
|
||||
background-color: #232323;
|
||||
&__logo {
|
||||
height: 120px;
|
||||
background: url('@/assets/logo.png') no-repeat center / 80px auto;
|
||||
}
|
||||
.el-menu {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
.el-header {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.el-dropdown__box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.el-icon {
|
||||
color: #999;
|
||||
margin-left: 10px;
|
||||
}
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.scale-enter-active,
|
||||
.scale-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.scale-enter-from,
|
||||
.scale-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,186 @@
|
|||
<script setup>
|
||||
import { userLoginService, userGetVerificationService } from '@/api/user'
|
||||
import { User, Lock } from '@element-plus/icons-vue'
|
||||
import { ref, onMounted, useTemplateRef } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores'
|
||||
|
||||
const form = useTemplateRef('form')
|
||||
|
||||
// 整个表单用于提交的form数据对象
|
||||
const formModle = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
captchaKey: '',
|
||||
captchaCode: ''
|
||||
})
|
||||
|
||||
let src = ref('')
|
||||
onMounted(async () => {
|
||||
const init = await userGetVerificationService()
|
||||
formModle.value.captchaKey = init.data.data.key
|
||||
src.value = init.data.data.image
|
||||
})
|
||||
|
||||
// 整个表单的校验规则
|
||||
/// 1、非空校验 required:true,message消息提示,trigger触发校验时机 blur change
|
||||
/// 2、长度校验 min最小长度,max最大长度
|
||||
/// 3、正则校验 pattern正则表达式
|
||||
/// 4、自定义校验 validator函数
|
||||
//// validator: (rule, value, callback)
|
||||
///// 1. rule:当前校验规则
|
||||
///// 2. value:所校验的表单元素的值
|
||||
///// 3. callback:无论成功还是失败,都需要callback回调
|
||||
///// - callback() 回调函数,如果校验通过
|
||||
///// - callback(new Error('错误信息')) 如果校验不通过
|
||||
const rules = ref({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 1, max: 10, message: '用户名必须是 5-10 位字符', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^\S{6,15}$/,
|
||||
message: '密码必须是 6-15 位的非空字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
captchaCode: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 登录
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
const login = async () => {
|
||||
await form.value.validate()
|
||||
const res = await userLoginService(formModle.value)
|
||||
// 保存token
|
||||
userStore.setToken(res.data.data)
|
||||
ElMessage.success('登录成功')
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
const getVerification = async () => {
|
||||
const update = await userGetVerificationService()
|
||||
formModle.value.captchaKey = update.data.data.key
|
||||
src.value = update.data.data.image
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 结构
|
||||
el-form 整个表单组件
|
||||
1、ref="form" 给组件起一个名字,方便后续操作
|
||||
2、size="large" 设置表单大小
|
||||
3、autocomplete="off" 关闭浏览器自动填充功能
|
||||
el-form-item 表单的一行
|
||||
el-input 表单元素
|
||||
:prefix-icon="User" 设置表单前面的元素
|
||||
-->
|
||||
<!-- 校验
|
||||
1、el-form =>
|
||||
:model="formModle" 绑定整个form的数据对象
|
||||
:rules="rules" 绑定整个rules规则的数据对象
|
||||
3、el-form-item =>
|
||||
prop="username"配置生效的是哪个校验规则
|
||||
v-model="formModle.username" 绑定表单元素的数据
|
||||
-->
|
||||
<div class="login-page">
|
||||
<div class="form">
|
||||
<div class="logo"></div>
|
||||
<el-form :model="formModle" :rules ref="form" size="large" class="forms">
|
||||
<el-form-item>
|
||||
<h1 style="margin-bottom: 8px">欢迎来到后台管理!</h1>
|
||||
</el-form-item>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="formModle.username"
|
||||
:prefix-icon="User"
|
||||
placeholder="请输入用户名"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="formModle.password"
|
||||
:prefix-icon="Lock"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="captchaCode" class="verification">
|
||||
<el-input
|
||||
v-model="formModle.captchaCode"
|
||||
:prefix-icon="Lock"
|
||||
type="captchaCode"
|
||||
placeholder="请输入验证码"
|
||||
>
|
||||
</el-input>
|
||||
<img @click="getVerification" :src="src" alt="" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
@click="login"
|
||||
class="button"
|
||||
type="primary"
|
||||
auto-insert-space
|
||||
>
|
||||
登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #fff;
|
||||
background: url('@/assets/login_bg.png') no-repeat center / cover;
|
||||
border-radius: 0 20px 20px 0;
|
||||
.form {
|
||||
width: 420px;
|
||||
height: 500px;
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 0px 20px 8px #ccc;
|
||||
user-select: none; // 禁止用户选择页面上的文本
|
||||
.logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
margin-top: 16px;
|
||||
opacity: 0.8;
|
||||
background: url('@/assets/logo.png') center / cover;
|
||||
}
|
||||
.button {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.forms {
|
||||
width: 75%;
|
||||
box-sizing: border-box;
|
||||
:deep(.el-form-item__content) {
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
img {
|
||||
cursor: pointer;
|
||||
border: #ccc 1px solid;
|
||||
border-radius: 4px;
|
||||
margin-left: 6px;
|
||||
height: 38px;
|
||||
width: 68px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,9 @@
|
|||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<page-container title="更换头像">
|
||||
其他内容
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,9 @@
|
|||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<page-container title="重置密码">
|
||||
其他内容
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,9 @@
|
|||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<page-container title="个人详情">
|
||||
其他内容
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,37 @@
|
|||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
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({
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver()]
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver()]
|
||||
})
|
||||
],
|
||||
// 路径解析配置
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
},
|
||||
base: './',
|
||||
server: {
|
||||
port: 3000,
|
||||
host: true,
|
||||
proxy: {
|
||||
'/admin': {
|
||||
target: 'http://10.138.8.154:8080',
|
||||
changeOrigin: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue