第七节:三大封装组件的联调 和 用户/角色/菜单模块核心功能的快速搭建
一. 三大组件联调
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>
搜索配置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', }, }, ], };
内容配置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, };
弹框配置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, };
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]; }
二. 核心模块的搭建
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": "查询角色", }], }] }]
代码分享:
<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>
效果图:
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>
配置代码:
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', }, }, };
效果图:
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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。