vue3-admin商品管理后台项目(后台布局layout布局开发一)

发布于:2022-11-27 ⋅ 阅读:(494) ⋅ 点赞:(0)

今天继续进行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地址,有需要的兄弟自取。

项目gitee地址

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到