第七节:三大封装组件的联调 和 用户/角色/菜单模块核心功能的快速搭建

一. 三大组件联调

1. 说明

 以用户管理为例,调用page-search搜索框组件、page-content内容区域组件、page-modal弹框组件,完成用户管理模块的:表格分页展示、条件搜索、新增用户、编辑用户、删除用户 等通用功能。

2. 实操

(1). 在<template>中依次引入 page-search、page-content、page-modal组件,并传入相应的配置,监听相关方法。

  A. page-search:传入配置文件searchFormConfig,监听重置方法@resetBtnClick和搜索方法@queryBtnClick

  B. page-content:  传入配置文件contentTableConfig 和 pageName标记users,通过ref绑定该组件,监听新增方法@newBtnClick 和 编辑方法@editBtnClick

  C. page-modal: 传入pageName标记users,通过ref绑定该组件,传入表单配置myModalConfig、弹框配置dialogConfig、默认值defaultInfo。

(2). page-search组件

   监听的重置方法和搜索方法中,通过绑定到page-content组件上的对象pageContentRef,调用pageContent中的getPageData方法即可,其中查询监听中,默认接收搜索框的表单内容。

(3). page-content组件

 监听的新增方法中,默认值defaultInfo需要置空,通过绑定在page-modal组件上的pageModalRef对象控制弹框打开,同时修改modalconfig配置文件,让密码框显示。

 监听的吸怪方法中,默认值defaultInfo需要传递该行的内容,通过绑定在page-modal组件上的pageModalRef对象控制弹框打开,同时修改modalconfig配置文件,让密码框隐藏。

(4). page-modal组件

    动态修改配置文件中的options值,从vuex中获取,用于给下拉框赋值。

代码分享:

user.vue

<template>
    <page-search
        :searchFormConfig="searchFormConfig"
        @resetBtnClick="handleResetClick"
        @queryBtnClick="handleQueryClick"
    ></page-search>
    <page-content
        pageName="users"
        ref="pageContentRef"
        :contentTableConfig="contentTableConfig"
        @newBtnClick="handleNewData"
        @editBtnClick="handleEditData"
    >
        <template #name="myScope">
            <strong>{{ myScope.row2.name.substring(0, 10) }}</strong>
        </template>
    </page-content>
    <page-modal
        pageName="users"
        ref="pageModalRef"
        :modalConfig="myModalConfig"
        :dialogConfig="dialogConfig"
        :defaultInfo="defaultInfo"
    ></page-modal>
</template>

<script lang="ts">
    import { defineComponent, computed, ref } from 'vue';
    import PageSearch from '@/components/page-search';
    import PageContent from '@/components/page-content';
    import { searchFormConfig } from './config/search.config';
    import { contentTableConfig } from './config/content.config';
    import { usePageSearch } from '@/hooks/use-page-search';
    import { usePageModa } from '@/hooks/use-page-modal';
    import PageModal from '@/components/page-modal/src/page-modal.vue';
    import { modalConfig, dialogConfig } from './config/modal.config';
    import { useStore } from '@/store';

    export default defineComponent({
        name: 'users',
        components: {
            PageSearch,
            PageContent,
            PageModal,
        },
        setup() {
            //1.pageContent组件对象
            const pageContentRef = ref<InstanceType<typeof PageContent>>();
            //2.重置方法
            const handleResetClick = () => {
                pageContentRef.value?.getPageData();
            };
            //3.查询方法
            // @queryInfo:搜索框中的内容
            const handleQueryClick = (queryInfo: any) => {
                pageContentRef.value?.getPageData(queryInfo);
            };

            // 等价于上述1,2,3 (ts中包警告,问题不大)
            // const [pageContentRef, handleResetClick, handleQueryClick] = usePageSearch();

            //4.pageModal组件对象
            const pageModalRef = ref<InstanceType<typeof PageModal>>();
            //5. 传递给pageModal的数据对象
            const defaultInfo = ref({});
            //6.新增方法
            const handleNewData = () => {
                defaultInfo.value = {};
                if (pageModalRef.value) {
                    pageModalRef.value.dialogVisible = true;
                }
                // 设置密码输入框显示
                const passwordItem = modalConfig.formItems.find((item) => item.field === 'password');
                passwordItem.isHidden = false;
            };
            // 7.修改方法
            // @item: 所在行的对象
            const handleEditData = (item: any) => {
                dialogConfig.title = '修改用户';
                defaultInfo.value = { ...item }; //把item浅拷贝给defalutInfo
                if (pageModalRef.value) {
                    pageModalRef.value.dialogVisible = true;
                }
                // 设置密码输入框隐藏
                const passwordItem = modalConfig.formItems.find((item) => item.field === 'password');
                passwordItem.isHidden = true;
            };

            // 等价于上述4,5,6,7 (ts中包警告,问题不大)
            // pageModal组件对象、pageModal的数据对象、新增方法、修改方法
            // const newCallback = () => {
            //     const passwordItem = modalConfig.formItems.find((item) => item.field === 'password');
            //     passwordItem.isHidden = false;
            // };
            // const editCallback = () => {
            //     const passwordItem = modalConfig.formItems.find((item) => item.field === 'password');
            //     passwordItem.isHidden = true;
            // };
            // const [pageModalRef, defaultInfo, handleNewData, handleEditData] = usePageModa(
            //     '修改用户',
            //     newCallback,
            //     editCallback,
            // );

            // 8. 动态给modalconfig的中的部门和角色赋值
            // 默认直接绑定配置文件中的modalConfig就行了,但是这里需要给部门角色赋值,最后绑定的是处理后的modalConfig
            const store = useStore();
            const myModalConfig = computed(() => {
                const departmentItem = modalConfig.formItems.find((item) => item.field === 'departmentId');
                departmentItem.options = store.state.entireDepartment.map((item) => {
                    return { title: item.name, value: item.id };
                });
                const roleItem = modalConfig.formItems.find((item) => item.field === 'roleId');
                roleItem.options = store.state.entireRole.map((item) => {
                    return { title: item.name, value: item.id };
                });
                return modalConfig;
            });

            return {
                contentTableConfig,
                searchFormConfig,
                handleResetClick,
                handleQueryClick,
                pageContentRef,
                handleNewData,
                handleEditData,
                pageModalRef,
                modalConfig,
                myModalConfig,
                dialogConfig,
                defaultInfo,
            };
        },
    });
