网站分析的优劣势,云主机费用,免费婚庆网站模板,php第一季网站开发实例教程一、路由配置
项目一共需要4个一级路由#xff1a;登录#xff08;login#xff09;、主页#xff08;home#xff09;、404、任意路由#xff08;重定向到404#xff09;。
1.1 安装路由插件
pnpm install vue-router
1.2 创建路由组件
在src目录下新建views文件…一、路由配置
项目一共需要4个一级路由登录login、主页home、404、任意路由重定向到404。
1.1 安装路由插件
pnpm install vue-router
1.2 创建路由组件
在src目录下新建views文件夹在views中创建login、home、404路由组件。 1.3 配置路由
在src目录下新建router文件夹书写路由配置包含index.ts和routes.ts。
src/router/routes.ts
// 对外暴露配置路由常量路由
export const constantRoute [{// 登录path: /login,component: () import(/views/login/index.vue),name: login},{// 登录成功以后展示数据的路由path: /,component: () import(/views/home/index.vue),name: layout},{// 404path: /404,component: () import(/views/404/index.vue),name: 404},{// 任意路由path: /:pathMatch(.*)*,redirect: /404,name: Any}
]
src/router/index.ts
// 通过vue-router插件实现路由配置
import { createRouter, createWebHashHistory } from vue-router;
// 引入routes配置项
import { constantRoute } from ./routes;
// 创建路由
let router createRouter({// 路由模式hashhistory: createWebHashHistory(),routes: constantRoute,// 滚动行为scrollBehavior() {return {left: 0,top: 0}}
})export default router;
1.4 引入路由
在入口文件main.js引入路由
// 引入路由
import router from /router
// 注册模板路由
app.use(router)
最后在模板中通过 router-view/router-view占位根据当前的路由状态动态地渲染匹配到的组件。
二、登录模块 2.1 登录路由静态的搭建
采用element-plus中的Layout布局栅格布局、From表单组件、input组件、button组件。 Layout布局一共是24 分栏:span代表栅格占据的列数:xs代表屏幕宽度768px时栅格占据的列数。 input组件:prefix-icon代表前缀图标show-password代表是否显示切换密码图标 src/views/login/index.vue
templatediv classlogin_containerel-rowel-col :span12 :xs0/el-colel-col :span12 :xs24el-form classlogin_fromh1Hello/h1h2欢迎来到唧唧bong甄选/h2el-form-itemel-input :prefix-iconUser v-modelloginFrom.username/el-input/el-form-itemel-form-itemel-input typepassword :prefix-iconLock v-modelloginFrom.password show-password/el-input/el-form-itemel-form-itemel-button classlogin_btn typeprimary sizedefault登录/el-button/el-form-item/el-form/el-col/el-row/div
/templatescript setup langts
import {User, Lock} from element-plus/icons-vue
import { reactive } from vue;
let loginFrom reactive({username: admin,password: 111111
})
/scriptstyle scoped langscss
.login_container {width: 100%;height: 100vh;background: url(/assets/images/background.jpg) no-repeat;background-size: cover;.login_from{width: 80%;position: relative;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 0;}.login_btn{width: 100%;}}
}
/style
2.2 模板封装登录业务
点击登录时会携带用户名和密码向服务器发请求获取token此时我们需要把token存储起来用于后续向服务端发请求获取信息的身份验证这里我们用pinia和loacalStroage进行存储。
安装pinia
pnpm i pinia
创建大仓库src/store/index.ts
import { createPinia } from pinia
//创建大仓库
const pinia createPinia()
//对外暴露入口文件需要安装仓库
export default pinia
在入口文件main.ts中引入并安装src/main.ts
// 引入大仓库
import pinia from ./store
// 安装仓库
app.use(pinia)
创建小仓库src/store/modules/user.ts
import { defineStore } from pinia
// 引入接口
import { reqLogin } from /api/user
// 引入类型
import type { loginForm, loginResponseData } from /api/user/type
import type { UserState } from ./types/type
// 引入操作本地存储的工具方法
import { SET_TOKEN, GET_TOKEN } from /utils/token
// 创建用户小仓库
const useUserStore defineStore(User, {// 小仓库存储数据的地方state: (): UserState {return {token: GET_TOKEN(), //用户唯一的标识token}},// 异步|逻辑的地方actions: {// 用户登录的方法async userLogin(data: loginForm) {// 登录请求let result: loginResponseData await reqLogin(data)// 登录请求成功200-token// 登录请求失败201-登录失败错误的信息if (result.code 200) {// pinia仓库存储一下token// 由于pinia|vuex存储数据其实利用js对象(非持久化存储)this.token (result.data.token as string)// 本地化持久存储一份SET_TOKEN((result.data.token as string))// 能保证当前async函数返回一个成功的promisereturn ok}else {return Promise.reject(new Error(result.data.message))}}},getters: {}
})// 对外暴露用户小仓库
export default useUserStore
在登录页面中引入小仓库点击登录时通知user小仓库发请求存储tokensrc/views/login/index.vue
script setup langts
import { reactive, ref } from vue;
import { useRouter } from vue-router
// 引入element-plus提示框
import { ElNotification } from element-plus
// 引入用户相关的小仓库
import useUserStore from /store/modules/user
let useStore useUserStore()// 获取路由
let $router useRouter()
// 定义变量控制按钮加载效果
let loading ref(false)
// 收集账号与密码的数据
let loginFrom reactive({username: admin,password: 111111
})
const login async () {// 加载效果开始加载loading.value true//点击登录按钮以后干什么?//通知仓库发登录请求//请求成功-首页展示数据的地方//请求失败-弹出登录失败信息try {// 保证登录成功await useStore.userLogin(loginFrom)// 编程式导航跳转到展示数据首页$router.push(/)// 登录成功信息提示ElNotification({type: success,message: 登录成功,})// 登录成功加载效果消失loading.value false} catch (error) {// 登录失败加载效果消失loading.value false// 登录失败的提示信息ElNotification({type: error,message: (error as Error).message})}
}
/script userLogin会返回一个Promise此处可以使用try...catch...或.then来进行下一步结果处理。不管成功或失败都需要使登录加载效果消失因此也可以统一写在finally里面 try {// 保证登录成功await useStore.userLogin(loginFrom)// 编程式导航跳转到展示数据首页$router.push(/)// 登录成功信息提示ElNotification({type: success,message: 登录成功,}) } catch (error) {// 登录失败的提示信息ElNotification({type: error,message: (error as Error).message})} finally{// 登录成功/失败加载效果消失loading.value false} 2.3 用户仓库数据ts类型的定义
定义小仓库数据state类型src\store\modules\types\type.ts
// 定义小仓库数据state类型
export interface UserState {token: string | null
} 登录接口返回的数据类型src\api\user\type.ts 登录请求可能返回成功/失败的数据因此类型需要dataType需要包括成功的数据token和失败的数据message且是可选的要加上?。 interface dataType {token?: string,message?:string
}// 登录接口返回的数据类型
export interface loginResponseData {code: number,data: dataType
}
封装本地存储数据和读取方法src/utils/token.js
// 存储数据
export const SET_TOKEN (token: string) {localStorage.setItem(TOKEN, token)
}// 本地存储获取数据
export const GET_TOKEN () {return localStorage.getItem(TOKEN)
}
2.4 登录时间的判断与封装
在utils中封装一个函数src/utils/time.js
// 封装一个函数获取一个结果当前早上|上午|中午|下午|晚上
export const getTime () {let time // 通过内置的构造函数Datelet hour new Date().getHours()if (hour 9) {time 早上}else if (hour 12) {time 上午}else if (hour 14) {time 中午}else if (hour 18) {time 下午}else {time 晚上}return time
} 在login组件中引入并使用
// 引入当前时间的函数
import { getTime } from /utils/time
......// 登录成功信息提示
ElNotification({type: success,message: 欢迎回来,title: HI,${getTime()}好
})
2.5 登录模块表单校验
使用element-plus的表单验证功能 步骤如下
给el-form添加 :modelloginFrom和:rulesrules给需要验证的每个el-form-item添加prop属性如 propusername、proppassword定义表单校验需要配置对象rules请求前使用 loginFroms.value.validate()触发表单中所有表单项的校验保证全部的表单项校验通过再发请求 :model要验证的表单数据对象:rulesrules表单验证规则prop要校验字段的属性名 // 第一步给el-form添加 :modelloginFrom和:rulesrules
el-form classlogin_form :modelloginForm :rulesrules refloginFroms// 第二步给需要验证的每个el-form-item添加prop属性如 propusername、proppassword
el-form-item propusernameel-input :prefix-iconUser v-modelloginFrom.username/el-input
/el-form-item
el-form-item proppasswordel-input typepassword :prefix-iconLock v-modelloginFrom.password show-password/el-input
/el-form-item// 第三步定义表单校验需要配置对象rules
// 规则对象属性
// required代表这个字段必须校验
// min文本长度至少多少位
// max文本长度最多多少位
// message错误的提示信息
// trigger触发校验表单的时机change文本发生变化时触发校验blur失去焦点时触发校验
const rules {username: [{ required: true, min: 5, max: 10, message: 用户名长度应为5-10位, trigger: change },],password: [{ required: true, min: 6, max: 10, message: 密码长度应为6-10位, trigger: change },
]
}第四步请求前使用 loginFroms.value.validate()触发表单中所有表单项的校验保证全部的表单项校验通过再发请求
// 通过ref属性获取el-form组件
let loginFroms ref()
const login async () {// 保证全部的表单项校验通过再发请求await loginForms.value.validate()......
} PS在 el-form 组件中可以使用 ref 属性来获取表单的引用然后调用该引用上的 validate 方法。这个方法会触发表单中所有表单项的校验并返回一个 Promise 对象该对象的 resolve 回调函数会在校验通过时被调用而 reject 回调函数会在校验失败时被调用。 2.6 自定义校验表单
上面的验证比较简单公司的开发项目中表单验证会更复杂这个时候就要用到element-plus的自定义校验规则了 。
自定义校验表单的配置项中需要一个validator属性值是一个方法用于书写自定义规则。
// 自定义校验规则函数
const validateUsername (rule: any, value: any, callback: any) {//rule:即为校验规则对象//value:即为表单元素文本内容//函数:如果符合条件callback放行通过即为//如果不符合条件callback方法,注入错误提示信息if(value.length 5){callback()}else{callback(new Error(用户名不少于5位))}
}const validatePassword (rule: any, value: any, callback: any) {if(value.length 6){callback()}else{callback(new Error(用户名不少于6位))}
}// 定义表单校验需要配置对象
const rules {username: [{ validator: validateUsername, trigger: change },],password: [{ validator: validatePassword, trigger: change },]
} PS这里只是简单的示范正式开发中大多场景的校验规则会更复杂需要用到正则表达式来书写。 三、layout组件
3.1 layout组件的静态搭建
layout组件主页分为三部分左侧菜单、顶部导航、内容展示区域。
在src目录下创建layout组件src/layout/index.vue
templatediv classlayout_container!-- 左侧菜单 --div classlayout_slider左侧菜单/div!-- 顶部导航 --div classlayout_tabbar顶部导航/div!-- 内容展示区域 --div classlayout_mainp styleheight: 10000px;background: red;内容/p/div/div
/templatescript setup langts/scriptstyle scoped langscss
.layout_container {width: 100%;height: 100vh;.layout_slider {width: $base-menu-width;height: 100vh;background: $base-menu-background;}.layout_tabbar {position: fixed;width: calc(100% - $base-menu-width);height: $base-tabbar-height;background: $base-tabbar-background;top: 0px;left: $base-menu-width;}.layout_main {position: absolute;width: calc(100% - $base-menu-width);height: calc(100vh - $base-tabbar-height);background: $base-main-background;top: $base-tabbar-height;left: $base-menu-width;overflow: auto;padding: 20px;}
}
/style
配置layout相关的样式的全局变量src/styles/variable.scss
// 左侧菜单的宽度
$base-menu-width: 260px;
// 左侧菜单的背景颜色
$base-menu-background: #001529;
// 顶部导航的高度
$base-tabbar-height: 50px;
// 顶部导航的背景颜色
$base-tabbar-background: #ffffff;
// 内容展示区域的背景颜色
$base-main-background: #ccc8cc; 设置滚动条样式src/styles/index.scss
// 滚动条外观设置
::-webkit-scrollbar{width: 10px;
}::-webkit-scrollbar-track{background: $base-menu-background;
}::-webkit-scrollbar-thumb{width: 10px;background: yellowgreen;border-radius: 10px;
}
3.2 Logo组件的封装
创建logo组件src/layout/logo/index.vue
templatediv classlogo v-ifsetting.logoHiddenimg :srcsetting.logo altp{{ setting.title }}/p/div
/templatescript setup langts
//引入设置标题与logo这配置文件
import setting from /setting
/scriptstyle scoped langscss
.logo {width: 100%;height: $base-menu-logo-height;color: white;display: flex;align-items: center;padding: 10px;img {width: 40px;height: 40px;}p {font-size: $base-logo-title-fontSize;margin-left: 10px;}
}
/style
配置logo相关的样式的全局变量src/styles/variable.scss
//左侧菜单logo高度设置
$base-menu-logo-height:50px;//左侧菜单logo右侧文字大小
$base-logo-title-fontSize:16px;
项目logo/标题配置文件src/setting.ts
// 用于项目logo|标题配置
export default {title:唧唧bong甄选运营平台, // 项目标题logo:/logo.png, // 项目logo设置logoHidden: true // logo组件是否隐藏设置
}
3.3 左侧菜单组件
3.3.1 递归组件生成动态菜单
创建menu组件src/layout/menu/index.vue并在layout中引入并使用menu组件
添加二级路由src/router/routes.ts {// 登录成功以后展示数据的路由path: /,component: () import(/layout/index.vue),name: layout,meta: {title: layout,hidden: true },children: [{path: /home,component: () import(/views/home/index.vue),name: home,meta: {title: 首页,hidden: false }}]},
将路由数组存储到store中方便组件访问路由数据src/store/modules/user.ts
// 引入路由(常量路由)
import { constantRoute } from /router/routes;
// 创建用户小仓库
const useUserStore defineStore(User, {// 小仓库存储数据的地方state: (): UserState {return {token: GET_TOKEN(), //用户唯一的标识token//路由配置数据menuRoutes: constantRoute}},......
})
UserState中添加路由的类型定义src/store/modules/types/type.ts
// 引入描述路由配置信息的类型(这个类型包含了路由的路径、组件、子路由等信息)
import type { RouteRecordRaw } from vue-router
// 定义小仓库数据state类型
export interface UserState {token: string | null,menuRoutes: RouteRecordRaw[]
}
layout组件中引入小仓库获取路由数据通过props传递给menu组件src/layout/index.vue
templatediv classlayout_container!-- 左侧菜单 --div classlayout_sliderLogo/Logo!-- 展示菜单 --el-scrollbar classscrollbarel-menu background-color#001529 text-colorwhite!-- 传递路由数据给menu组件 --Menu :menuListuserStore.menuRoutes/Menu/el-menu/el-scrollbar/div....../div
/templatescript setup langts
// 引入菜单组件
import Menu from ./menu/index.vue// 获取用户相关的小仓库
import useUserStore from /store/modules/user
let userStore useUserStore()
/script给每个路由添加meta元信息src/router/routes.ts
meta: {title: 登录, // 菜单标题hidden: true // 代表路由标题在菜单中是否隐藏 true:隐藏 false:显示}
书写menu组件src/layout/menu/index.vue 1. menu组件分三种情况 没有子路由有且只有一个子路由有一个以上的子路由 2. 点击菜单item跳转路由clickgoRoute el-menu-item标签有click事件菜单点击时的回调函数回调参数是el-menu-item实例。 templatetemplate v-for(item, index) in menuList :keyitem.path!-- 没有子路由 --template v-if!item.childrenel-menu-item :indexitem.path v-if!item.meta.hidden clickgoRoutetemplate #titleel-iconcomponent :isitem.meta.icon/component/el-iconspan{{ item.meta.title }}/span/template/el-menu-item/template!-- 有且只有一个子路由 --template v-ifitem.children item.children.length 1el-menu-item :indexitem.children[0].path v-if!item.children[0].meta.hidden clickgoRoutetemplate #titleel-iconcomponent :isitem.children[0].meta.icon/component/el-iconspan{{ item.children[0].meta.title }}/span/template/el-menu-item/template!-- 有子路由,且个数大于一 --el-sub-menu v-ifitem.children item.children.length 1 :indexitem.pathtemplate #titleel-iconcomponent :isitem.meta.icon/component/el-iconspan{{ item.meta.title }}/span/templateMenu :menuListitem.children/Menu/el-sub-menu/template
/templatescript setup langts
import { useRouter } from vue-router;
// 获取父组件传递过来的全部路由数组
defineProps([menuList])
// 获取路由对象
let $router useRouter()
// 点击菜单的回调
const goRoute (vc: any) {// 路由跳转$router.push(vc.index)
}
/script
script langts
export default {name: Menu
}
/scriptstyle scoped/style PS递归组件必须有一个名字因为在vue中组件是通过其名字进行注册和引用的。递归组件需要在自身的模板中引用自身但如果组件没有名字Vue无法在模板中正确地引用它从而导致递归出现问题。 3.3.2 菜单图标完成
将element-plus图标 注册成全局组件src/components/index.ts
具体可参考官网Icon 图标 | Element Plus (gitee.io)
// 引入elemnet-plus提供全部图标组件
import * as ElementPlusIconsVue from element-plus/icons-vue// 对外暴露一个插件对象
export default {install(app: any) {// 将element-plus提供图标注册为全局组件for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)}}
}
菜单图标由路由配置决定meta中添加 icon 字段src/router/routes.ts
meta: {......icon: Promotion, // 菜单文字左侧的图标,支持element-plus全部图标}
在menu中使用element-plus图标src/layout/menu/index.vue
el-iconcomponent :isitem.meta.icon/component
/el-icon
3.3.3 项目全部路由配置 首页重定向到home权限管理和商品管理的一级路由用的还是组件 layout src/router/routes.ts
// 对外暴露配置路由常量路由
export const constantRoute [{// 登录path: /login,component: () import(/views/login/index.vue),name: login,meta: {title: 登录, // 菜单标题hidden: true, // 代表路由标题在菜单中是否隐藏 true:隐藏 false:显示icon: Promotion, // 菜单文字左侧的图标,支持element-plus全部图标}},{// 登录成功以后展示数据的路由path: /,component: () import(/layout/index.vue),name: layout,meta: {title: layout,hidden: true,icon: Avatar,},redirect: /home,children: [{path: /home,component: () import(/views/home/index.vue),name: home,meta: {title: 首页,hidden: false,icon: HomeFilled,}}]},{// 404path: /404,component: () import(/views/404/index.vue),name: 404,meta: {title: 404,hidden: true,icon: BrushFilled,}},{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: 权限管理,icon: Lock,},children: [{path: /acl/user,component: () import(/views/acl/user/index.vue),name: User,meta: {title: 用户管理,icon: User,}},{path: /acl/role,component: () import(/views/acl/role/index.vue),name: Role,meta: {title: 角色管理,icon: UserFilled,}},{path: /acl/permission,component: () import(/views/acl/permission/index.vue),name: Permission,meta: {title: 菜单管理,icon: Monitor,}},]},{path: /product,component: () import(/layout/index.vue),name: Product,meta: {title: 商品管理,icon: Goods,},children: [{path: /product/trademark,component: () import(/views/product/trademark/index.vue),name: Trademark,meta: {title: 品牌管理,icon: ShoppingCartFull,}},{path: /product/attr,component: () import(/views/product/attr/index.vue),name: Attr,meta: {title: 属性管理,icon: ChromeFilled,}},{path: /product/spu,component: () import(/views/product/spu/index.vue),name: Spu,meta: {title: SPU管理,icon: Calendar,}},{path: /product/sku,component: () import(/views/product/sku/index.vue),name: Sku,meta: {title: SKU管理,icon: Orange,}},]},{// 任意路由path: /:pathMatch(.*)*,redirect: /404,name: Any,meta: {title: 任意路由,hidden: true,icon: Wallet,}}
] layout右侧展示区域封装成一个组件 main为了实现一些动画效果src/layout/main/main.vue
关于路由过度可参考官网过渡动效 | Vue Router (vuejs.org)
template!-- 路由组件出口的位置 --router-view v-slot{ Component }transition namefade!-- 渲染layout一级路由组件的子路由 --component :isComponent //transition/router-view
/templatescript setup langts/scriptstyle scoped
.fade-enter-from {opacity: 0;transform: scale(0);
}.fade-enter-active {transition: all .3s;
}.fade-enter-to {opacity: 1;transform: scale(1);
}
/style
在layout组件中引入mainsrc/layout/index.vue
// 右侧内容展示组件
import Main from /layout/main/index.vue
!-- 内容展示区域 --
div classlayout_mainMain /
/div 3.4 顶部tabbar组件
3.4.1 顶部tabbar组件静态搭建与拆分
左侧菜单刷新折叠问题解决src/layout/index.vue
// el-menu中新增default-active属性
el-menu background-color#001529 text-colorwhite :default-active$route.path!-- 根据路由动态生成菜单 --Menu :menuListuserStore.menuRoutes/Menu
/el-menu// 获取路由对象
import { useRoute } from vue-router
let $route useRoute() tabbar组件封装拆分成左侧面包屑组件breadcrumb和右侧设置组件setting
面包屑组件scr/layout/tabbar/breadcrumb/index.vue
template!-- 顶部左侧静态 --el-icon stylemargin-right: 10px;Expand //el-icon!-- 左侧面包屑 --el-breadcrumb separator-iconArrowRightel-breadcrumb-item权限管理/el-breadcrumb-itemel-breadcrumb-item用户管理/el-breadcrumb-item/el-breadcrumb
/templatescript setup langts/scriptstyle scoped/style
设置组件scr/layout/tabbar/setting/index.vue
templateel-button sizesmall iconRefresh circle/el-buttonel-button sizesmall iconFullScreen circle/el-buttonel-button sizesmall iconSetting circle/el-buttonimg src/public/logo.png stylewidth: 20px;height: 20px;margin: 0 10px;!-- 下拉菜单 --el-dropdownspan classel-dropdown-linkadminel-icon classel-icon--rightarrow-down //el-icon/spantemplate #dropdownel-dropdown-menuel-dropdown-item退出登录/el-dropdown-item/el-dropdown-menu/template/el-dropdown
/templatescript setup langts/scriptstyle scoped/style tabbar组件scr/layout/tabbar/index.vue
templatediv classtabbardiv classtabbar_leftBreadcrumb //divdiv classtabbar_rightSetting //div/div
/templatescript setup langts
import Breadcrumb from ./breadcrumb/index.vue
import Setting from ./setting/index.vue
/scriptstyle scoped langscss
.tabbar {width: 100%;height: 100%;display: flex;justify-content: space-between;.tabbar_left {display: flex;margin-left: 20px;align-items: center;}.tabbar_right {display: flex;align-items: center;}
}
/style 3.4.2 菜单折叠效果实现
定义控制折叠/展开响应式数据foldsrc/store/modules/setting.ts 因为layout组件和breadcrumb组件都需要用到fold说定义在仓库比较合适。 // 小仓库layout组件相关配置仓库
import { defineStore } from piniaconst useLayoutSettingStore defineStore(SettingStore, {state: () {return {fold: false, // 用户控制菜单折叠还是收起}}
})export default useLayoutSettingStore 面包屑组件折叠图标切换实现src/layout/tabbar/breadcrumb/index.vue
template!-- 顶部左侧静态 --el-icon stylemargin-right: 10px; clickchangeIconcomponent :islayoutSettingStore.fold ? Expand : Fold/component/el-icon......
/templatescript setup langts
import useLayoutSettingStore from /store/modules/setting;
// 获取layout配置相关的仓库
let layoutSettingStore useLayoutSettingStore()
// 点击图标的方法
const changeIcon () {// 图标进行切换layoutSettingStore.fold !layoutSettingStore.fold
}
/script
layout组件菜单折叠效果实现src/layout/index.vue 步骤 通过el-menu标签的collapse属性配合fold实现菜单折叠/展开效果给左侧菜单、顶部导航、右侧内容展示区域添加动态类fold实现折叠/展开的布局改变 PS折叠之后图标不见的问题将icon标签放在title插槽外面
templatediv classlayout_container!-- 左侧菜单 --div classlayout_slider :class{ fold: layoutSettingStore.fold ? true : false }Logo/Logo!-- 展示菜单 --!-- 滚动组件 --el-scrollbar classscrollbar!-- 菜单组件 --el-menu background-color#001529 text-colorwhite :default-active$route.path:collapselayoutSettingStore.fold!-- 根据路由动态生成菜单 --Menu :menuListuserStore.menuRoutes/Menu/el-menu/el-scrollbar/div!-- 顶部导航 --div classlayout_tabbar :class{ fold: layoutSettingStore.fold ? true : false }Tabbar/Tabbar/div!-- 内容展示区域 --div classlayout_main :class{ fold: layoutSettingStore.fold ? true : false }Main/Main/div/div
/templatescript setup langts
......
import useLayoutSettingStore from /store/modules/setting;
let userStore useUserStore()
// 获取layout配置仓库
let layoutSettingStore useLayoutSettingStore()
/scriptstyle scoped langscss
.layout_container {.......fold {width: $base-menu-min-height;}}.layout_tabbar {.......fold {width: calc(100vw - $base-menu-min-height);left: $base-menu-min-height;}}.layout_main {.......fold {width: calc(100vw - $base-menu-min-height);left: $base-menu-min-height;}}
}
/style
3.4.3 顶部面包屑动态展示 通过$route.matched获取匹配的路由信息实现动态展示。点击首页不需要展示layout路由所以删除router.ts文件中layout路由中的元信息title和icon的值并通过v-show判断是否展示。通过 :to 可使点击面包屑跳转匹配路由。 src/layout/tabbar/breadcrumb/index.vue !-- 左侧面包屑 --
el-breadcrumb separator-iconArrowRight!-- 面包屑动态展示路由图标与标题 --el-breadcrumb-item v-for(item, index) in $route.matched :keyindex v-showitem.meta.title :toitem.path!-- 图标 --el-iconcomponent :isitem.meta.icon/component/el-icon!-- 标题 --span{{ item.meta.title }}/span/el-breadcrumb-item
/el-breadcrumb// 获取路由对象
import { useRoute } from vue-router
let $route useRoute() PS点击商品管理、权限管理等一级路由的面包屑时默认跳转到它的首个二级路由因此需要在router.ts文件中给商品管理、权限管理的路由添加重定向。
redirect: /acl/user,
redirect: /product/trademark, 3.4.4 刷新业务的实现
刷新业务就是路由组件销毁和重建的过程。涉及顶部导航组件和内容区域组件通信因此可以使用store存储刷新业务相关标识。
小仓库中添加刷新标识数据src/store/modules/setting.ts
refresh: false,// 用于控制刷新效果
顶部导航setting组件实现控制下仓库refresh变化 src/layout/tabbar/setting/index.vue
// 给刷新按钮绑定点击事件
el-button sizesmall iconRefresh circle clickupdateRefresh/el-button// 获取仓库中刷新标识
import useLayoutSettingStore from /store/modules/setting
let layoutSettingStore useLayoutSettingStore()
// 刷新按钮点击回调
const updateRefresh () {// 更新刷新标识layoutSettingStore.refresh !layoutSettingStore.refresh
}
main组件中监听小仓库refresh是否变化控制路由销毁与重建src/layout/main/index.vue component :isComponent v-ifflag /import { watch, ref, nextTick } from vue
import useLayoutSettingStore from /store/modules/setting
let layoutSettingStore useLayoutSettingStore()
// 控制当前组件是否销毁重建
let flag ref(true)
// 监听仓库内部数据是否发生变化如果发生变化说明用户点击过刷新按钮
watch(() layoutSettingStore.refresh, () {// 点击刷新按钮路由组件销毁flag.value falsenextTick(() {flag.value true})
})
3.4.5 全屏模式的切换
这里利用DOM实现全屏切换不同浏览器可能会有兼容问题也可以使用插件实现。
src/layout/tabbar/setting/index.vue
// 给全屏按钮绑定点击事件
el-button sizesmall iconFullScreen circle clickfullScreen/el-button// 全屏按钮点击回调
const fullScreen () {// DOM对象的一个属性可以用来判断当前是不是全屏模式全屏true不是全屏falselet full document.fullscreenElement// 切换为全屏模式if (!full) {// 文档根节点的方法requestFullscreen实现全屏模式document.documentElement.requestFullscreen()} else {// 变为不是全屏模式 - 退出全屏模式document.exitFullscreen()}
} 3.4.6 获取用户信息与token理解
发生登录请求时由后端返回的唯一标识后续向后端发送各种请求都需要携带token因此token作为每次请求都需带的公共参数放在请求拦截器里通过config配置项hearders携带最合适。
src/utils/request.ts
// 引入用户相关的小仓库
import useUserStore from /store/modules/user;request.interceptors.request.use((config) {// config配置对象包括hearders属性请求头经常给服务端携带公共参数let useStore useUserStore()if(useStore.token){config.headers.token useStore.token}// 返回配置对象return config;
});
home首页挂载完毕发请求获取用户信息src/views/home/index.vue
import {onMounted} from vue
// 获取仓库
import useUserStore from /store/modules/user;
let useStore useUserStore()
// 目前首页挂载完毕发请求获取用户信息
onMounted(() {useStore.userInfo()
}) 用户小仓库src/store/modules/user.ts 在type.ts文件中定义username、avatar类型 username: string,
avatar: string // 小仓库存储数据的地方state: (): UserState {return {......username:,avatar:}},// 异步|逻辑的地方actions: {......// 获取用户信息async userInfo(){// 获取用户信息进行存储仓库当中用户头像、名字let result await reqUserInfo()// 如果获取信息成功存储下用户信息if(result.code 200){this.username result.data.checkUser.usernamethis.avatar result.data.checkUser.avatar}}},
在setting组件中通过user小仓库获取用户信息进行展示src/layout/tabbar/setting/index.vue ......
img :srcuseStore.avatar stylewidth: 20px;height: 20px;margin: 0 10px;border-radius: 50%;!-- 下拉菜单 --
el-dropdownspan classel-dropdown-link{{ useStore.username }}/span......
/el-dropdown// 获取用户相关的小仓库
import useUserStore from /store/modules/user;
let useStore useUserStore()
3.4.7 退出登录业务 退出登录时需要做的事情 需要向服务器发请求退出登录接口仓库中关于用户相关的数据清空token|username|avatar跳转到登录页面 src/layout/tabbar/setting/index.vue
el-dropdown-item clicklogout退出登录/el-dropdown-item// 退出登录点击回调
const logout () {// 第一件事情需要向服务器发请求退出登录接口----目前还没有// 第二件事情仓库中关于用户相关的数据清空token|username|avataruseStore.userLogout()// 第三件事情跳转到登录页面通过query参数传递退出登录前的路径$router.push({ path: /login, query: { redirect: $route.path } })
} 封装删除token本地存储的方法src/utils/token.ts
// 本地存储删除数据方法
export const REMOVE_TOKEN () {localStorage.removeItem(TOKEN)
} 用户小仓库src/store/modules/user.ts
// 引入操作本地存储的工具方法
import { SET_TOKEN, GET_TOKEN, REMOVE_TOKEN } from /utils/token// 退出登录
userLogout() {// 目前没有mock接口退出登录接口通知服务器本地用户唯一标识失败this.token this.username this.avatar REMOVE_TOKEN()}
login组件添加登录前判断跳转路由的逻辑src/views/login/index.vue
import { useRouter, useRoute } from vue-router
// 获取路由对象
let $route useRoute()......// 判断登录的时候路由的路径当中是否有query参数如果有就往query参数跳转没有就跳转到首页
let redirect: any $route.query.redirect
$router.push({ path: redirect || / })
四、路由鉴权和进度条业务
路由鉴权 项目中能不能被访问的权限设置某一个路由什么条件下可以访问什么条件下不可以访问。 安装nprogress插件pnpm i nprogress src/permission.ts
// 路由鉴权项目中能不能被访问的权限设置某一个路由什么条件下可以访问什么条件下不可以访问
import router from /router
import setting from /setting
import { SET_TOKEN, GET_TOKEN, REMOVE_TOKEN } from /utils/token
// ts-ignore
import nprogress from nprogress
// 引入进度条样式
import nprogress/nprogress.css
nprogress.configure({ showSpinner: false })
// 获取用户相关的小仓库内部token数据去判断用户是否登录成功
import useUserStore from ./store/modules/user
import pinia from ./store
let useStore useUserStore(pinia)
// 全局守卫项目中任意路由切换都会触发的钩子
// 全局前置守卫
router.beforeEach(async (to: any, from: any, next: any) {// to你将要访问哪个路由// from你从哪个路由而来// next路由的放行函数// 进度条开始nprogress.start()// 获取token去判断用户登录还是未登录let token useStore.token// 获取用户名字let username useStore.username// 用户登录判断if (token) {// 登录成功不能访问login指向homeif (to.path /login) {next({ path: / })} else {// 登录成功访问其余六个路由登录排除// 有用户信息if (username) {// 放行next()} else {// 如果没有用户信息在守卫这里发请求获取到了用户信息再放行try {// 获取用户信息await useStore.userInfo()// 放行next()} catch (error) {// token过期获取不到用户信息了// 用户手动修改本地存储token// 退出登录-用户相关的数据清空useStore.userLogout()next({ path: /login })}}}} else {// 用户未登录判断if (to.path /login) {next()} else {next({ path: /login, query: { redirect: to.path } })}}
})
// 全局后置守卫
router.afterEach((to: any, from: any) {document.title ${setting.title} - ${to.meta.title}// 进度条结束nprogress.done()
})// 第一个问题任意路由切换实现进度条业务 ---nprogress
// 第二个问题路由鉴权路由组件访问权限的设置
// 全部路由组件登录|404|任意路由|首页|数据大屏|权限管理三个子路由|商品管理四个子路由// 用户未登录可以访问login其余六个路由不能访问指向login
// 用户登录成功不可以访问login指向首页
PS在组件的外部通过同步的语句获取仓库的数据是拿不到的。如果想获取小仓库的数据必须先得有大仓库pinia 。
在入口文件main.ts引入鉴权文件
// 引入路由鉴权文件
import ./permission 五、真实接口替换mock接口和接口ts类型定义
1. 替换各个环境下的服务器地址 .env.development、.env.production、.env.test
2. 配饰代理跨域vite.config.ts具体配置参数可参考官网开发服务器选项 | Vite 官方中文文档
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/, ),}}}}
}) 3. 重新书写API接口文件及接口类型文件
src/api/user/index.ts
// 统一管理项目用户相关的接口
import request from /utils/request;
import type { loginFormData, loginResponseData, userInfoResponeData } 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.postany, loginResponseData(API.LOGIN_URL, data)
// 获取用户信息
export const reqUserInfo () request.getany, userInfoResponeData(API.USERINFO_URL)
// 退出登录
export const reqLogout () request.postany, any(API.LOGOUT_URL) src/api/user/index.ts
// 定义用户相关数据的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 userInfoResponeData extends ResponseData {data: {routes: string[],buttons: string[],roles: string[],name: string,avatar: string}
} 4. 修改接口相关的代码src/store/modules/user.ts、permission.ts等文件