做后台管理项目期间,遇到一些略微复杂的场景,原则就是尽可能的把逻辑放在模板之外,根据特点,做成可配置的方式。

下面选取了两个场景,分别来说明配置的使用,解决方式都差不多。

场景1:一个状态对应多个操作

项目中会根据某一状态,展示几个可以操作的按钮,例如状态 A,对应是编辑、删除;状态 B,对应的是编辑、预览、撤回;状态 C,对应的是预览、撤回、记录。状态对应的操作,不是一成不变的,可能根据不同的权限,增加或者去除某个操作。

根据状态的不同,把逻辑判断写在模板中,会增加模板的复杂度。

以下是伪代码:

  1. <template>
  2. <el-button v-if="item.status === 'A' || item.status === 'B'">编辑</el-button>
  3. <el-button v-if="item.status === 'A'">删除</el-button>
  4. <el-button v-if="item.status === 'A' || item.status === 'C'">预览</el-button>
  5. <el-button v-if="item.status === 'B' || item.status === 'C'">撤回</el-button>
  6. <el-button v-if="item.status === 'A' || item.status === 'B' || item.status === 'C'">记录</el-button>
  7. </template>

或者根据状态聚合操作,伪代码:

  1. <template>
  2. <div v-if="item.status === 'A'">
  3. <el-button>编辑</el-button>
  4. <el-button>删除</el-button>
  5. </div>
  6. <div v-else-if="item.status === 'B'">
  7. <el-button>编辑</el-button>
  8. <el-button>删除</el-button>
  9. <el-button>预览</el-button>
  10. </div>
  11. <div v-else-if="item.status === 'C'">
  12. <el-button>撤回</el-button>
  13. <el-button>预览</el-button>
  14. <el-button>记录</el-button>
  15. </div>
  16. </template>

第一种做法让操作来适应状态,在操作上做多个状态判断,维护起来困难。
第二种做法是根据状态找对应的操作,这种方式比较直观。写在模板中,增加去除不太灵活。

第二种做法可以放在js中做,先将状态对应的操作,抽离成一个映射表。状态映射到对应的操作列表,后期维护直接维护映射表即可,不需要动模板逻辑,这样数据逻辑和模板完全分离开。

类似这样定义,伪代码:

  1. export const operationMapAll = {
  2. 0: ['bianjiAndTishen', 'shanchu', 'jilu', 'yulan'],
  3. 1: ['chehui', 'jilu', 'yulan'],
  4. 2: ['bianjiAndTishen', 'shanchu', 'jilu'],
  5. 4: ['jilu'],
  6. 5: ['bianjiAndTishen', 'jilu'],
  7. 6: ['jilu'],
  8. 7: ['jilu', 'yulan'],
  9. 8: ['jilu'],
  10. 31: ['chehui', 'jilu', 'yulan'],
  11. 32: ['shuju', 'jilu'],
  12. 33: ['shuju', 'jilu'],
  13. }

key 为状态码,值为操作集合(这里用了拼音,哈哈,直观)。

某个权限下不展示给用户某些操作,可以先定义这个权限需要过滤的操作的配置:

  1. // 没有操作权限配置
  2. const guildActivityOperateMap = {
  3. bianjiAndTishen: 1,
  4. shanchu: 1,
  5. chehui: 1,
  6. }

写一个方法,过滤掉这里定义的规则:

  1. // 过滤方法
  2. const filterByguildActivityOperate = () => {
  3. return Object.keys(operationMapAll).reduce((newMap, key) => {
  4. newMap[key] = operationMapAll[key].filter(txt => {
  5. return !guildActivityOperateMap[txt]
  6. })
  7. return newMap
  8. }, {})
  9. }

然后根据权限,拿到最终的操作映射表:

  1. // 有权限,则走全部配置,否则过滤一遍
  2. export const operationMap = guild_activity_operate ? operationMapAll : filterByguildActivityOperate()

如果有多个权限,只要增加一个过滤的配置和一个方法,根据权限执行该方法;如果需要增加某个操作,也是同样的道理。

定义每个操作要做的事情,伪代码:

  1. export const operation = {
  2. bianjiAndTishen:{
  3. value: '编辑/提审'
  4. handler(comInstall, params){
  5. // TODO something
  6. }
  7. },
  8. shanchu:{
  9. value: '删除'
  10. handler(comInstall, params){
  11. // TODO something
  12. }
  13. },
  14. jilu:{
  15. value: '记录'
  16. handler(comInstall, params){
  17. // TODO something
  18. }
  19. }
  20. ... // 其他忽略
  21. }