</script>

<style scoped>
    .searchForm {
        padding: 5px;
        margin-bottom: 4px;
        height: 80px;
    }
    .main {
        border-top: 10px solid #f5f5f5;
    }
</style>
View Code

搜索配置search.config

import { IForm } from '@/base-ui/form';

export const searchFormConfig: IForm = {
    labelWidth: '120px',
    itemLayout: {
        padding: '5px 5px',
    },
    colLayout: {
        span: 8,
    },
    formItems: [
        {
            field: 'id',
            type: 'input',
            label: 'id',
            placeholder: '请输入id',
            otherOptions: {
                size: 'small',
                maxlength: '200',
            },
        },
        {
            field: 'name',
            type: 'input',
            label: '用户名',
            placeholder: '请输入用户名',
        },
        {
            field: 'realname',
            type: 'input',
            label: '真实姓名',
            placeholder: '请输入真实姓名',
        },
        {
            field: 'cellphone',
            type: 'input',
            label: '电话号码',
            placeholder: '请输入电话号码',
        },
        {
            field: 'enable',
            type: 'select',
            label: '用户状态',
            placeholder: '请选择用户状态',
            options: [
                { title: '启用', value: 1 },
                { title: '禁用', value: 0 },
            ],
        },
        {
            field: 'createAt',
            type: 'datepicker',
            label: '创建时间',
            otherOptions: {
                startPlaceholder: '开始时间',
                endPlaceholder: '结束时间',
                type: 'daterange',
            },
        },
    ],
};
View Code

内容配置content.config

export const contentTableConfig = {
    title: '用户列表',
    propList: [
        { prop: 'name', label: '用户名', minWidth: '100', slotName: 'name' },
        { prop: 'realname', label: '真实姓名', minWidth: '100' },
        { prop: 'cellphone', label: '手机号码', minWidth: '100' },
        { prop: 'enable', label: '状态', minWidth: '100', slotName: 'status' },
        {
            prop: 'createAt',
            label: '创建时间',
            minWidth: '250',
            slotName: 'createAt', //这里的slotName是自己起名的,用来动态定义封装的插槽名称
        },
        { prop: 'updateAt', label: '更新时间', minWidth: '250', slotName: 'updateAt' },
        { label: '操作', minWidth: '120', slotName: 'handler' },
    ],
    // 开启多选列
    showSelectColumn: true,
    // 开启索引列
    showIndexColumn: true,
    // 是否显示底部分页
    showFooter: true,
};
View Code

弹框配置modal.config

import { IForm } from '@/base-ui/form';

