El-table优化&列表生成器|项目复盘

929 阅读3分钟

前言

工作中难免遇到重复劳动。每次对着 element-ui 中 的 el-table 一顿乱撸。为了避免重复劳动,代码生成器一定是一个不错的选择。附图。

微信图片_20210319113631.png

el-table 优化

在日常开发中难免对后台返回的数据进行一些处理。然而在使用 el-table-colume 的时候,经常使用 el-table-column 的插槽会增加最少一行代码,同时也会降低代码的阅读理解能力。通过对文档的研究发现发现 formatter 参数可以对值对象进行格式化。使用该参数可以提高数据处理的复用能力,同时也将省略slot插槽,提高代码的阅读流畅度。

优化前


<el-table-column prop="startTime" label="开始时间">
    <template slot-scope="{ row }">
        {{ row.startTime | dayjs('YYYY-MM-DD HH:mm:ss') }}
    </template>
</el-table-column>
// 封装优化代码
export class ElTableUtil {
    // 日期格式化
    static dateFormatter(format = 'YYYY-MM-DD HH:mm:ss') {
        return (row, column, cellValue, index) => {
            if (!cellValue) return ''
            const _cellValue = _.castArray(cellValue || '')
            return _cellValue.reduce((total, str, index) => {
                const tailIndicator = index === _cellValue.length - 1 ? '' : ' - '
                return total + dayjs(str).format(format) + tailIndicator
            }, '')
        }
    }
    // 除法
    static divide(num: number, indicator = 2) {
        if (num === 0) throw new Error('ElementUI table 除法不能除0')
        return (row, column, cellValue, index) => {
            return (cellValue / num).toFixed(indicator)
        }
    }
    // 除以另外一个字段
    static divideBy(key: string, indicator = 2) {
        return (row, column, cellValue, index) => {
            if (!row[key] || row[key] === 0) return NaN
            return (cellValue / row[key]).toFixed(indicator)
        }
    }
    // 秒或毫秒转 => xx天xx小时
    // 数值 => 数值/100 + '%'
    // 数值 => 解析对应中文
}

优化后

<!-- 日期格式化 -->
<el-table-column prop="startTime" label="时间" :formatter="ElTableUtil.dateFormatter()" />
<!-- 除法并限制小数点 -->
<el-table-column prop="rate" label="XX率(%)":formatter="ElTableUtil.divide(1, 0)" />

代码生成器

最初想到这个东西的时候,后台开始启用 Swagger,前端开始使用 TypeScript 来进行项目开发。既然后端已经有了类型为何我们不把后端的类型拿来用呢?

灵感

通过对调查,发现 swagger 返回的 json 中会带有一些固定类型,比如枚举,字符串,数字等。通过对此json和类型的解析。可以自动生成 TypeScript 中的 type 类型。 代码生成器:Swagger Codegen

// Swagger api 会返回类似的 json
// QRCodeDTO 的 description 可以解析成页面的 title
// 对象的 key 可以解析成 column 的 prop
// description 可以解析成 column 的 label
const swaggerJson = {
    "QRCodeDTO": {
      "type": "object",
      "properties": {
        "xxId": { "type": "string", "description": "xxid" },
        "XXXId": { "type": "string", "description": "XXXid" },
        "XXXXId": {
          "type": "string",
          "description": "XXXXid""required": true
        }
      },
      "title": "QRCodeDTO",
      "description": "生成QRCODE用的数据"
    }
}
// 通过 代码生成器 可转化为
type Check = {
    'xxId' ? : string
    'XXXId' ? : boolean
    'XXXXId' : string
};

生成器