当后端返回的状态值,匹配到了定义的映射表,便会找到对应的操作列表,循环展示到模板中,模板的按钮统一都是点击触发,为了达到统一的效果,所有的方法都标配一个 handler 方法,点击时触发改方法。value 是显示在页面中的文案。如果按钮还有其他的事件处理,可以再指定一个方法,供该事件调用。

方法接收两个参数,第一个是组件实例,能够在时间触发后做到和组件 methods 中一样的事,需要用到实例的情况,例如触发 this.$message() 提示。当然你也可以通过 call 来改变 this 为组件实例;

第二个参数是处理的数据,可以拿到数据后发送请求之类。

模板渲染:

  1. <el-button
  2. v-for="item in scope.row.operation"
  3. @click="operationHandler(item, scope.row)"
  4. >
  5. {{ item.value }}
  6. </el-button>

methods 方法:

  1. operationHandler(item, row) {
  2. item?.handler(this, row)
  3. // 或者 item?.handler.call(this, row)
  4. },

场景2:多按钮对多视图

场景描述:三个按钮,每切换到一个按钮上会展示不同的 table,同时不同的按钮还会对应不同的筛选控件,以及不同的请求方法和参数。

对视图 table 的权衡,table 有重复的地方,也有不一样的地方。重复的地方改动频率不高,所以分成了三个组件文件。如果对重复功能改动频繁,则最好是重复的抽离成一个组件,方便改一次全部都更改。

只有三个视图,如果视图有多个或者日后要增加,为了日后维护扩展性好,不要分成太多的组件文件,而是改成用配置的思路,只有一个视图。抽离公共的配置要比视图更简洁些。

对按钮和对应视图进行配置,这样也方便通过权限进行过滤,伪代码:

  1. export const tableMap = {
  2. allTable: {
  3. com: () => import('./allTable.vue'),
  4. downUrl: '/exportAllTaskList',
  5. new_perm: window.NEW_PERM.better_all_query,
  6. title: '全部',
  7. class: '',
  8. data: {
  9. starid: '',
  10. level: '0',
  11. page: 1,
  12. intern_status: '0',
  13. is_signed: '0',
  14. },
  15. fetch(params) { // 请求
  16. return allTaskList(params)
  17. },
  18. },
  19. probationTable: {
  20. com: () => import('./probationTable.vue'),
  21. downUrl: '/exportPreTaskList',
  22. new_perm: window.NEW_PERM.m_betterstar_pre_task,
  23. title: '试用期任务',
  24. class: 'borderLeft borderRight',
  25. data: {
  26. starid: '',
  27. level: '0',
  28. page: 1,
  29. status: '0',
  30. },
  31. fetch(params) { // 请求
  32. return preTaskList(params)
  33. },
  34. },
  35. signTable: {
  36. com: () => import('./signTable.vue'),
  37. downUrl: '/exportSignTaskList',
  38. new_perm: window.NEW_PERM.m_betterstar_sign_task,
  39. title: '签约考核任务',
  40. class: '',
  41. data: {
  42. month: '',
  43. starid: '',
  44. level: '0',
  45. page: 1,
  46. is_signed: '0',
  47. },
  48. fetch(params) { // 请求
  49. return signTaskList(params)
  50. },
  51. },
  52. }

配置中的 com 字段是渲染的 table 视图,通过 Vue 中的 <\/component> 进行渲染。data 字段是表单控件所用的数据的初始值,每个按钮对应的数据是不一样的。这里没有抽离表单控件,控件数量不太多,在模板中判断就可以了,改动不太大抽离显得有点过渡了。 fetch 是每个按钮对应请求的方法。new_perm 是权限控制,如果该按钮没有这一项权限,则不再展示,可以通过这个字段进行过滤。

monted 中做过滤:

  1. const keys = Object.keys(tableMap).filter(key => tableMap[key]?.new_perm)
  2. this.tableMap = keys.reduce((obj, key) => {
  3. obj[key] = tableMap[key]
  4. return obj
  5. }, {})
  6. this.tableType = keys[0]
  7. this.info = this.tableMap[this.tableType]
  8. // 表单控件使用的字段,复制一份,防止防止保留上一次操作的数据
  9. this.formData = { ...this.info?.data } || {}

按钮渲染后,只需要通过 tableType 找到过滤后 tableMap 的值即可。

总结

当出现以上场景时,建议走配置的方式,将逻辑放在 js 中做,减少在模板中做判断的逻辑。逻辑再js中后,再考虑如何进行抽象,达到可扩展的程度。

扩展阅读:
https://segmentfault.com/a/1190000015643488
https://blog.csdn.net/qq_37210523/article/details/109019664