列表 Mixin

... 2020-12-07 02:10:42 大约 5 分钟

后台项目做多了就会发现,在一个成熟的后台项目中,列表页面的结构以及逻辑都是相对固定的,怎样才能把重复的这部分提取出来呢

# 哪些需要提取

项目里用的 UI 库是 Element UI 。一个列表页从 UI 表现来看上至下通常包含这么几部分:筛选栏,列表,分页。在 vue 单文件组件中看的话,主要是模板区和 js 区,项目里列表页一般是统一风格,所以公共样式不会写在列表组件里。首先模板部分我认为是不需要提取出来的,一方面是因为用了 element 表格组件,二次封装会使传值等变的更为复杂,另一方面是封装过后在可读性上有很大的损失

# 怎么封装

由于只提取了公共的逻辑,模板部分未有提取,而且每个列表页调用的 api 也不同,选择采用 mixin 的方式,在 mixin 中定义公共的数据变量以及方法,在对应的业务单文件组件中引入。mixin 的特性可以在 官方文档 (opens new window) 中了解,通过这种方式优点是可以提取公共逻辑,并且可以在混入的地方通过新声明覆盖旧声明的方式重写;缺点是通过 mixin 引入的内容在业务页面是没法直接看到的,这也是使用 mixin 的主要问题。在项目的协作开发,我们采用约定开发的方式,使用者要了解页面所使用的 mixin ,这样可以降低使用 mixin 导致出问题的几率

# 成果

/**
 * list.js 列表 mixin
*/

import { TIMER_DURATION } from '@/config'

