根据上面的图片可以看到,左侧的十三个页面大体上结构都是相同的,除了右侧的不同的按钮以及不同的按钮个数,这种情况下,就可以考虑使用动态路由传参以及slot具名插槽等技术去处理简化达到最优解。
1、使用到的技术方向
- 动态路由传参
- slot具名插槽
- watch侦听器
- 组件的封装
..等
2、开始对路由进行处理
体系建设是我这个项目的一个大的模块,下面13个页面结构类似,因此,直接写一个主入口targetManagement
组件,这个模块所有的页面都跳转到这个页面,通过不同的页面的id
实现获取不同的接口数据,由于我这个项目还有个打开文件夹的功能,因此,需要使用/targetManagement/:id/:fid
将id
以及fid
拼接到后面作为参数传入路由。
// 体系建设
{
path: '/targetManagement/:id',
name: 'targetManagement',
component: () => import('@/views/pages/systemConstruction/targetManagement.vue'),
meta: {
title: [{
name: '体系建设',
url: ''
}]
}
},
// 文件夹查看
{
path: '/targetManagement/:id/:fid',
name: 'targetManagement',
component: () => import('@/views/pages/systemConstruction/components/foldersView.vue'),
meta: {
title: [{
name: '体系建设',
url: ''
}, {
name: '目标管理',
url: ''
}]
}
},
3、对页面进行唯一性处理
router-view
是指的页面内容占位区域,也就是显示的部分,这里将路由路径进行唯一的key
值绑定
<template>
<div class="box">
<navTop />
<div class="content">
<transition name="myfade" mode="out-in">
<router-view :key="key" />
</transition>
</div>
</div>
</template>
<script>
import navTop from "./navTop";
export default {
name: "viewBox",
components: {
navTop,
},
computed: {
key() {
return this.$route.fullPath;
},
},
};
</script>
4、左侧导航栏菜单根据后台接口返回动态渲染
这部分获取接口返回过来的左侧菜单栏进行渲染操作
<!-- 左侧菜单 -->
<div class="menu-box">
<div class="menu-wrap" :style="styleProps">
<div class="logo-img">
<!-- <img src="../assets/logo.png" alt /> -->
<span>******平台</span>
</div>
<Menu
:style="styleProps"
:active-name="$route.menuUrl"
:open-names="['home']"
:theme="menuTheme"
width="auto"
:class="menuitemClasses"
accordion
>
<template v-for="(item, componentIndex) in menu">
<!-- 展开并且有子菜单 -->
<Submenu
v-if="!isCollapsed && item.children && item.children.length"
v-bind:key="componentIndex"
:name="componentIndex"
>
<template slot="title">
<Icon :type="item.menuIcon" />
<span>{{ item.menuName }}</span>
</template>
<MenuItem
v-for="children in item.children"
:key="children.menuUrl"
:name="children.menuUrl"
@click="handleRouter(children)"
:to="children.menuUrl"
>
{{ children.menuName }}
</MenuItem>
</Submenu>
<!-- 展开但没有子菜单 -->
<MenuItem
v-else-if="!isCollapsed"
:name="item.menuUrl"
:to="item.menuUrl"
v-bind:key="componentIndex"
>
<Icon :type="item.menuIcon" />
<span>{{ item.menuName }}</span>
</MenuItem>
<!-- 不展开有子菜单 -->
<Dropdown
v-else-if="
isCollapsed && item.children && item.children.length
"
v-bind:key="componentIndex"
placement="right-start"
class="menu-dropdown"
>
<MenuItem :name="item.menuUrl" :to="item.menuUrl">
<Icon :type="item.menuIcon" />
<span>{{ item.menuName }}</span>
</MenuItem>
<DropdownMenu slot="list">
<DropdownItem
v-for="(children, index) in item.children"
:key="index"
style="padding: 0 0; background-color: #515a6e"
>
<MenuItem :name="children.menuUrl" :to="children.menuUrl">
{{ children.menuName }}
</MenuItem>
</DropdownItem>
</DropdownMenu>
</Dropdown>
<!-- 不展开无子菜单 -->
<Tooltip
v-else-if="isCollapsed"
:content="item.name"
placement="right"
v-bind:key="componentIndex"
>
<MenuItem :name="item.menuUrl" :to="item.menuUrl">
<Icon :type="item.menuIcon" />
<span>{{ item.menuName }}</span>
</MenuItem>
</Tooltip>
</template>
</Menu>
</div>
</div>
5、模块处理过程
5.1、公共页面组件的搭建
创建一个BasicSystem
组件为一个子组件,也就是去负责一些公共的部分,而右侧的不同内容的按钮需要根据item.btnId == key.split('/')[2]
判断对应的内容去显示出来,uniId
也就是监听到的拼接在路径末尾不同的id
值。
<transition-group name="fade" mode="out-in">
<BasicSystem
:key="uniId"
:ps="ps"
:pn="pn"
:total="total"
@handleSearch="handleSearch"
@changePage="changePage"
@handleSuccess="handleSuccess"
@handleUpload="handleUpload"
:dataList="dataList"
>
<template v-for="item in btns">
<Button
v-if="item.btnId == key.split('/')[2]"
:key="item.id"
type="primary"
slot="fn-btn"
@click="handleAll(item.handleName, item.row)"
>{{ item.text }}
</Button>
</template>
</BasicSystem>
</transition-group>
5.2、主入口监听路由以及存储
监听路由获取uniId
存储在会话中有一个好处,就是在这个页面下去频繁使用到uniId处理逻辑的时候可以去直接获取而不用频繁的使用params或者query去传参。
存储btnsLength
是因为按钮的个数是不确定的,因此页面的 header布局也是动态的,无法固定死,需要去计算。
computed: {
key() {
this.handleSession();
this.handleTypeList();
return this.$route.fullPath;
},
},
watch: {
$route: {
handler(to, from) {
this.btnsLength = 0;
this.uniId = to.params.id;
btns.forEach((item) => {
if (item.btnId == this.uniId) {
this.btnsLength++;
}
});
sessionStorage.setItem("uniId", this.uniId);
sessionStorage.setItem("btnsLength", this.btnsLength);
},
immediate: true,
},
},
5.3、子组件的头部动态布局处理
子组件中因为头部的按钮可以是一个也可以是三个,因此需要btnsLength=sessionStorage.getItem("btnsLength")?sessionStorage.getItem("btnsLength"): 0
去处理头部的占位问题。
<div class="basic-header">
<Row class="row">
<Col span="12">
<Input
v-model="keyWord"
clearable
placeholder="请输入文件名称查询"
style="width: 300px"
></Input>
<Button class="basic-btn" type="primary" @click="handleSearch"
>查询</Button
>
<Button
class="basic-btn secondary"
type="success"
@click="handleDown"
>批量下载</Button
>
</Col>
<Col :span="12 - 2 * btnsLength" class="basic-btn-upload right-btn">
<Button type="success" @click="handleUpload">文档上传</Button>
</Col>
<Col :span="2 * btnsLength" class="basic-btn-operator">
<slot name="fn-btn"></slot>
</Col>
</Row>
</div>
5.4、右侧按钮的统一化处理
在处理右侧按钮时,也就是在targetManagement组件的过程中,使用:
<template v-for="item in btns">
<Button
v-if="item.btnId == key.split('/')[2]"
:key="item.id"
type="primary"
slot="fn-btn"
@click="handleAll(item.handleName, item.row)"
>{{ item.text }}
</Button>
</template>
将所有的按钮放在一个js文件中,然后用上述的方法去遍历,获取的到方法的第一个参数就是按钮的事件名,第二个参数就是按钮的一些id等数据用于去判断。
5.5、右侧按钮的统一化处理(二)
将事件写出去,但是当点击主入口上的不同的按钮时,依旧是可以执行方法的。
// 右侧的操作按钮
handleAll(handleName, row) {
switch (handleName) {
// 目标管理签署列表
case "handleSignListManage":
this.handleSignListManage();
break;
// 组织结构文件签署
case "handleSignListOrigin":
this.handleSignListOrigin();
break;
// 安全生产费用
case "handleSafetyCost":
this.handleSafetyCost();
break;
// 年产总值
case "handleTotalProduction":
this.handleTotalProduction();
break;
// 培训列表
case "handleTrainList":
this.handleTrainList();
break;
// 演练列表
case "handleDrillList":
this.handleDrillList();
break;
// 设备清单
case "handleEquipment":
this.handleEquipment();
break;
default:
break;
}
},
这样,大概总共几十甚至近百的页面,简单的优化处理,至少省去了几十个页面的重复绘制以及写一些重复的逻辑过程,在后期项目维护中,也是大大节省了成本,只需修改一个主入口即可。