代理跨域
1、vite.config.ts檔案配置(增加程式碼)
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
//獲取各種環境對應的變數
let env = loadEnv(mode, process.cwd());
return {
//代理跨域
server: {
proxy: {
[env.VITE_APP_BASE_API]: {
//獲取資料伺服器地址設定
target: env.VITE_SERVE,
//需要代理跨域
changeOrigin: true,
//路徑重寫
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
})
路由配置
1、src/router目錄下建立index.ts進行路由配置
//透過vue-router實現模板路由配置
import { createRouter, createWebHashHistory } from 'vue-router'
import { constantRoute } from './routes'
let router = createRouter({
//路由模式
history: createWebHashHistory(),
routes: constantRoute,
//滾動行為
scrollBehavior() {
return {
left: 0,
top: 0
}
}
})
export default router
2、src/router目錄下建立routes.ts檔案進行具體配置
//對外暴露配置路由(常量路由)
export const constantRoute = [
{
//登入
path: '/login',
component: () => import('@/views/login/index.vue'),
name: 'login',
meta: {
title: '登入',//選單標題
hidden: true,//代表路由標題在選單中是否隱藏
icon: 'Promotion'//選單文字左側的圖示,支援element-plus全部圖示
}
},
{
//登入成功,展示資料
path: '/',
component: () => import('@/layout/index.vue'),
name: 'layout',
meta: {
title: '',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: ''
},
redirect: '/home',
children: [
{
path: '/home',
component: () => import('@/views/home/index.vue'),
name: 'Home',
meta: {
title: '首頁',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'HomeFilled'
}
}
]
},
{
//資料大屏
path: '/screen',
component: () => import('@/views/screen/index.vue'),
name: 'Screen',
meta: {
title: '資料大屏',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'Platform'
}
},
{
//許可權管理
path: '/acl',
component: () => import('@/layout/index.vue'),
name: 'Acl',
meta: {
title: '許可權管理',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'Lock'
},
redirect: '/acl/user',
children: [
{
path: '/acl/user',
component: () => import('@/views/acl/user/index.vue'),
name: 'User',
meta: {
title: '使用者管理',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'User'
}
},
{
path: '/acl/role',
component: () => import('@/views/acl/role/index.vue'),
name: 'Role',
meta: {
title: '角色管理',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'UserFilled'
}
},
{
path: '/acl/permission',
component: () => import('@/views/acl/permission/index.vue'),
name: 'Permission',
meta: {
title: '選單管理',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'Grid'
}
}
]
},
{
//商品管理
path: '/product',
component: () => import('@/layout/index.vue'),
name: 'Product',
meta: {
title: '商品管理',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'Goods'
},
redirect: '/product/trademark',
children: [
{
path: '/product/trademark',
component: () => import('@/views/product/trademark/index.vue'),
name: 'Trademark',
meta: {
title: '品牌管理',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'ShoppingCartFull'
}
},
{
path: '/product/attr',
component: () => import('@/views/product/attr/index.vue'),
name: 'Attr',
meta: {
title: '屬性管理',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'ChromeFilled'
}
},
{
path: '/product/spu',
component: () => import('@/views/product/spu/index.vue'),
name: 'Spu',
meta: {
title: 'SPU管理',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'MostlyCloudy'
}
},
{
path: '/product/sku',
component: () => import('@/views/product/sku/index.vue'),
name: 'Sku',
meta: {
title: 'SKU管理',//選單標題
hidden: false,//代表路由標題在選單中是否隱藏
icon: 'PartlyCloudy'
}
}
]
},
{
//404
path: '/404',
component: () => import('@/views/404/index.vue'),
name: '404',
meta: {
title: '404',//選單標題
hidden: true,//代表路由標題在選單中是否隱藏
icon: 'Promotion'
}
},
{
path: '/:pathMatch(.*)*',
redirect: '/404',
name: 'Any',
meta: {
title: '任意路由',//選單標題
hidden: true,//代表路由標題在選單中是否隱藏
icon: 'Promotion'
}
}
]
路由鑑權
1、src目錄建立permission.ts檔案進行鑑權操作的配置
//路由鑑權:專案中路由能不能被訪問的許可權設定
import router from "@/router";
import setting from "./setting";
import nprogress from 'nprogress'
nprogress.configure({ showSpinner: false })
//引入進度條樣式
import 'nprogress/nprogress.css'
//獲取使用者相關倉庫裡面的token,用於判斷使用者是否登入成功
import useUserStore from "./store/modules/user";
import pinia from "./store";
let userStore = useUserStore(pinia)
//全域性守衛:專案中任意路由切換都會觸發的鉤子
//全域性前置守衛
router.beforeEach(async (to: any, from: any, next: any) => {
document.title = `${setting.title} - ${to.meta.title}`
//訪問某一個路由之前的守衛
//to:將要訪問的路由
//from:起始路由
//next:路由的放行函式
nprogress.start()
//獲取token判斷使用者登入與否
let token = userStore.token
//獲取使用者名稱
let username = userStore.username
//使用者登入判斷
if (token) {
//登入
if (to.path == '/login') {
next({ path: '/' })
} else {
//登入成功訪問其餘路由,除登入
//有使用者資訊
if (username) {
next()
} else {
//沒有使用者資訊,獲取使用者資訊
try {
//獲取使用者資訊
await userStore.userInfo()
next()
} catch (error) {
//token過期,獲取不到使用者資訊
//使用者手動修改本地儲存token
//退出登入,清空相關資料
await userStore.userLogout()
next({ path: '/login', query: { redirect: to.path } })
}
}
}
} else {
//未登入
if (to.path == '/login') {
next()
} else {
next({ path: '/login', query: { redirect: to.path } })
}
}
next()
})
//全域性後置守衛
router.afterEach((to: any, from: any) => {
nprogress.done()
})
登入功能
1、介面準備,src/api目錄建立user資料夾
2、src/utils中封裝工具檔案
- 建立token.ts檔案進行localstorage本地儲存token操作封裝
//封裝本地儲存資料與讀取資料方法
//儲存資料
export const SET_TOKEN = (token: string) => {
localStorage.setItem('TOKEN', token)
}
//本地儲存獲取資料
export const GET_TOKEN = () => {
return localStorage.getItem('TOKEN')
}
//本地儲存刪除資料
export const REMOVE_TOKEN = () => {
localStorage.removeItem('TOKEN')
}
- 建立time.ts檔案進行獲取時間資訊操作封裝
//獲取當前是早上、上午、下午還是晚上
export const getTime = () => {
let message = ''
let hours = new Date().getHours()
if (hours <= 9) {
message = '早上'
} else if (hours <= 12) {
message = '上午'
} else if (hours <= 18) {
message = '下午'
} else {
message = '晚上'
}
return message
}
3、user目錄增加index.ts檔案封裝介面方法
//統一管理專案使用者相關的介面
import request from '@/utils/request'
import type { loginFormData, loginResponseData, userInfoResponseData } from './type'
//專案使用者相關的請求地址
enum API {
LOGIN_URL = '/admin/acl/index/login',
USERINFO_URL = '/admin/acl/index/info',
LOGOUT_URL = '/admin/acl/index/logout'
}
//登入介面
export const reqLogin = (data: loginFormData) => request.post<any, loginResponseData>(API.LOGIN_URL, data)
//獲取使用者資訊
export const reqUserInfo = () => request.get<any, userInfoResponseData>(API.USERINFO_URL)
//退出登入
export const reqLogout = () => request.post<any, any>(API.LOGOUT_URL)
4、user目錄增加type.ts檔案配置資料型別
//登入介面需要攜帶引數ts型別
export interface loginFormData {
username: string
password: string
}
//定義所有介面返回資料都擁有的ts型別
export interface ResponseData {
code: number,
message: string,
ok: boolean
}
//登入介面返回的資料型別
export interface loginResponseData extends ResponseData {
data: string
}
//定義獲取使用者資訊返回資料型別
export interface userInfoResponseData extends ResponseData {
data: {
routes: string[],
buttons: string[],
roles: string[],
name: string,
avatar: string
}
}
5、src/store目錄建立modules資料夾和index.ts檔案,index.ts檔案用於建立大倉庫,modules資料夾中存放小倉庫
//大倉庫
import { createPinia } from 'pinia'
//建立大倉庫
let pinia = createPinia()
//對外暴露,入口檔案安裝
export default pinia
6、modules目錄建立user.ts檔案
//建立使用者相關的小倉庫
import { defineStore } from 'pinia'
//引入介面
import { reqLogin, reqUserInfo, reqLogout } from '@/api/user'
//引入資料型別
import { loginFormData, loginResponseData, userInfoResponseData } from '@/api/user/type'
import { UserState } from './types/type'
//引入操作本地儲存的工具方法
import { SET_TOKEN, GET_TOKEN, REMOVE_TOKEN } from '@/utils/token'
//引入路由(常量路由)
import { constantRoute } from '@/router/routes'
//建立使用者小倉庫
let useUserStore = defineStore("User", {
//小倉庫儲存資料的地方
state: (): UserState => {
return {
token: GET_TOKEN(),//使用者的唯一標識
menuRoutes: constantRoute,//倉庫儲存生成選單需要的陣列(路由)
username: '',
avatar: ''
}
},
//非同步|邏輯的地方
actions: {
//使用者登入的方法
async userLogin(data: loginFormData) {
//登入請求
let result: loginResponseData = await reqLogin(data)
//登入成功
if (result.code === 200) {
//pinia倉庫儲存一下token
this.token = (result.data as string)
//本地持久化儲存token
SET_TOKEN((result.data as string))
//保證當前async函式返回一個成功的promise
return 'ok'
} else {
//返回錯誤的promise
return Promise.reject(new Error(result.data))
}
},
//獲取使用者資訊的方法
async userInfo() {
//獲取使用者資訊儲存於倉庫
let result: userInfoResponseData = await reqUserInfo()
//如果獲取使用者資訊成功,儲存使用者資訊
if (result.code === 200) {
this.username = result.data.name
this.avatar = result.data.avatar
return 'ok'
} else {
return Promise.reject(new Error(result.message))
}
},
//退出登入的方法
async userLogout() {
let result: any = await reqLogout()
if (result.code === 200) {
this.token = ''
this.username = ''
this.avatar = ''
REMOVE_TOKEN()
return 'ok'
} else {
return Promise.reject(new Error(result.message))
}
}
},
getters: {
}
})
//對外暴露小倉庫
export default useUserStore
7、modules目錄下建立types/index.ts用於規範小倉庫中的資料型別
import type { RouteRecordRaw } from 'vue-router'
//定義小倉庫資料state型別
export interface UserState {
token: string | null,
menuRoutes: RouteRecordRaw[],
username: string,
avatar: string
}
8、src/views目錄下建立login/index.vue作為登入頁面
<template>
<div class="login_container">
<el-row>
<el-col :span="12" :xs="0"></el-col>
<el-col :span="12" :xs="24">
<el-form
class="login_form"
:model="loginForm"
:rules="rules"
ref="loginForms"
>
<h1>Hello</h1>
<h2>歡迎來到管理系統</h2>
<el-form-item prop="username">
<el-input
:prefix-icon="User"
v-model="loginForm.username"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
:prefix-icon="Lock"
show-password
v-model="loginForm.password"
></el-input>
</el-form-item>
<el-form-item>
<el-button
:loading="loading"
class="login_btn"
type="primary"
size="default"
@click="login"
>
登入
</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { User, Lock } from '@element-plus/icons-vue'
import { reactive, ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElNotification } from 'element-plus'
//引入獲取當前時間的函式
import { getTime } from '@/utils/time'
//引入使用者相關的小倉庫
import useUserStore from '@/store/modules/user'
//獲取使用者倉庫
let userStore = useUserStore()
//獲取el-form元件
let loginForms = ref()
//獲取路由器
let $router = useRouter()
//獲取路由
let $route = useRoute()
//定義變數控制按鈕載入效果
let loading = ref(false)
//收集賬號和密碼的資料
let loginForm = reactive({ username: 'admin', password: '111111' })
//登入按鈕的回撥
const login = async () => {
//保證全部的表單校驗透過再發請求
await loginForms.value.validate()
//載入效果,開始載入
loading.value = true
try {
//保證登入成功
await userStore.userLogin(loginForm)
//程式設計式導航跳轉到展示資料首頁
//判斷登入的時候,路由路徑中是否有query引數,有跳轉query引數頁面,沒有跳轉到首頁
let redirect: any = $route.query.redirect
$router.push({ path: redirect || '/' })
//登入成功的提示資訊
ElNotification({
type: 'success',
message: '歡迎回來',
title: `HI,${getTime()}好`,
})
//登入成功,載入效果消失
loading.value = false
} catch (error) {
//登入失敗,載入效果消失
loading.value = false
//登入失敗的提示資訊
ElNotification({
type: 'error',
message: (error as Error).message,
})
}
}
//自定義校驗規則函式
const validatorUserName = (rule: any, value: any, callback: any) => {
//rule為校驗規則物件
//value:校驗的文字值
//如果符合條件透過callback放行透過,不符合條件透過callback注入錯誤資訊
if (value.length >= 5) {
callback()
} else {
callback(new Error('使用者名稱長度至少5位'))
}
}
const validatorPassword = (rule: any, value: any, callback: any) => {
if (value.length >= 6) {
callback()
} else {
callback(new Error('密碼長度至少6位'))
}
}
//定義表單校驗需要的配置物件
const rules = {
//規則物件屬性:
//required:代表這個欄位務必要校驗
//min:文字長度至少多少位
//max:文字長度最多多少位
//message:校驗失敗的提示資訊
//trigger:觸發校驗表單的時機(change:文字發生變化觸發,blur:失去焦點觸發)
username: [
// {
// required: true,
// min: 5,
// max: 10,
// message: '使用者名稱長度至少5位,最多15位',
// trigger: 'change',
// },
{ trigger: 'change', validator: validatorUserName },
],
password: [
// {
// required: true,
// min: 6,
// max: 15,
// message: '密碼長度至少6位,最多15位',
// trigger: 'change',
// },
{ trigger: 'change', validator: validatorPassword },
],
}
</script>
<style scoped lang="scss">
.login_container {
width: 100%;
height: 100vh;
background: url('@/assets/images/background.jpg') no-repeat;
background-size: cover;
}
.login_form {
position: relative;
width: 80%;
top: 30vh;
background: url('@/assets/images/login_form.png') no-repeat;
background-size: cover;
padding: 40px;
h1 {
color: white;
font-size: 40px;
}
h2 {
color: white;
font-size: 20px;
margin: 20px 0px;
}
.login_btn {
width: 100%;
}
}
</style>