// 1.弹框中form表单的属性
export const modalConfig: IForm = {
    formItems: [
        {
            field: 'name',
            type: 'input',
            label: '用户名',
            placeholder: '请输入用户名',
            otherOptions: {
                size: 'small',
                clearable: true,
            },
        },
        {
            field: 'realname',
            type: 'input',
            label: '真实姓名',
            placeholder: '请输入真实姓名',
            otherOptions: {
                size: 'small',
                clearable: true,
            },
        },
        {
            field: 'password',
            type: 'password',
            label: '用户密码',
            placeholder: '请输入密码',
            otherOptions: {
                size: 'small',
                clearable: true,
            },
            // 控制显示or隐藏
            isHidden: false,
        },
        {
            field: 'cellphone',
            type: 'input',
            label: '电话号码',
            placeholder: '请输入电话号码',
            otherOptions: {
                size: 'small',
                clearable: true,
            },
        },
        {
            field: 'departmentId',
            type: 'select',
            label: '选择部门',
            placeholder: '请选择部门',
            options: [],
            otherOptions: {
                size: 'small',
            },
        },
        {
            field: 'roleId',
            type: 'select',
            label: '选择角色',
            placeholder: '请选择角色',
            options: [],
            otherOptions: {
                size: 'small',
            },
        },
    ],
    colLayout: { span: 24 },
    itemLayout: {
        padding: '5px 5px',
    },
};
//2. 弹框自身的属性
export const dialogConfig = {
    title: '新增用户',
    width: '500px',
    center: false,
};
View Code

3. 扩展

 上述user.vue中,部分代码可以抽离出来hooks,即上述注释那部分,分享代码如下: 

use-page-seach.ts

import { ref } from 'vue';
import PageContent from '@/components/page-content';

export function usePageSearch() {
    const pageContentRef = ref<InstanceType<typeof PageContent>>();
    const handleResetClick = () => {
        pageContentRef.value?.getPageData();
    };
    const handleQueryClick = (queryInfo: any) => {
        pageContentRef.value?.getPageData(queryInfo);
    };
    return [pageContentRef, handleResetClick, handleQueryClick];
}

use-page-modal.ts 

import PageModal from '@/components/page-modal';
import { dialogConfig } from '@/views/main/system/user/config/modal.config';
import { ref } from 'vue';

/*
@title: 弹框的标题
@newCb: 新增弹框的回掉
@editCb:编辑弹框的回掉
*/
type callBackFn = (item?: any) => void;
export function usePageModa(title: string, newCb?: callBackFn, editCb?: callBackFn) {
    //4.pageModal组件对象
    const pageModalRef = ref<InstanceType<typeof PageModal>>();
    //5. 传递给pageModal的数据对象
    const defaultInfo = ref({});
    //6.新增方法
    const handleNewData = () => {
        defaultInfo.value = {};
        if (pageModalRef.value) {
            pageModalRef.value.dialogVisible = true;
        }
        // 新语法,当有值的时候调用这个方法
        newCb && newCb();
    };
    // 7.修改方法
    // @item: 所在行的对象
    const handleEditData = (item: any) => {
        dialogConfig.title = title;
        defaultInfo.value = { ...item }; //把item浅拷贝给defalutInfo
        if (pageModalRef.value) {
            pageModalRef.value.dialogVisible = true;
        }
        // 新语法,当有值的时候调用这个方法
        editCb && editCb();
    };
    return [pageModalRef, defaultInfo, handleNewData, handleEditData];
}
View Code

 

 

二. 核心模块的搭建

1. 新增/编辑角色弹框中的树菜单

(1). 前面的封装的page-modal组件里有个默认插槽,所以在使用该组件的时候,在其默认插槽中调用el-tree组件即可。

