新系统开发小问题记录

890 阅读3分钟

近期由于业务调整,业务所涉及的所有系统都做了重构,历时两个多月,终于进入全免测试的阶段,本次开发主要涉及pc、h5、小程序三端内容。小程序涉及到微信和支付宝,由于老系统是两套原生语法,新系统依旧沿用了原生语法;pc采用了vue+elementui的经典组合;h5采用了vue+antmobile组合。整个开发过程中抛开较复杂的业务逻辑外,并没有遇到太大的困难,但是有些问题点可能遇见过很多次,但真正开发中可能依旧不太熟悉,在此简单记录下。

1、vue全局过滤器

简单说就是在一个文件里定义好相关方法,在main.js里引入,并循环绑定方法名和对应的方法。

// filter.js
export privatePhone = function(val,retain = 4){
    if(!NUMBER(val) || String(val).length !== 11 || retain==0 ) return val;
    let phone = String(val)
    let digit = 11 - 3 - retain
    let reg = new RegExp(`^(\d{3})\d{${digit}}(\d{${retain}})$`)
    return mobile.replace(reg,`$1${'*'.repeat(digit)}$2`)
}

// main.js
import * as filters from '../filter.js'

Object.keys(filters).forEach((filter)=>{
    Vue.filter(filter, filters[filter])
})

在使用时,就可以直接通过 {{number | privatePhone}} 或者 v-bind:val="number | privatePhone" 直接调用

2、vue-router跳转传参方式

这个需要记住一点就是params传参,只能和name一起使用,不能和path一起使用

// 方法一:
this.$router.push({name: 'Home', params: {type: 'open'}})
取值:this.$route.params.type

// 方法二
this.$router.puah({path: '/index', query: {type: 'open'}})
取值:this.$route.query.type

3、vue关于父子组件传参和方法传递

这个问题可以说是老生常谈了,面试中也经常说到,但是在实际使用中还是会有些小问题

(1)嵌套组件间值和方法传递

这里我们不说其他方法,主要记录下 $attrs$listeners 方式。

// 父组件
<template>
    <Child :foo="foo" :bar="bar" v-on:getChild="getChild" />
</template>
<script>
    data(){
        return {
            foo: 'foo',
            bar: 'bar'
        }
    },
    methods: {
        getChild(){}
    }
</script>

// 一级子组件Child
<template>
    <div>foo: {{foo}}</div>
    <ChildLit v-bind="$attrs" v-on="$listeners" />
</template>
<script>
    props: ['foo']
</script>

// 二级子组件ChildLit
<template>
    <div @click="getChild">bar: {{bar}}</div>
</template>
<script>
    props: ['bar'],
    methods: {
        getChild(){
            this.$emit('getChild')
        }
    }
</script>

(2)子组件如何判断父组件是否传递了相关方法

有时候在子组件里直接调用父组件传递的方法,但是可能某种情况并没有该方法。这个判断方法其实也是用了上边提到的 $listeners 方法

// 子组件
childMethod(){
    if(this.$listeners['getChild']){
        this.$emit('getChild')
    }
}

(3)父组件接收子组件传递过来参数时,如何再添加自定义参数

我们知道,在父组件使用子组件传递过来方法和参数只要这样就可以:

// 父组件
@click="getChild(type)"

// 子组件
this.$emit('getChild', 'open')

但是如果在父组件方法中还需要传递自己所需要的其他参数呢,比如循环列表的index?可以这样操作,使用 $event 获取传递过来的参数,其他自己的参数正常传就可以了:

// 父组件
@click="getChild($event, index)"

// 子组件
this.$emit('getChild', 'open')

4、关于element的一些问题

elementui组件库可以说很强大很实用,但是其中也有一些问题可能文档里没有写,或者需要花费很长时间去看文档才能使用,这里简单记录几个问题。

(1)封装element-ui table

表格的使用可以说是管理端最常用的功能了,封装一个拿来即用的表格,开发效率大大提升,话不多说,直接上代码。

