列表 Mixin

进击的学霸...大约 5 分钟

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

哪些需要提取

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

怎么封装

由于只提取了公共的逻辑,模板部分未有提取,而且每个列表页调用的 api 也不同,选择采用 mixin 的方式,在 mixin 中定义公共的数据变量以及方法,在对应的业务单文件组件中引入。mixin 的特性可以在 官方文档open in 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)
    }
  }
}
/**
* 页面中引入 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>

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

总结

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

评论
  • 按正序
  • 按倒序
  • 按热度