建议先大致看一下模板引擎 [EJS](https://ejs.bootcss.com/) 的语法。不然应该不好看明白。

既然可以生成 typescript 为什么不直接生成页面呢。自动写代码何乐而不为!采用 ejs 作为模板引擎。 首先做json转列表的解析,对特定字段进行正则匹配,然后解析成对应的组件。

字段解析成组件模板

<% if (/.*img.*/.test(_.lowerCase(item.id)) || /.*image.*/.test(_.lowerCase(item.id))) {%> 
    <el-table-column label="<%= item.description || item.id %>" prop="<%= item.id %>" show-overflow-tooltip align="center">
        <template slot-scope="scope">
            <el-img :value="scope.row.<%= item.id %>" />
        </template>
    </el-table-column>
<% } else if (/.*id.*/.test(_.replace(_.lowerCase(item.id), ' ', '')) && item.id != 'id') { %> 
    <el-table-column label="<%= item.description || item.id %>" prop="<%= item.id %>" show-overflow-tooltip :formatter="ElTableUtil.get('<%= item.id %>_name')" align="center" />
<% } else if (/.*status.*/.test(_.replace(_.lowerCase(item.id), ' ', ''))) { %> 
    <el-table-column label="<%= item.description || item.id %>" prop="<%= item.id %>" show-overflow-tooltip align="center">
        <template slot-scope="scope">
            <el-tag type="default|success|info|warning|danger">{{ scope.row.<%= item.id %> }}</el-tag>
        </template>
    </el-table-column>
<% } else if (/.*time.*/.test(_.lowerCase(item.id))) {%> 
    <el-table-column label="<%= item.description || item.id %>" prop="<%= item.id %>" show-overflow-tooltip :formatter="ElTableUtil.dateFormatter()" align="center"/>
<% } else if (item.type == 'number') {%> 
    <el-table-column label="<%= item.description || item.id %>" prop="<%= item.id %>" show-overflow-tooltip :formatter="ElTableUtil.divide(1)" align="center"/>
<% } else if (item.type == 'boolean') {%> 
    <el-table-column label="<%= item.description || item.id %>" prop="<%= item.id %>" show-overflow-tooltip :formatter="ElTableUtil.boolean('是','否')" align="center"/>
<% } else if (item.type == 'string' && item.enum) {%> 
    <el-table-column label="<%= item.description || item.id %>" prop="<%= item.id %>" show-overflow-tooltip :formatter="ElTableUtil.enum('')" align="center"/>
<% } else { %>
    <el-table-column label="<%= item.description || item.id %>" prop="<%= item.id %>" show-overflow-tooltip align="center" />
<% } %>

字段解析查询框模板

<%  if (/.*org.*/.test(_.replace(_.lowerCase(item.id), ' ', ''))) {%> 
   
<% } else if (/.*time.*/.test(_.replace(_.lowerCase(item.id), ' ', '')) || /.*date.*/.test(_.replace(_.lowerCase(item.id), ' ', ''))) {%> 
    <el-date-picker v-model="condition.<%= item.id %>" value-format="timestamp" type="daterange" :default-time="['00:00:00', '23:59:59']"> </el-date-picker>
<% } else if (item.type == 'boolean') {%> 
    <el-select v-model="condition.<%= item.id %>" clearable>
        <el-option label="是" :value="true" />
        <el-option label="否" :value="false" />
    </el-select>
<% } else if (item.type == 'integer') {%> 
    <el-input-number v-model="condition.<%= item.id %>" :min="1" :max="10" label="<%= item.description %>"></el-input-number>
<% } else if (item.type == 'string' || item.type == 'number') { %> 
    <el-input v-model.trim="condition.<%= item.id %>" clearable/>
<% } else {%>
    <!-- 不知道是啥 -->
    <component v-model="condition.<%= item.id %>" />
<% } %> 

页面解析模板

<template>
    <page-list>
         <!-- 查询条件 -->
        <template slot="search">
            <% conditions.forEach(condition => {  %>
                <el-form-item label="<%= condition.description || condition.id %>">
                    <%= formatEdit({item: condition}) %>
                </el-form-item>
            <% }) %> 
        </template>
         <!-- 列表部分 -->
        <template slot="list">
            <% list.children.forEach(item => {  %>
                <%= formatlist({item: item}) %>
            <% }) %> 
            <el-table-column label="操作" width="130" fixed="right">
                <template #default="{ row }">
                    <el-button icon="el-icon-s-check" type="text" title="查看详情"  @click="handleEdit(row.id)" />
                </template>
            </el-table-column>
        </template>
    </page-list>
</template>

解析

import { saveAs } from 'file-saver'
// condition 和 list 均为swagger 返回后的json
const gendata = { conditions: this.selectNode?.children?.filter(e => e.search) || [], list: this.selectNode }
// model, search, list, pagelist 均为 ejs 模板
let fn = ejs.render(`<%- pagelist({conditions:conditions,list:list,formatlist:formatlist,formatEdit:formatEdit,formatModel:formatModel})%>`, {
            formatModel: model,
            formatEdit: search,
            formatlist: list,
            pagelist: pagelist,
            ...gendata
        })
const file = new File([fn], 'hello world.txt', { type: 'text/plain;charset=utf-8' })
// 页面解析模板,解析的结果
saveAs(file, _.kebabCase(this.selectNode.label) + '.vue') 

总结

通过对 swagger 生成 json 的解析,我们可以利用模板引擎一键生成标准模板页面。通过 file-saver 一键下载到本地。不过缺点也是显而易见的,后端没有对应swagger对象的时候。我们还是无能为力的,需要后端先行确认数据结构。

文章末尾请带上以下文字及链接:本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情