(2). el-tree组件的几个配置:

 A. node-key:每个树节点用来作为唯一标识的属性,整棵树应该是唯一的。(绑定到数据源中,必须有这个属性,且数据是唯一的

 B. show-checkbox:设置节点可以被选中

 C. props:"{ children: 'children', label: 'name' }",表示子树对应的属性为children,没有children或者为空,则没有子节点;label表示树的内容对应数据源中的name属性

 D. data:数据源,按照如上配置,仅需要 id、name、children 三个属性即可。

 E. @check:设置目前勾选的节点,使用此方法必须设置 node-key 属性。 有两个参数,①返回选中的节点 ②返回半选中的节点

(3). 如何获取选中的节点?

 在监听事件中,获得选中节点和半选中节点,合并一下,赋值给相应对象即可。 

 

(4). 编辑弹框如何默认显示选中的节点?

 根据获取到的具有权限的菜单数据,获取所有的子节点,然后通过 setCheckedKeys赋值,特别注意第二参数要设置为false,否则只有当前节点被选中哦,另外要放到nextTick中哦

 

数据源:

[{
    "id": 38,
    "name": "系统总览",
    "children": [{
        "id": 39,
        "name": "核心技术",
        "children": null,

    }, {
        "id": 40,
        "name": "商品统计",
        "children": null,
    }]
}, {
    "id": 1,
    "name": "系统管理",
    "children": [{
        "id": 2,
        "name": "用户管理",
        "children": [{
            "id": 5,
            "name": "创建用户",
        }, {
            "id": 6,
            "name": "删除用户",
        }, {
            "id": 7,
            "name": "修改用户",
        }, {
            "id": 8,
            "name": "查询用户",
        }],
    }, {
        "id": 3,
        "name": "部门管理",
        "children": [{
            "id": 17,
            "name": "创建部门",
        }, {
            "id": 18,
            "name": "删除部门",
        }, {
            "id": 19,
            "name": "修改部门",
        }, {
            "id": 20,
            "name": "查询部门",
        }],
    }, {
        "id": 4,
        "name": "菜单管理",
        "children": [{
            "id": 21,
            "name": "创建菜单",
        }, {
            "id": 22,
            "name": "删除菜单",
        }, {
            "id": 23,
            "name": "修改菜单",
        }, {
            "id": 24,
            "name": "查询菜单",
        }],
        "parentId": 1
    }, {
        "id": 25,
        "name": "角色管理",
        "children": [{
            "id": 26,
            "name": "创建角色",
        }, {
            "id": 27,
            "name": "删除角色",
        }, {
            "id": 28,
            "name": "修改角色",
        }, {
            "id": 29,
            "name": "查询角色",
        }],
    }]
}]
View Code

代码分享:

<template>
    <page-search
        :searchFormConfig="searchFormConfig"
        @resetBtnClick="handleResetClick"
        @queryBtnClick="handleQueryClick"
    ></page-search>
    <page-content
        :contentTableConfig="contentTableConfig"
        pageName="role"
        ref="pageContentRef"
        @newBtnClick="handleNewData"
        @editBtnClick="handleEditData"
    ></page-content>
    <page-modal
        pageName="role"
        ref="pageModalRef"
        :modalConfig="modalConfig"
        :dialogConfig="dialogConfig"
        :defaultInfo="defaultInfo"
        :otherInfo="otherInfo"
    >
        <div class="menu-tree">
            <el-tree
                node-key="id"
                show-checkbox
                ref="elTreeRef"
                :data="menus"
                :props="{ children: 'children', label: 'name' }"
                @check="handleCheckChange"
            ></el-tree>
        </div>
    </page-modal>
</template>

<script lang="ts">
    import { computed, defineComponent, nextTick, ref } from 'vue';
    import PageSearch from '@/components/page-search/src/page-search.vue';
    import PageContent from '@/components/page-content/src/page-content.vue';

    import { searchFormConfig } from './config/search.config';
    import { contentTableConfig } from './config/content.config';
    import { modalConfig, dialogConfig } from './config/modal.config';
    import PageModal from '@/components/page-modal/src/page-modal.vue';
    import { useStore } from '@/store';
    import { menuMapLeafKeys } from '@/utils/map-menus';
    import { ElTree } from 'element-plus';
    import { usePageSearch } from '@/hooks/use-page-search';
    import { usePageModa } from '@/hooks/use-page-modal';

    export default defineComponent({
        name: 'myRole',
        components: {
            PageSearch,
            PageContent,
            PageModal,
        },
        setup() {
            //1.pageContent组件对象
            const pageContentRef = ref<InstanceType<typeof PageContent>>();
            //2.重置方法
            const handleResetClick = () => {
                pageContentRef.value?.getPageData();
            };
            //3.查询方法
            // @queryInfo:搜索框中的内容
            const handleQueryClick = (queryInfo: any) => {
                pageContentRef.value?.getPageData(queryInfo);
            };

            // 等价于上述1,2,3 (ts中包警告,问题不大)
            // const [pageContentRef, handleResetClick, handleQueryClick] = usePageSearch();

            //4.pageModal组件对象
            const pageModalRef = ref<InstanceType<typeof PageModal>>();
            //5. 传递给pageModal的数据对象
            const defaultInfo = ref({});
            //6.新增方法
            const handleNewData = () => {
                defaultInfo.value = {};
                if (pageModalRef.value) {
                    pageModalRef.value.dialogVisible = true;
                }
            };
            // 7.修改方法
            // @item: 所在行的对象
            const handleEditData = (item: any) => {
                dialogConfig.title = '修改角色';
                defaultInfo.value = { ...item }; //把item浅拷贝给defalutInfo
                if (pageModalRef.value) {
                    pageModalRef.value.dialogVisible = true;
                }
                editCallback(item);
            };

            // 等价于上述4,5,6,7 (ts中包警告,问题不大)
            // pageModal组件对象、pageModal的数据对象、新增方法、修改方法

            /*             const [pageModalRef, defaultInfo, handleNewData, handleEditData] = usePageModa(
                '修改用户',
                undefined,
                editCallback,
            );
     */
            //8 树形菜单相关
            const store = useStore();
            const menus = computed(() => store.state.entireMenu);
            console.log(store.state.entireMenu);
            const otherInfo = ref({});
            // 监听选中后的回掉
            const handleCheckChange = (data1: any, data2: any) => {
                // 选中的节点
                const checkedKeys = data2.checkedKeys;
                // 半选中节点
                const halfCheckedKeys = data2.halfCheckedKeys;
                // 合并
                const menuList = [...checkedKeys, ...halfCheckedKeys];
                otherInfo.value = { menuList };
            };
            // 封装回显事件
            const elTreeRef = ref<InstanceType<typeof ElTree>>();
            const editCallback = (item: any) => {
                const leafKeys = menuMapLeafKeys(item.menuList);
                // 此处仔细理解一下,为什么不写这个,来不及绑定,所以要用nextTick
                nextTick(() => {
                    console.log(elTreeRef.value);
                    elTreeRef.value?.setCheckedKeys(leafKeys, false);
                });
            };

            return {
                contentTableConfig,
                searchFormConfig,
                pageContentRef,
                handleResetClick,
                handleQueryClick,
                modalConfig,
                dialogConfig,
                defaultInfo,
                handleNewData,
                handleEditData,
                pageModalRef,
                handleCheckChange,
                menus,
                otherInfo,
                elTreeRef,
            };
        },
    });
</script>

<style scoped>
    .searchForm {
        padding: 5px;
        margin-bottom: 4px;
        height: 80px;
    }
    .main {
        border-top: 10px solid #f5f5f5;
    }
</style>
View Code

效果图:

2. 菜单管理表格-树形数据

(1). 在table组件的基础上,首先要指定rowKey字段,用作标记,一般是指定id。

(2).  配置treeprops属性,:tree-props="{ children: 'children }",表示数据源中有children字段有数据的时候,则有子菜单。

数据源:

  同上1 角色弹框中的数据源

代码分享

菜单页面:

<template>
    <div class="menu">
        <page-content pageName="menu" :contentTableConfig="contentTableConfig"></page-content>
    </div>
</template>

<script lang="ts">
    import { defineComponent } from 'vue';
    import PageContent from '@/components/page-content/src/page-content.vue';
    import { contentTableConfig } from './config/content.config';

    export default defineComponent({
        components: { PageContent },
        name: 'menus',
        setup() {
            return {
                contentTableConfig,
            };
        },
    });
</script>

<style scoped></style>
View Code

配置代码:

export const contentTableConfig = {
    title: '菜单列表',
    propList: [
        { prop: 'name', label: '菜单名称', minWidth: '100' },
        { prop: 'type', label: '类型', minWidth: '60' },
        { prop: 'url', label: '菜单url', minWidth: '100' },
        { prop: 'icon', label: '菜单icon', minWidth: '100' },
        { prop: 'permission', label: '按钮权限', minWidth: '100' },
        {
            prop: 'createAt',
            label: '创建时间',
            minWidth: '220',
            slotName: 'createAt',
        },
        {
            prop: 'updateAt',
            label: '更新时间',
            minWidth: '220',
            slotName: 'updateAt',
        },
        { label: '操作', minWidth: '120', slotName: 'handler' },
    ],
    showIndexColumn: false,
    showSelectColumn: false,
    showFooter: false,
    childrenProps: {
        rowKey: 'id',
        treeProp: {
            children: 'children',
        },
    },
};
View Code 

效果图:

3. 商品管理图片点击放大

  使用el-image组件,设置src属性和preview-src-list属性即可

            <el-image
                    style="width: 60px; height: 60px"
                    :src="myScope.row2.imgUrl"
                    :preview-src-list="[myScope.row2.imgUrl]"
                >
            </el-image>

效果图: 

  

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2021-12-19 19:43  Yaopengfei  阅读(244)  评论(4编辑  收藏  举报