<template>
    <div>
        <el-table
            :data="dataList"
            border
            style="width: 100%"
            :show-header="isHeader"
            :row-key="rowKey"
            :indent="indentNum"
            :ref="refs"
            @select="selectOne"
            @select-all="seleceAll"
            @selection-change="selectChange"
            @toggleRowSelection='toggleRowSelection'
        >
            <template v-if="columnList.length">
                <el-table-column 
                    v-if="isSelect"
                    type="selection"
                    header-align="center"
                    align="center"
                    width="50"
                    :selectable="selectRow"
                ></el-table-column>
                <el-table-column
                    v-for="item in columnList" 
                    :key="item.prop"
                    :type="item.elType"
                    :prop="item.prop"
                    :label="item.label"
                    :width="item.width"
                    :fixed='item.fixed'
                    :show-overflow-tooltip="item.isShowTip"
                    :align='item.align ? item.align : "center"'
                    header-align='center'
                >
                    <template slot-scope="scope">
                        <template v-if="item.slot">
                            <!-- 需要使用插槽展示的数据 -->
                            <slot :name="item.prop" :scope="scope.row"></slot>
                        </template>
                        <template v-else>
                            <!-- 直接获取属性展示内容 -->
                            {{scope.row[item.prop]}}
                        </template>
                    </template>
                </el-table-column>
            </template>
        </el-table>
        
        <!-- 页码 -->
        <template v-if="isPagination">
            <el-pagination
                :current-page="pageIndex"
                :page-sizes="[10, 20, 50, 100]"
                :page-size="pageSize"
                :total="totalPage"
                layout="total, sizes, prev, pager, next, jumper"
                @size-change="sizeChangeHandle"
                @current-change="currentChangeHandle"
            ></el-pagination>
        </template>
    </div>
</template>

<script>
export default {
    props: {
        // 是否是可选表格,默认不是可选
        isSelect: {
            type: Boolean,
            default: false
        },
        // 表格数据
        dataList: {
            type: Array,
            detault: []
        },
        // 是否显示头部,默认显示
        isHeader: {
            type: Boolean,
            default: true
        },
        // rowKey值,折叠表格需要
        rowKey: {
            type: [Function, String],
            default: ''
        },
        // 折叠表格时折叠内容缩进
        indentNum: {
            type: Number,
            default: 16
        },
        // 表头列表
        columnList: {
            type: Array,
            default: []
        },
        // 当前页
        pageIndex: {
            type: Number
        },
        // 每页条数
        pageSize: {
            type: Number,
            default: 10
        },
        // 总页数
        totalPage: {
            type: Number
        },
        // 是否显示页码,默认显示
        isPagination: {
            type: Boolean,
            default: true
        },
        // 是否默认全选
        isSelectAll: {
            type: Boolean,
            default: false
        },
        // table ref
        refs: {
            type: String,
            default: ''
        }
    },
    methods: {
        // 输入页码
        sizeChangeHandle(val){
            this.$emit('sizeChangeHandle', val)
        },
        // 翻页
        currentChangeHandle(val){
            this.$emit('currentChangeHandle', val)
        },
        // 选择某一项
        selectOne(select, row){
            this.$emit('selectOne', select, row)
        },
        // 全选
        seleceAll(selection){
            this.$emit('selectAll', selection)
        },
        // 选择变化事件
        selectChange(selection){
            this.$emit('selectChange', selection)
        },
        // 选择变化事件
        selectRow(row){
            let itemName = null;
            this.$emit('selecTable', row, val => {
                itemName = val
            })
            return itemName;
        },
        // 有数据时默认全选
        toggleRowSelection(row, status){
            this.$nextTick(()=>{
                if (this.isSelectAll && this.refs){
                    for(let i = 0; i < this.dataList.length; i++){
                        this.$refs[this.refs].toggleRowSelection(this.dataList[i], true);
                    }
                }
            })
        }
    },

    watch: {
        dataList(valArr) {
            if (valArr.length && this.isSelectAll){
                this.toggleRowSelection()
            }
        }
    }
}
</script>

上面封边的table方法可以说一些常用的功能都包含在里边了,包括但不限于普通表格、页码、折叠表格、表格是否可以勾选、默认勾选所有内容,基本满足日常开发,如果需要其他功能,可以直接添加扩展。

(2)默认勾选表格某一项(全部)

上边表格封装方法里已经写入了该方法,就是这段:

this.$refs[this.refs].toggleRowSelection(this.dataList[i], true);