export default {
  data () {
    return {
      // 接口的 api 方法
      getListApi: () => {},
      // 请求参数
      reqParams: {},
      // 过滤参数
      filterParams: {},
      // 列表数据
      list: [],
      // 分页数据
      page: {
        pageStart: 1,
        pageSize: 20
      },
      // 返回参对应列表数据的字段名
      responseListName: 'list',
      // 请求接口 loading flag
      listLoading: false,
      // 是否开启轮询
      needPolling: false,
      // 轮询的时间间隔,默认采用全局统一间隔时间
      timerDuration: TIMER_DURATION,
      // 轮询定义的定时器
      timer: null
    }
  },
  mounted () {
    // 方法调用放在页面中
    // this.getList()
    // this.startPolling()
  },
  beforeDestroy () {
    this.endPolling()
  },
  methods: {
    getList () {
      if (!this.listLoading) {
        this.listLoading = true
        this.getListApi({
          ...this.reqParams,
          ...this.filterParams,
          ...this.page
        }).then(res => {
          // 翻页可能连续点击,如果返回数据不是当前页,不执行赋值操作
          if (res.data && res.data.page && (this.page.pageStart !== res.data.page.pageStart)) {
            return
          }
          this.handleGetListResponse(res)
        }).catch(() => {
          this.handleGetListErr()
        }).finally(() => {
          this.listLoading = false
        })
      }
    },

    // 如果默认方法不能满足需求,可以在组件中重写这个方法来处理返回数据
    handleGetListResponse (res) {
      res.data = res.data || {}
      this.list = res.data[this.responseListName] || []
      res.data.page && (this.page = res.data.page)
    },

    handleGetListErr () {},

    // 翻页
    handleSizeChange (val) {
      this.page.pageStart = val
      this.getList()
    },

    // 过滤
    handFilterStatusChange (e) {
      // 结束当前的定时任务,避免和本次请求冲突
      this.endPolling()
      this.getList()
      this.startPolling()
    },

    // 开始轮询
    startPolling () {
      if (!this.needPolling) {
        return
      }
      this.timer = setInterval(this.getList, this.timerDuration)
    },
    // 结束轮询
    endPolling () {
      if (!this.needPolling) {
        return
      }
      clearInterval(this.timer)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
* 页面中引入 mixin 使用
*/
<template>
  <div class="user-manage">
    <basic-box-comp>
      <el-tabs v-model="reqParams.auditStatus" type="card" @tab-click="handleTabClick">
        <!-- 待审核 -->
        <el-tab-pane label="待审核" name="0" />
        <!-- 审核通过 -->
        <el-tab-pane label="审核通过" name="1" />
        <!-- 审核未通过 -->
        <el-tab-pane label="审核未通过" name="2" />
      </el-tabs>

      <!-- 查询栏 -->
      <div class="node-fliter-bar">
        <el-form ref="filterParams" :model="filterParams" :inline="true" class="search">
          <el-form-item label="用户名">
            <el-input v-model="filterParams.userName" placeholder="请输入用户名" />
          </el-form-item>
          <el-form-item label="用户类型">
            <el-select v-model="filterParams.userType" clearable placeholder="请选择用户类型">
              <el-option value="1" label="个人用户" />
              <el-option value="2" label="企业用户" />
              <el-option value="" label="不限" />
            </el-select>
          </el-form-item>
          <el-form-item label="用户名ID">
            <el-input v-model="filterParams.userId" placeholder="请输入用户名ID" />
          </el-form-item>
          <el-form-item label="是否可信">
            <el-select v-model="filterParams.trusted" clearable placeholder="请选择是否可信">
              <el-option value="1" label="" />
              <el-option value="0" label="" />
              <el-option value="" label="不限" />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button class="search-btn" size="mini" type="primary"
              @click="handFilterStatusChange">查询</el-button>
            <el-button
              class="search-btn"
              size="mini"
              @click="handleResetFilterParams"
            >重置</el-button>
          </el-form-item>
        </el-form>
      </div>

      <!-- 列表表格 -->
      <el-table v-loading="listLoading" :data="list">
        <el-table-column prop="userName" label="用户名" />
        <el-table-column label="用户类型">
          <template slot-scope="scope">{{ scope.row.userType | fmtUserType}}</template>
        </el-table-column>
        <el-table-column label="企业名称">
          <template slot-scope="scope">{{ scope.row.companyName || '--' }}</template>
        </el-table-column>
        <el-table-column prop="chainName" label="所属链" />
        <el-table-column prop="userId" label="用户ID" width="200" />
        <el-table-column label="电话号码">
          <template slot-scope="scope">
            <span v-if="scope.row.phone">{{ scope.row.phone | hidePhoneNum }}</span>
            <span v-else>--</span>
          </template>
        </el-table-column>
        <el-table-column label="是否可信认证" width="110" align="center">
          <template slot-scope="scope">{{ scope.row.trusted | fmtTrusted}}</template>
        </el-table-column>
        <el-table-column label="时间" width="200">
          <template slot-scope="scope">{{ scope.row.createTime | fmtTimeFromTimestamp }}</template>
        </el-table-column>
        <el-table-column label="操作" width="80">
          <template slot-scope="scope">
            <el-button type="text" @click="handleGoDeatil(scope.row)">
              {{ scope.row.auditStatus === '0' ? '审核' : '详情' }}
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- 分页器 -->
      <div class="pagination-box">
        <el-pagination
          hide-on-single-page
          background
          layout="prev, pager, next"
          :current-page="page.pageStart"
          :page-size="page.pageSize"
          :total="page.pageTotal"
          @current-change="handleSizeChange" />
      </div>
    </basic-box-comp>
  </div>
</template>

<script>
import listMixin from '@/utils/mixins/list'
import { getUserListApi } from '@/api/user'

const defaultFilterParams = {
  code: '',
  userName: '',
  userType: '',
  userId: '',
  trusted: '',
}
export default {
  name: 'UserList',
  mixins: [listMixin],
  data () {
    return {
      getListApi: getUserListApi,
      responseListName: 'userList',
      listLoading: false,
      reqParams: {
        auditStatus: '0', // 0待审核 1 通过 2不通过
      },
      filterParams: { ...defaultFilterParams }
    };
  },
  mounted () {
    this.getList()
  },
  methods: {
    handleTabClick (tab, event) {
      this.page = {
        pageStart: 1,
        pageSize: 20,
      }
      this.getList()
    },
    handleGoDeatil ({ userId, code }) {
      this.$router.push({
        name: 'UserDetail',
        params: { userId },
        query: { code }
      })
    },
    handleResetFilterParams () {
      this.filterParams = { ...defaultFilterParams }
      this.handFilterStatusChange()
    }
  },
};
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147

上述示例是稍复杂的应用,若只是展示列表,只需重置 data 中的数据即可

# 总结

通过这样的方式,将同类业务中相同的逻辑抽离出来,列表页面的逻辑被简化,后续的业务迭代中只需很少的代码即可实现需求,公共的逻辑在 mixin 中统一维护,提高开发效率

上次编辑于: 2021年5月31日 15:57