今天继续进行vue3-admin商品管理后台项目开发的后台布局layout布局开发。
首先布局使用的是element plus里的container布局容器里推荐的布局方式
用于布局的容器组件,方便快速搭建页面的基本结构:
<el-container>:外层容器。 当子元素中包含 <el-header> 或 <el-footer> 时,全部子元素会垂直上下排列, 否则会水平左右排列。
<el-header>:顶栏容器。
<el-aside>:侧边栏容器。
<el-main>:主要区域容器。
<el-footer>:底栏容器。
在src文件夹下创建layout文件夹,并且在该文件夹下创建admin.vue
,然后再layout文件夹下创建components文件夹,在该文件夹下面创建FHeader.vue(头部),FMenu.vue(侧边栏),FTagList.vue(标签导航栏部分)
编辑admin.vue
<template>
<el-container>
<el-header>
<f-header/>
</el-header>
<el-container>
<el-aside>
<f-menu/>
</el-aside>
<el-main>
<f-tag-list/>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<!-- <el-container>:外层容器。 当子元素中包含 <el-header> 或 <el-footer> 时,全部子元素会垂直上下排列, 否则会水平左右排列。
<el-header>:顶栏容器。
<el-aside>:侧边栏容器。
<el-main>:主要区域容器。
<el-footer>:底栏容器。 -->
<script setup>
import FHeader from './components/FHeader.vue'
import FMenu from './components/FMenu.vue'
import FTagList from './components/FTagList.vue'
</script>
主要是对页面的结构进行分块
然后分别编辑FHeader.vue(头部),FMenu.vue(侧边栏),FTagList.vue(标签导航栏部分),加上
<!-- 头部部分 -->
<template>
<div>
头部部分
</div>
</template>
以此类推都是这样
然后编辑router文件夹下面的index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import Index from '~/pages/index.vue'
import Login from '~/pages/login.vue'
import NotFound from '~/pages/404.vue'
// 引入主布局
import Admin from "~/layouts/admin.vue"
const routes = [{
// 根路由
path:"/",
component:Admin,
// 子路由
children:[{
path:"/",
component:Index,
meta:{
title:"后台首页"
}
}]
},{
// 登录路由
path:"/login",
component:Login,
meta:{
title:"登录页"
}
},{
//404路由 将匹配所有内容并将其放在 `$route.params.pathMatch` 下
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
}]
const router = createRouter({
history:createWebHashHistory(),
routes
})
export default router
主要是改了根路由那一部分。
这样之后再运行项目登录到主页,就会发现,分块成功了
这样我们的初步目的就达到了。
然后我们继续进行公共头部开发-样式布局:
这里直接上写好的代码了,注释很清楚:按钮以及下拉框什么的都是从element plus里面取下来的
<!-- 头部部分 -->
<template>
<div class="f-header">
<span class="logo">
<!-- logo图标 -->
<!-- mr-1就是margin-right:0.25rem -->
<el-icon class="mr-1"><ElemeFilled /></el-icon>
mzldustu商城
</span>
<!-- 折叠图标 -->
<el-icon class="icon-btn"><Fold /></el-icon>
<!-- 刷新图标 -->
<el-icon class="icon-btn"><Refresh /></el-icon>
<!-- 等价于margin-left = auto,flex布局,垂直居中-->
<div class="ml-auto flex justify-center">
<!-- 全屏图标 -->
<el-icon class="icon-btn"><FullScreen /></el-icon>
<el-dropdown class="dropdown">
<!-- flex布局,垂直居中 字体颜色 -->
<span class="flex items-center text-light-50">
<!-- 获取用户头像 头像的右外边距-->
<el-avatar class="mr-2" :size="50" :src="$store.state.user.avatar" />
<!-- 获取用户username -->
{{ $store.state.user.username }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>修改密码</el-dropdown-item>
<el-dropdown-item>退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script>
</script>
<style>
.f-header {
/* flex布局,垂直居中,背景颜色,字体亮度,fixed,top:0 left:0 right:0 */
@apply flex items-center bg-indigo-700 text-light-50 fixed top-0 left-0 right-0;
height: 64px;
}
.logo {
width: 250px;
/* flex布局,垂直水平居中,字体大小xl,字体稍微瘦一点*/
@apply flex justify-center items-center text-xl font-thin;
}
/* 按钮 */
.icon-btn {
/* flex布局 水平居中 垂直居中 */
@apply flex justify-center items-center;
width: 42px;
height: 64px;
cursor: pointer;
}
/* 鼠标覆盖到按钮颜色变浅 */
.icon-btn:hover{
@apply bg-indigo-600;
}
/* 下拉菜单 */
.f-header .dropdown {
height: 64px;
cursor: pointer;
/* flex布局,垂直水平居中 左右距离 */
@apply flex justify-center items-center mx-5;
}
</style>
然后我们继续来写刷新和全屏:
首先给那两个按钮增加一个提示,就是鼠标移上去的时候会显示这是什么功能
代码实现:
<!-- 刷新图标以及它的鼠标触碰提示 -->
<el-tooltip effect="dark" content="刷新" placement="bottom">
<!-- 刷新图标 -->
<el-icon class="icon-btn" @click="handleRefresh"><Refresh /></el-icon>
</el-tooltip>
全屏功能也以此类推
然后做方法功能,另外,退出登录功能也可以迁移过来了,将之前写退出登陆的所有逻辑复制过来,然后利用element下拉栏里面的@command功能绑定方法即可。
然后刷新功能就很简单:
// 刷新方法
const handleRefresh = ()=> location.reload()
全屏功能的话,是使用vueuse里面的一个useFullscreen功能:
// 获取全屏功能里面一些方法
//是否全屏,切换
const { isFullscreen, toggle } = useFullscreen()
然后直接在刷新图标写上它里面提供的toggle方法即可
<!-- 全屏图标 -->
<el-icon class="icon-btn" @click="toggle"><FullScreen /></el-icon>
然后此时就可以实现刷新和全屏功能了
然后我们来实现修改密码的功能:
先去看修改密码的接口,然后使用element 的抽屉组件。
先在manager.js里面写上接口方法:
// 修改密码
export function updatePassword(data){
return axios.post("/admin/updatepassword",data)
}
直接上代码了,主要是引用的login页面的表单,不过修改成了输入旧密码,输入新密码和确认密码,修改成功后退出登录然后重新登录,如果你浏览器里的token失效了,你进行一些操作的话,会直接给你退出让你先登录。
这里直接放上代码:
<!-- 头部部分 -->
<template>
<div class="f-header">
<span class="logo">
<!-- logo图标 -->
<!-- mr-1就是margin-right:0.25rem -->
<el-icon class="mr-1">
<ElemeFilled />
</el-icon>
mzldustu商城
</span>
<!-- 折叠图标 -->
<el-icon class="icon-btn">
<Fold />
</el-icon>
<!-- 刷新图标以及它的鼠标触碰提示 -->
<el-tooltip effect="dark" content="刷新" placement="bottom">
<!-- 刷新图标 -->
<el-icon class="icon-btn" @click="handleRefresh">
<Refresh />
</el-icon>
</el-tooltip>
<!-- 等价于margin-left = auto,flex布局,垂直居中-->
<div class="ml-auto flex justify-center">
<!-- 全屏图标以及它的鼠标触碰提示 -->
<el-tooltip effect="dark" content="全屏" placement="bottom">
<!-- 全屏图标 -->
<el-icon class="icon-btn" @click="toggle">
<FullScreen />
</el-icon>
</el-tooltip>
<!-- @command是element里面下拉框能触发事件回调的指令 -->
<el-dropdown class="dropdown" @command="handleCommand">
<!-- flex布局,垂直居中 字体颜色 -->
<span class="flex items-center text-light-50">
<!-- 获取用户头像 头像的右外边距-->
<el-avatar class="mr-2" :size="50" :src="$store.state.user.avatar" />
<!-- 获取用户username -->
{{ $store.state.user.username }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="rePassword">修改密码</el-dropdown-item>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<!-- 抽屉组件 -->
<!-- :close-on-click-modal="false"是设置点到其他旁边空白是否会关闭抽屉,为了防止误触,我们还是关闭了 -->
<el-drawer v-model="showDrawer" title="修改密码" size="45%" :close-on-click-modal="false">
<el-form ref="formRef" :rules="rules" :model="form" label-width="80px" size="small">
<!-- 旧密码输入框 -->
<el-form-item prop="oldpassword" label="旧密码">
<el-input type="password" v-model="form.oldpassword" placeholder="请输入旧密码">
</el-input>
</el-form-item>
<!-- 密码输入框 -->
<el-form-item prop="password" label="新密码">
<!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
<el-input type="password" v-model="form.password" placeholder="请输入新密码" show-password>
</el-input>
</el-form-item>
<!-- 重复新密码输入框 -->
<el-form-item prop="repassword" label="确认密码">
<!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
<el-input type="password" v-model="form.repassword" placeholder="请再次输入新密码" show-password>
</el-input>
</el-form-item>
<!-- 按钮 -->
<el-form-item>
<!-- 加一个loading状态,正常状态下是false,改变之后就是true -->
<el-button type="primary" @click="onSubmit" :loading="loading">
提交</el-button>
</el-form-item>
</el-form>
</el-drawer>
</template>
<script setup>
// 退出接口
import { logout, updatepassword } from "~/api/manager"
// 提示框
import { showModal, toast } from "~/composables/util"
// 方便页面跳转
import { useRouter } from "vue-router"
// 引入useStore
import { useStore } from 'vuex'
// 引入全屏功能
import { useFullscreen } from '@vueuse/core'
// 抽屉组件
import { ref, reactive } from 'vue'
// 获取全屏功能里面一些方法
//是否全屏,切换
const { isFullscreen, toggle } = useFullscreen()
const router = useRouter()
const store = useStore()
// 修改密码部分
// 通过控制showDrawer的true和false来控制显示和隐藏
const showDrawer = ref(false)
// do not use same name with ref
const form = reactive({
oldpassword: "",
password: "",
repassword: ""
})
// 表单验证rules,要在前面指定prop
const rules = {
oldpassword: [
// 书写验证规则
{
required: true,
message: '旧密码不能为空',
// 失去焦点的时候触发
trigger: 'blur'
},
],
password: [
{
required: true,
message: '新密码不能为空',
// 失去焦点的时候触发
trigger: 'blur'
},
],
repassword: [
{
required: true,
message: '确认密码不能为空',
// 失去焦点的时候触发
trigger: 'blur'
},
]
}
// setup里拿到el-form节点
const formRef = ref(null)
// 定义一个loading,默认让它为false,不显示
const loading = ref(false)
const onSubmit = () => {
formRef.value.validate((valid) => {
// 会输出true或者false
// console.log(valid)
if (!valid) {
return false
}
// 提交验证之前让按钮loading
loading.value = true
// 提交验证之后执行
// 因为接口要传data,所以这里要把form传过来
updatepassword(form)
.then(res=>{
toast("修改密码成功,请重新登录")
store.dispatch("logout")
// 跳转回登录页
router.push("/login")
}).finally(()=>{
loading.value = false
})
})
}
// 下拉框的选项方法
const handleCommand = (c) => {
// 控制台发现能拿到它指定的rePassword和logout
// console.log(c)
switch (c) {
case "logout":
handleLogout()
break;
case "rePassword":
showDrawer.value = true
break;
}
}
// 退出方法
function handleLogout() {
showModal("是否要退出登录?").then(res => {
console.log("退出登录");
logout().finally(() => {
// 移出cookie里的token
// 清除当前用户状态 vuex里的user
// 上面的两步已经在store里面实现了
store.dispatch("logout")
// 跳转回登录页
router.push("/login")
// 提示退出登录成功
toast("退出登录成功")
})
})
}
// 刷新方法
const handleRefresh = () => location.reload()
</script>
<style>
.f-header {
/* flex布局,垂直居中,背景颜色,字体亮度,fixed,top:0 left:0 right:0 */
@apply flex items-center bg-indigo-700 text-light-50 fixed top-0 left-0 right-0;
height: 64px;
}
.logo {
width: 250px;
/* flex布局,垂直水平居中,字体大小xl,字体稍微瘦一点*/
@apply flex justify-center items-center text-xl font-thin;
}
/* 按钮 */
.icon-btn {
/* flex布局 水平居中 垂直居中 */
@apply flex justify-center items-center;
width: 42px;
height: 64px;
cursor: pointer;
}
/* 鼠标覆盖到按钮颜色变浅 */
.icon-btn:hover {
@apply bg-indigo-600;
}
/* 下拉菜单 */
.f-header .dropdown {
height: 64px;
cursor: pointer;
/* flex布局,垂直水平居中 左右距离 */
@apply flex justify-center items-center mx-5;
}
</style>
然后以及axios的代码:
到此为止我们的头部以及功能就全部开发完了。后面继续其他功能以及封装。
然后我们进行通用弹框表单组件的封装:
在components文件夹下面创建FormDrawer.vue
编辑:
<template>
<!-- 抽屉组件 -->
<!-- :close-on-click-modal="false"是设置点到其他旁边空白是否会关闭抽屉,为了防止误触,我们还是关闭了 -->
<el-drawer v-model="showDrawer" title="修改密码" size="45%" :close-on-click-modal="false">
<div class="formDrawer">
<!-- 内容部分 -->
<div class="body">
<slot></slot>
</div>
<!-- 按钮部分 -->
<div class="actions">
<el-button type="primary">提交</el-button>
<el-button type="default" @click="close">取消</el-button>
</div>
</div>
</el-drawer>
</template>
<script setup>
import { ref } from "vue"
// 因为是响应式,控制显示和隐藏
const showDrawer = ref(false)
// 打开
const open = ()=> showDrawer.value = true
// 关闭
const close = () => showDrawer.value = false
// 在这里定义的方法并不能通过外部拿到,像父组件暴露以下方法,因为默认是关闭的
defineExpose({
open,
close
})
</script>
<style>
.formDrawer {
width: 100%;
height: 100%;
position: relative;
@apply flex flex-col;
}
.formDrawer .body{
flex: 1;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 50px;
/* 超出部分上下滚动 */
overflow-y: auto;
}
.formDrawer .actions{
height: 50px;
@apply mt-auto flex items-center;
}
</style>
这里主要是封装一个公共的抽屉组件,以及设置它的open和close方法,另外别忘了要暴露出去,因为script setup里面的组件方法并不能直接被外界调用
在FHeader.vue里面先注释掉之前写的修改密码抽屉
然后import引入通用抽屉组件
然后直接调用
然后上FHeader.vue代码:
<!-- 头部部分 -->
<template>
<div class="f-header">
<span class="logo">
<!-- logo图标 -->
<!-- mr-1就是margin-right:0.25rem -->
<el-icon class="mr-1">
<ElemeFilled />
</el-icon>
mzldustu商城
</span>
<!-- 折叠图标 -->
<el-icon class="icon-btn">
<Fold />
</el-icon>
<!-- 刷新图标以及它的鼠标触碰提示 -->
<el-tooltip effect="dark" content="刷新" placement="bottom">
<!-- 刷新图标 -->
<el-icon class="icon-btn" @click="handleRefresh">
<Refresh />
</el-icon>
</el-tooltip>
<!-- 等价于margin-left = auto,flex布局,垂直居中-->
<div class="ml-auto flex justify-center">
<!-- 全屏图标以及它的鼠标触碰提示 -->
<el-tooltip effect="dark" content="全屏" placement="bottom">
<!-- 全屏图标 -->
<el-icon class="icon-btn" @click="toggle">
<FullScreen />
</el-icon>
</el-tooltip>
<!-- @command是element里面下拉框能触发事件回调的指令 -->
<el-dropdown class="dropdown" @command="handleCommand">
<!-- flex布局,垂直居中 字体颜色 -->
<span class="flex items-center text-light-50">
<!-- 获取用户头像 头像的右外边距-->
<el-avatar class="mr-2" :size="50" :src="$store.state.user.avatar" />
<!-- 获取用户username -->
{{ $store.state.user.username }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="rePassword">修改密码</el-dropdown-item>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<!-- 抽屉组件 -->
<!-- :close-on-click-modal="false"是设置点到其他旁边空白是否会关闭抽屉,为了防止误触,我们还是关闭了 -->
<!-- <el-drawer v-model="showDrawer" title="修改密码" size="45%" :close-on-click-modal="false">
<el-form ref="formRef" :rules="rules" :model="form" label-width="80px" size="small"> -->
<!-- 旧密码输入框 -->
<!-- <el-form-item prop="oldpassword" label="旧密码">
<el-input type="password" v-model="form.oldpassword" placeholder="请输入旧密码">
</el-input>
</el-form-item> -->
<!-- 密码输入框 -->
<!-- <el-form-item prop="password" label="新密码"> -->
<!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
<!-- <el-input type="password" v-model="form.password" placeholder="请输入新密码" show-password>
</el-input>
</el-form-item> -->
<!-- 重复新密码输入框 -->
<!-- <el-form-item prop="repassword" label="确认密码"> -->
<!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
<!-- <el-input type="password" v-model="form.repassword" placeholder="请再次输入新密码" show-password>
</el-input>
</el-form-item> -->
<!-- 按钮 -->
<!-- <el-form-item> -->
<!-- 加一个loading状态,正常状态下是false,改变之后就是true -->
<!-- <el-button type="primary" @click="onSubmit" :loading="loading">
提交</el-button>
</el-form-item>
</el-form>
</el-drawer> -->
<!-- 直接调用form-drawer -->
<form-drawer ref="formDrawerRef">
123
<div class="bg-rose-400" style="height: 1000px;"></div>
</form-drawer>
</template>
<script setup>
// 退出接口
import { logout, updatepassword } from "~/api/manager"
// 提示框
import { showModal, toast } from "~/composables/util"
// 方便页面跳转
import { useRouter } from "vue-router"
// 引入useStore
import { useStore } from 'vuex'
// 引入全屏功能
import { useFullscreen } from '@vueuse/core'
// 抽屉组件
import { ref, reactive } from 'vue'
// 引入通用抽屉组件
import FormDrawer from '../../components/FormDraw.vue'
// 获取全屏功能里面一些方法
//是否全屏,切换
const { isFullscreen, toggle } = useFullscreen()
const router = useRouter()
const store = useStore()
// 修改密码部分
// 通过控制showDrawer的true和false来控制显示和隐藏
const formDrawerRef = ref(null)
const showDrawer = ref(false)
// do not use same name with ref
const form = reactive({
oldpassword: "",
password: "",
repassword: ""
})
// 表单验证rules,要在前面指定prop
const rules = {
oldpassword: [
// 书写验证规则
{
required: true,
message: '旧密码不能为空',
// 失去焦点的时候触发
trigger: 'blur'
},
],
password: [
{
required: true,
message: '新密码不能为空',
// 失去焦点的时候触发
trigger: 'blur'
},
],
repassword: [
{
required: true,
message: '确认密码不能为空',
// 失去焦点的时候触发
trigger: 'blur'
},
]
}
// setup里拿到el-form节点
const formRef = ref(null)
// 定义一个loading,默认让它为false,不显示
const loading = ref(false)
const onSubmit = () => {
formRef.value.validate((valid) => {
// 会输出true或者false
// console.log(valid)
if (!valid) {
return false
}
// 提交验证之前让按钮loading
loading.value = true
// 提交验证之后执行
// 因为接口要传data,所以这里要把form传过来
updatepassword(form)
.then(res=>{
toast("修改密码成功,请重新登录")
store.dispatch("logout")
// 跳转回登录页
router.push("/login")
}).finally(()=>{
loading.value = false
})
})
}
// 下拉框的选项方法
const handleCommand = (c) => {
// 控制台发现能拿到它指定的rePassword和logout
// console.log(c)
switch (c) {
case "logout":
handleLogout()
break;
case "rePassword":
// showDrawer.value = true
// 就不能单独showDrawer,要用上面的showDrawer
formDrawerRef.value.open()
break;
}
}
// 退出方法
function handleLogout() {
showModal("是否要退出登录?").then(res => {
console.log("退出登录");
logout().finally(() => {
// 移出cookie里的token
// 清除当前用户状态 vuex里的user
// 上面的两步已经在store里面实现了
store.dispatch("logout")
// 跳转回登录页
router.push("/login")
// 提示退出登录成功
toast("退出登录成功")
})
})
}
// 刷新方法
const handleRefresh = () => location.reload()
</script>
<style>
.f-header {
/* flex布局,垂直居中,背景颜色,字体亮度,fixed,top:0 left:0 right:0 */
@apply flex items-center bg-indigo-700 text-light-50 fixed top-0 left-0 right-0;
height: 64px;
}
.logo {
width: 250px;
/* flex布局,垂直水平居中,字体大小xl,字体稍微瘦一点*/
@apply flex justify-center items-center text-xl font-thin;
}
/* 按钮 */
.icon-btn {
/* flex布局 水平居中 垂直居中 */
@apply flex justify-center items-center;
width: 42px;
height: 64px;
cursor: pointer;
}
/* 鼠标覆盖到按钮颜色变浅 */
.icon-btn:hover {
@apply bg-indigo-600;
}
/* 下拉菜单 */
.f-header .dropdown {
height: 64px;
cursor: pointer;
/* flex布局,垂直水平居中 左右距离 */
@apply flex justify-center items-center mx-5;
}
</style>
再加上上面的FormDrawer.vue代码及样式,我们发现基本样式已经实现了。运行查看
然后继续完善FormDrawer组件
首先设置不同抽屉页变化不同标题,就要将标题设置为动态的,然后封装了抽屉的size和title,将其改为了动态的,然后就是封装了loading功能,主要是利用vue3里面的defineProps和defineEmits
直接上代码了,
FormDrawer.vue
<template>
<!-- 抽屉组件 -->
<!-- :close-on-click-modal="false"是设置点到其他旁边空白是否会关闭抽屉,为了防止误触,我们还是关闭了 -->
<el-drawer v-model="showDrawer" :title="title" :size="size" :close-on-click-modal="false" :destroy-on-close="destroyOnClose">
<div class="formDrawer">
<!-- 内容部分 -->
<div class="body">
<slot></slot>
</div>
<!-- 按钮部分 -->
<div class="actions">
<!-- 点击这个click事件,触发submit,再去通知下面暴露出的submit事件,FHeader那边就能通过触发这个事件 -->
<el-button type="primary" @click="submit" :loading="loading">{{ confirmText }}</el-button>
<el-button type="default" @click="close">取消</el-button>
</div>
</div>
</el-drawer>
</template>
<script setup>
import { ref } from "vue"
// 因为是响应式,控制显示和隐藏
const showDrawer = ref(false)
// 这之后就能直接动态上面的title了,size也是可以动态了,所以上面的size和title都加上了冒号,然后我们就能在外面配置size和title了
const props = defineProps({
title:String,
size:{
type:String,
default:"45%"
},
destroyOnClose:{
// 控制drawer关闭的时候是否清除全部子元素
type:Boolean,
default:false
},
confirmText:{
type:String,
default:"提交"
}
})
// 定义loading组件
const loading = ref(false)
const showLoading = () =>loading.value = true
const hideLoading = () =>loading.value = false
// 打开
const open = ()=> showDrawer.value = true
// 关闭
const close = () => showDrawer.value = false
// 提交,用到了vue3的defineEmits,暴露submit事件
const emit = defineEmits(["submit"])
const submit = () =>emit("submit")
// 在这里定义的方法并不能通过外部拿到,像父组件暴露以下方法,因为默认是关闭的
defineExpose({
open,
close,
showLoading,
hideLoading
})
</script>
<style>
.formDrawer {
width: 100%;
height: 100%;
position: relative;
@apply flex flex-col;
}
.formDrawer .body{
flex: 1;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 50px;
/* 超出部分上下滚动 */
overflow-y: auto;
}
.formDrawer .actions{
height: 50px;
@apply mt-auto flex items-center;
}
</style>
然后是FHeader.vue
<!-- 头部部分 -->
<template>
<div class="f-header">
<span class="logo">
<!-- logo图标 -->
<!-- mr-1就是margin-right:0.25rem -->
<el-icon class="mr-1">
<ElemeFilled />
</el-icon>
mzldustu商城
</span>
<!-- 折叠图标 -->
<el-icon class="icon-btn">
<Fold />
</el-icon>
<!-- 刷新图标以及它的鼠标触碰提示 -->
<el-tooltip effect="dark" content="刷新" placement="bottom">
<!-- 刷新图标 -->
<el-icon class="icon-btn" @click="handleRefresh">
<Refresh />
</el-icon>
</el-tooltip>
<!-- 等价于margin-left = auto,flex布局,垂直居中-->
<div class="ml-auto flex justify-center">
<!-- 全屏图标以及它的鼠标触碰提示 -->
<el-tooltip effect="dark" content="全屏" placement="bottom">
<!-- 全屏图标 -->
<el-icon class="icon-btn" @click="toggle">
<FullScreen />
</el-icon>
</el-tooltip>
<!-- @command是element里面下拉框能触发事件回调的指令 -->
<el-dropdown class="dropdown" @command="handleCommand">
<!-- flex布局,垂直居中 字体颜色 -->
<span class="flex items-center text-light-50">
<!-- 获取用户头像 头像的右外边距-->
<el-avatar class="mr-2" :size="50" :src="$store.state.user.avatar" />
<!-- 获取用户username -->
{{ $store.state.user.username }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="rePassword">修改密码</el-dropdown-item>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<!-- 抽屉组件 -->
<!-- :close-on-click-modal="false"是设置点到其他旁边空白是否会关闭抽屉,为了防止误触,我们还是关闭了 -->
<!-- <el-drawer v-model="showDrawer" title="修改密码" size="45%" :close-on-click-modal="false"> -->
<!-- 这一块的部分全部剪切到下面的插槽了 -->
<!-- </el-drawer> -->
<!-- 直接调用form-drawer -->
<form-drawer ref="formDrawerRef" title="修改密码" destoryOnClose @submit="onSubmit">
<el-form ref="formRef" :rules="rules" :model="form" label-width="80px" size="small">
<!-- 旧密码输入框 -->
<el-form-item prop="oldpassword" label="旧密码">
<el-input type="password" v-model="form.oldpassword" placeholder="请输入旧密码">
</el-input>
</el-form-item>
<!-- 密码输入框 -->
<el-form-item prop="password" label="新密码">
<!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
<el-input type="password" v-model="form.password" placeholder="请输入新密码" show-password>
</el-input>
</el-form-item>
<!-- 重复新密码输入框 -->
<el-form-item prop="repassword" label="确认密码">
<!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
<el-input type="password" v-model="form.repassword" placeholder="请再次输入新密码" show-password>
</el-input>
</el-form-item>
<!-- 按钮,这个按钮目前不需要了,因为已经有submit方法 -->
<!-- <el-form-item> -->
<!-- 加一个loading状态,正常状态下是false,改变之后就是true -->
<!-- <el-button type="primary" @click="onSubmit" :loading="loading"> -->
<!-- 提交</el-button> -->
<!-- </el-form-item> -->
</el-form>
</form-drawer>
</template>
<script setup>
// 退出接口
import { logout, updatepassword } from "~/api/manager"
// 提示框
import { showModal, toast } from "~/composables/util"
// 方便页面跳转
import { useRouter } from "vue-router"
// 引入useStore
import { useStore } from 'vuex'
// 引入全屏功能
import { useFullscreen } from '@vueuse/core'
// 抽屉组件
import { ref, reactive } from 'vue'
// 引入通用抽屉组件
import FormDrawer from '../../components/FormDrawer.vue'
// 获取全屏功能里面一些方法
//是否全屏,切换
const { isFullscreen, toggle } = useFullscreen()
const router = useRouter()
const store = useStore()
// 修改密码部分
// 通过控制showDrawer的true和false来控制显示和隐藏
const formDrawerRef = ref(null)
// 我们就不用showDrawer了,因为使用了FormDrawer里面的showLoading和hideLoading
// const showDrawer = ref(false)
// do not use same name with ref
const form = reactive({
oldpassword: "",
password: "",
repassword: ""
})
// 表单验证rules,要在前面指定prop
const rules = {
oldpassword: [
// 书写验证规则
{
required: true,
message: '旧密码不能为空',
// 失去焦点的时候触发
trigger: 'blur'
},
],
password: [
{
required: true,
message: '新密码不能为空',
// 失去焦点的时候触发
trigger: 'blur'
},
],
repassword: [
{
required: true,
message: '确认密码不能为空',
// 失去焦点的时候触发
trigger: 'blur'
},
]
}
// setup里拿到el-form节点
const formRef = ref(null)
// 定义一个loading,默认让它为false,不显示
// const loading = ref(false)
const onSubmit = () => {
formRef.value.validate((valid) => {
// 会输出true或者false
// console.log(valid)
if (!valid) {
return false
}
// 提交验证之前让按钮loading
// loading.value = true
// 加载换成FormDrawer里面的方法
formDrawerRef.value.showLoading()
// 提交验证之后执行
// 因为接口要传data,所以这里要把form传过来
updatepassword(form)
.then(res=>{
toast("修改密码成功,请重新登录")
store.dispatch("logout")
// 跳转回登录页
router.push("/login")
}).finally(()=>{
// 加载完隐藏
formDrawerRef.value.hideLoading()
})
})
}
// 下拉框的选项方法
const handleCommand = (c) => {
// 控制台发现能拿到它指定的rePassword和logout
// console.log(c)
switch (c) {
case "logout":
handleLogout()
break;
case "rePassword":
// showDrawer.value = true
// 就不能单独showDrawer,要用上面的showDrawer
formDrawerRef.value.open()
break;
}
}
// 退出方法
function handleLogout() {
showModal("是否要退出登录?").then(res => {
console.log("退出登录");
logout().finally(() => {
// 移出cookie里的token
// 清除当前用户状态 vuex里的user
// 上面的两步已经在store里面实现了
store.dispatch("logout")
// 跳转回登录页
router.push("/login")
// 提示退出登录成功
toast("退出登录成功")
})
})
}
// 刷新方法
const handleRefresh = () => location.reload()
</script>
<style>
.f-header {
/* flex布局,垂直居中,背景颜色,字体亮度,fixed,top:0 left:0 right:0 */
@apply flex items-center bg-indigo-700 text-light-50 fixed top-0 left-0 right-0;
height: 64px;
}
.logo {
width: 250px;
/* flex布局,垂直水平居中,字体大小xl,字体稍微瘦一点*/
@apply flex justify-center items-center text-xl font-thin;
}
/* 按钮 */
.icon-btn {
/* flex布局 水平居中 垂直居中 */
@apply flex justify-center items-center;
width: 42px;
height: 64px;
cursor: pointer;
}
/* 鼠标覆盖到按钮颜色变浅 */
.icon-btn:hover {
@apply bg-indigo-600;
}
/* 下拉菜单 */
.f-header .dropdown {
height: 64px;
cursor: pointer;
/* flex布局,垂直水平居中 左右距离 */
@apply flex justify-center items-center mx-5;
}
</style>
今天先到这了,明天继续兄弟们,加油!
继续更新:
我们接着做组合式api封装简化代码:
因为前面的代码放在一起太冗杂,不利于后期的维护,所以我们利用composition api的方法进行封装。
比如FHeader.vue里面有修改密码和退出登录方法,所以我们封装这两个
在composables文件夹下创建一个useManager.js文件,用来存放各种封装的方法。
然后将FHeader.vue里面的修改密码和退出登录方法剪切过来。
// 抽屉组件
import { ref, reactive } from "vue";
// 退出接口
import { logout, updatepassword } from "~/api/manager";
// 提示框
import { showModal, toast } from "~/composables/util";
// 方便页面跳转
import { useRouter } from "vue-router";
// 引入useStore
import { useStore } from "vuex";
// 修改密码封装,在上面引入
export function useRepassword() {
// 引入router
const router = useRouter();
// 引入store
const store = useStore();
// 修改密码部分
// 通过控制showDrawer的true和false来控制显示和隐藏
const formDrawerRef = ref(null);
// 我们就不用showDrawer了,因为使用了FormDrawer里面的showLoading和hideLoading
// const showDrawer = ref(false)
// do not use same name with ref
const form = reactive({
oldpassword: "",
password: "",
repassword: "",
});
// 表单验证rules,要在前面指定prop
const rules = {
oldpassword: [
// 书写验证规则
{
required: true,
message: "旧密码不能为空",
// 失去焦点的时候触发
trigger: "blur",
},
],
password: [
{
required: true,
message: "新密码不能为空",
// 失去焦点的时候触发
trigger: "blur",
},
],
repassword: [
{
required: true,
message: "确认密码不能为空",
// 失去焦点的时候触发
trigger: "blur",
},
],
};
// setup里拿到el-form节点
const formRef = ref(null);
// 定义一个loading,默认让它为false,不显示
// const loading = ref(false)
const onSubmit = () => {
formRef.value.validate((valid) => {
// 会输出true或者false
// console.log(valid)
if (!valid) {
return false;
}
// 提交验证之前让按钮loading
// loading.value = true
// 加载换成FormDrawer里面的方法
formDrawerRef.value.showLoading();
// 提交验证之后执行
// 因为接口要传data,所以这里要把form传过来
updatepassword(form)
.then((res) => {
toast("修改密码成功,请重新登录");
store.dispatch("logout");
// 跳转回登录页
router.push("/login");
})
.finally(() => {
// 加载完隐藏
formDrawerRef.value.hideLoading();
});
});
};
// 修改密码的抽屉的打开
const openRePasswordForm = () => formDrawerRef.value.open();
// 将上面的方法全部return出去
return {
formDrawerRef,
form,
rules,
formRef,
onSubmit,
openRePasswordForm,
};
}
// 退出方法
export function useLogout() {
// 引入router
const router = useRouter();
// 引入store
const store = useStore();
// 退出方法
function handleLogout() {
showModal("是否要退出登录?").then((res) => {
console.log("退出登录");
logout().finally(() => {
// 移出cookie里的token
// 清除当前用户状态 vuex里的user
// 上面的两步已经在store里面实现了
store.dispatch("logout");
// 跳转回登录页
router.push("/login");
// 提示退出登录成功
toast("退出登录成功");
});
});
}
return {
handleLogout
}
}
封装其实没什么东西,主要就是两点,
拿修改密码举例子:
创建useRepassword函数,然后剪切进之前写的方法,然后return导出这些方法。
然后FHeader里面需要引用这里面的代码,就直接这样即可:
这样就达到了效果。
我们运行项目发现修改密码和退出登录功能都是没问题的,说明我们的封装成功了。
项目gitee地址,有需要的兄弟自取。