这里不在详细说里边的内容,需要的可以参考大佬的这篇文章 Element 默认勾选表格 toggleRowSelection的实现:www.jb51.net/article/169… 里边说的很详细了。

(3)element-ui table表格动态合并

有时候我们的表格可能需要进行某几行或者某几列纵向或者横向合并的需求,这就需要用到 table 表格的 span-method 属性。先看下官网对这个属性的说明:

貌似也没啥说明......

四个参数分别代表:行数据、列数据、第几行、第几列,这里以合并行数为例。

<el-table
    :data="ruleArr"
    border
    style="width: 100%"
    :span-method="spanTotal"
></el-table>

<script>
methods: {
    spanTotal({row, column, rowIndex, columnIndex}){
        if (columnIndex === 5 || columnIndex === 7 || columnIndex === 8){
            switch (rowIndex){
                case 0:
                    return {
                        rowspan: row.idNum,
                        colspan: 1
                    }
                default: 
                    return {
                        rowspan: 0,
                        colspan: 0
                    }
            } 
        }
    }
}
</script>

重点说下 spanTotal 方法:这里写的方法代表第5、7、8列,从第0行开始合并表格,长度(rowspan)为 row.idNum(示例里代表表格数据的长度)。此方法里最关键的就是 rowspan这个属性,表示需要合并多少个单元格,如果是固定值可以直接写数字,如果是动态的,最好添加到行或者列数据里,可以直接获取到。

(4)element-ui table获取当前行的索引

使用封装表格方法,如果需要获取当前行对应的索引,可以通过 scope.$index 方法获取

<template slot-scope="scope">
    <el-form-item>
        {{scope.$index}}
    </el-form-item>
</template>

(5) 关于element-ui 表单动态添加及校验

关于这部分内容不打算展开说,简单总结下就是:vue里的数据要动态响应要么开始在 data 里定义好,要么后期通过 vue.$set 方法添加;如果是深层对象数组嵌套可以通过 Object.assign 或者 splice 方法进行处理,否则可能会出现数据不响应等很莫名其妙的问题。

5、小程序接口登录过期无感刷新

对于登录,通常我们会存一个token,在请求头上带上token,但是如果token过期,对于某些没有登录页面的小程序来说就需要实现无感刷新token并重新请求之前的接口逻辑,直接上方法,也很好理解。

let isAgaginLogin = false
let requestList = []
function httpRequest(apiUrl, data = {}, isLoading=true, methods="POST") {
  isLoading && wx.showLoading({ title: '正在加载...' });
  return new Promise((resolve, reject) => {
    wx.request({
      url: apiUrl,
      data: data,
      method: methods,
      header: {
        'Content-Type': 'application/json',
        'token': wx.getStorageSync("token")
      },
      success: function(res) {
        wx.hideLoading();
        if (res.data.code == 200) {
          res.data.token = res.header["token"];
          resolve(res.data);
        } else {
          if(res.data.code == 403){
            console.log('登录过期,重新登录')
            new Promise((resolve1)=>{
              requestList.push(()=>{
                resolve(httpRequest(apiUrl, data, isLoading, methods))
              })
            })
            if(!isAgaginLogin){
              isAgaginLogin = true
              return wx.login({
                success: wxRes => {
                  httpRequest(loginUrl, {
                      code: wxRes.code
                  }).then(function(res) {
                    if (res && res.code == 200) {
                      let loginData = res.data
                      wx.setStorageSync('token', loginData.token);
                      setTimeout(()=>{
                        requestList.forEach((cb)=> cb())
                        requestList = []
                        isAgaginLogin = false
                        return
                      }, 2000)
                    }
                  }).catch((err)=>{
                    isAgaginLogin = false
                    reject(res)
                  })
                }
              })
            }
          } else {
            console.log('other err====>', res)
            wx.showToast({
              title: res.data.msg || '网络异常,请稍后再试' + "!",
              icon: 'none'
            });
            reject(res)
          }
        }
      },
      fail: function(err) {
        wx.hideLoading();
        wx.showToast({
          title: "网络异常,请稍后重试!",
          icon: 'none'
        })
      }
    })
  })
}

以上就是此次开发过程中遇到的小问题,还有一些问题没记住是啥,后面想起来再补上吧,主要是对自己做个记录。