项目开发经验记录

479 阅读9分钟
  1. 字段命名规则:简单易懂,驼峰命名法
  2. 错误码提示:当很多地方调用同一块代码块时,应该抽出来封装成一个方法,所有的数据操作均在封装的函数中实现
  3. 删除多余else:if判断时,去掉多余的else判断
  4. 定义数组对象中的标识字段:定义的字段应该要显现出定义的具体意义,不能笼统的写为1,2,3...
  5. 加入移除黑名单方案: 定义一个黑名单数组 在每次打开操作菜单时检测当前用户是否在黑名单数组中 如果在,就显示移除黑名单 如果不在,就显示加入黑名单
  6. 做弹框时拦截滑动事件
    <view catchtap="onPreventTap" catchtouchmove="onTouchmove"></view>
    onPreventTap(e) {}
    onTouchmove(e) {}
  1. vscode编辑软件快捷键记录
   重开一行: 光标在行尾的话,回车即可;不在行尾,ctrl + enter 向下重开一行;ctrl+shift + enter 则是在上一行重开一行。
   删除一行:光标没有选择内容时,ctrl + x 剪切一行;ctrl +shift + k 直接删除一行。
   移动一行:alt + ↑ 向上移动一行;alt + ↓ 向下移动一行。
   复制一行:shift + alt + ↓ 向下复制一行;shift + alt + ↑ 向上复制一行
   选中词:ctrl+d
   Ctrl+P 快速打开最近打开的文件

   Ctrl+Shift+N 打开新的编辑器窗口

   Ctrl+Shift+W 关闭编辑器

   Home 光标跳转到行头

   End 光标跳转到行尾

   Ctrl + Home 跳转到页头

   Ctrl + End 跳转到页尾

   Ctrl + Shift + [ 折叠区域代码   Ctrl + Shift + ] 展开区域代码

   Ctrl + / 添加关闭行注释

   Shift + Alt +A 块区域注释
  1. 检测输入框是否输入中文字符
  let reg = /^[\u4E00-\u9FA5]+$/;
  let name = this.data.name.trim();
  if (!reg.test(name)) {
    this.notifyFail('名字只能输入中文')
    return;
  }

9.微信小程序input只能输入汉字

/** 姓名输入框输入函数 */
  onNameInput(e) {
    let { value: text } = e.detail
    text = text.trim()

    // 检测是否输入的中文名字
    let reg = /^[\u4E00-\u9FA5]+$/;
    
    // 纯数字正则表达式 reg = /[^\d]/

    if (!reg.test(text)) {
      // 非法输入提示语
      wx.showToast({
        title: '名字只能输入汉字',
        icon: 'none',
        duration: 1500,
      });

      return text.substring(0, text.length - 1)
    }
  }

10.css兼容,去除底部灰色安全区域,全屏覆盖

padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);

// 对安全区域做加减处理
 bottom: calc(138rpx ~"+ constant(safe-area-inset-bottom)");
 bottom: calc(138rpx ~"+ env(safe-area-inset-bottom)");

11.个人信息获取及更新

// 获取个人信息
const userInfo = await xapi.getPersonalInfo(this.userId) || {};
// 更新个人信息
XData.getInstance().resetUserData(self.userId);
  1. css超出一行,两行用省略号
// 一行
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;

// 两行
overflow:hidden;
text-overflow:ellipsis;
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:2;
  1. swiper轮播卡死、不停晃动的问题 估计卡死的时候swiper内部组件出现了死循环,此时swiper绑定的bindchange方法也不会触发,猜测出现该问题的原因是给swiper组件绑定了current属性,然后又在绑定的bindchange事件里改变了current属性值,引起了swiper内部的bug。

解决方案:给swiper绑定的current属性,这个属性值存储在变量a里,a只用来控制改变swiper轮播位置;给swiper绑定的bindchange事件里,通过e.detail.current获取到值存储在另一个变量b里,b只用来获取当前轮播索引;需要重置数据时,将a和b一起重置为0。

代码:

<swiper
  autoplay
  interval="{{5000}}"
  circular
  class="bannerSwiper"
  current="{{current}}"
  bindchange="handleCarouselChange"
>
  <block wx:for="{{partData}}" wx:key="key">
    <swiper-item>
      <view
        class="imgWrapBanner {{carouselIndex === index ? 'active': ''"
      >
        <image
          class="imgBanner"
          src="{{item.pic}}"
        ></image>
      </view>
    </swiper-item>
  </block>
</swiper>
Component({
  properties: {
    partData: {
      type: Array,
      value: []
    }
  },

  data: {
    current: 0, // 只用于控制轮播位置
    carouselIndex: 0 // 只用于获取轮播索引
  },

  observers: {
    partData () {
      // 防止出现 [swiper] current无效,请修改current值 的警告,会导致图片空白
      this.setData({
        current: 0,
        carouselIndex: 0
      })
    }
  },

  methods: {
	handleCarouselChange (e) {
      this.setData({
        carouselIndex: e.detail.current
      })
    }
  }
})
  1. 小程序判断触屏上滑和下滑
<view bindtouchstart="onGuideTouchStart"
    bindtouchmove="onGuideTouchMove">
</view>
/** 指引蒙层触摸开始事件 */
onGuideTouchStart(e) {
  const { pageY } = e.touches[0] || []
  this.startPageY = pageY || 0
},
/** 指引蒙层触摸移动事件 */
onGuideTouchMove(e) {
  let { showMoveGuide } = this.data
  const { pageY } = e.touches[0] || []

  if (!showMoveGuide) return;
  // 和移动开始事件中的strartPageY比较,判断上滑或下滑动作
  if (this.startPageY > pageY) { // 上滑
    // 关闭指引蒙层,并将滑块移动到第二个
    this.setData({
      showMoveGuide: false,
      currentIndex: 1
    })
  }
}

15.小程序点击弹窗以外的的位置也可关闭弹窗 思路: 给弹窗view绑定一个拦截事件,给整个遮障层绑定关闭弹窗事件

<view class="popup" catchTap="close">
	<view class="popup-content" catchtap="onPreventTap"></view>
    <view class="close" catchTap="close"></view>
</view>

close() {
 console.log('关闭弹窗操作')
},
onPreventTap(){}
  1. 列表页加载数据显示问题 出现的问题: 控制页面显示很多都是通过列表数组长度控制,导致数据还没请求,缺省页及其他通过数组长度控制的页面被显示出来,

具体表现为:页面刚进去显示缺省页,等会数据加载完再显示数据列表页,给人一种很奇怪的感觉

解决方法: 定义一个字段,字段初始值为falae,等请求发送完改为true,通过定义的新字段和列表数据长度共同控制缺省页的显示。 18. 数组使用diff的方式setData 首先将data中的数据利用copyData方法进行拷贝,再将改变的数组利用updateData方法setData

/**
     * 更新data数据,优化setData处理;
     * 注意data数据不要直接引用this.data的数据,需要copy或重新赋值
     * @param {*} data
     */
    updateData(data) {
      return new Promise((resolve, reject) => {
        if (!data) {
          console.warn('=== updateData 格式非法 ===');
          resolve(null);
          return;
        }
        let result = diff(data, this.data);
        if (!Object.keys(result).length) {
          console.warn('=== updateData 无数据更新 ===');
          resolve(null);
          return;
        }
        this.setData(result, () => {
          resolve(result);
        });
      });
    },
    // 拷贝data中的数据
    copyData(data) {
      return JSON.parse(JSON.stringify(data));
    }

19.数组数据分步push,减少页面渲染压力

/**
     * 分步添加列表数据
     * @param {*} lists 列表数据
     * @param {*} name data中字段name
     * @param {*} step 每步多少条
     * @param {*} interval 间隔时间
     */
    stepPushList({ lists, name, step = 10, interval = 300, reset = false }) {
      this._clearBaseStepPushTimer();

      if (!lists || !name || !Array.isArray(lists)) return;
      if (lists.length <= 0) return;

      // 步进数量不能小于等于零
      step = step <= 0 ? 10 : step;

      // 如果重置
      reset && (this.data[name] = []);

      let oldLists = this.data[name] || [];
      let newLists = this.copyData(lists);
      let pushLists = newLists.splice(0, step);

      // 设置部分数据
      this.setListPartData({
        partLists: pushLists,
        start: oldLists.length,
        name,
        callback: () => {
          if (newLists.length <= 0) return;
          this._baseStepPushTimer = setTimeout(() => {
            this.stepPushList({ lists: newLists, name, step, interval });
          }, interval || 300)
        }
      });
    },
    // 清理分步定时器
    _clearBaseStepPushTimer() {
      this._baseStepPushTimer && clearTimeout(this._baseStepPushTimer);
      this._baseStepPushTimer = null;
    },

20.利用小程序swiper,video原生组件模仿抖音短视频

组件拆分: 将一个swiper页面单独抽出来做成一个组件,组件内部视频的播放与暂停通过组件内部控制

视频的播放与暂停通过父组件滑块的索引和列表数据索引是否相等进行控制

控制swiper的渲染,减少手机的渲染压力,每次只渲染3个滑块,通过Math.abs(swiperIndex- index) <= 1 || false控制 21.实时更新用户关系数据 获取更新记录的用户关系数据: XData.getInstance().getUserRelation(commentUserId); 更新用户关系数据: XApi.getUserRelation(fromUserId); 22.后台表格栏上移下移按钮控制显示

/** 能否下移函数 */
canSort(index, isUp = false) {
  const { count, size, total } = this.page

  if (isUp) {
    return !(index === 0 && count <= 1)
  }

  // 计算总的序号
  const _index = (count - 1) * size + index

  return (_index < total - 1)
},

在vue文件中使用
<el-button v-if=canSort(scope.$index, true)>上移</button>
<el-button v-if=canSort(scope.$index)>下移</button>
  1. 将number类型转换为字符串数组 例如:将123456转换为['1','2','3','4','5','6']
let count = 123456.toString().split('')

24.时间显示规则,X天/分钟前

// date.dateFormat为将时间戳转换为日期格式显示函数
getArticlePublishTime: function (createTime) {
    if (!createTime) return '';

    // 获取时间差,单位为秒
    var countTime = (Date.now() - createTime) / 1000

    // 时间差不存在或小于0,阻止函数执行
    if (!countTime || countTime < 0) return '';

    // 时间显示规则:一分钟以内,刚刚;一小时以内,X分钟前;
    // 一天以内,X小时前;2天以内,昨天;30以内,X天前;30天以后,显示具体日期
    if (countTime >= 2592000) return date.dateFormat(countTime * 1000);
    if (countTime >= 172800) return parseInt(countTime / 60 / 60 / 24) + '天前';
    if (countTime >= 86400) return '昨天';
    if (countTime >= 3600) return parseInt(countTime / 60 / 60) + '小时前';
    if (countTime >= 60) return parseInt(countTime / 60) + '分钟前';
    return '刚刚'
  }
  1. 饿了么组件表单校验
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
给el-form标签设置属性,rules设置校验规则
并将 form-item 的 prop 属性设置为需校验的字段名
  1. 微信网页授权步骤 1、引导用户进入授权页面同意授权,获取code

2、通过code换取网页授权access_token(与基础支持中的access_token不同)

3、如果需要,开发者可以刷新网页授权access_token,避免过期

4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)

  1. 微信网页开发配置安全域名

下载.txt文件部署在运行文件的根目录 38.移动端只能输入汉字

<div @input="dealName"></div>

/** 姓名输入校验 */
dealName() {
  let oldtxt = document.getElementById('username').value
  oldtxt = oldtxt.trim();
  // 检测是否输入的中文名字
  let reg = /^[\u4E00-\u9FA5]+$/;

  if (!reg.test(oldtxt)) {
    this.form.username = oldtxt.substring(0, oldtxt.length - 1)
  }
}
  1. 小程序textarea组件 textarea组件在苹果系统里面会有默认的上边距和左边距,可通过设置disable-default-padding属性去除

40.小程序保存图片

直接上码了,util.throttle方法防抖
 /** 保存证书按钮 */
    onSaveCard: util.throttle(function (e) {
      const self = this
      
      // 获取需要保存的图片
      const { img } = e.currentTarget.dataset
      if (!img) return;

      // 检查相册是否授权
      wx.getSetting({
        success (res) {
          if (!res.authSetting["scope.writePhotosAlbum"]) {
            wx.authorize({
              scope: "scope.writePhotosAlbum",
              success (res) {
                self.savePoster(img); //授权成功之后执行的方法
              },
              fail (res) {
                // 打开手机手册授权的设置界面
                self.openSetting(img);
              },
            });
          } else if (res.authSetting["scope.writePhotosAlbum"]) {
            self.savePoster(img); //授权成功之后执行的方法
          }
        },
      });
    }),
    /** 打开相册相册授权的相关设置 */
    openSetting (img) {
      if (!img) return;
      let self = this;
      wx.showModal({
        title: "用户未授权",
        content: "拒绝授权将不能保存证书,点击确定授权",
        confirmText: "去开启",
        success (res) {
          if (res.confirm) {
            wx.openSetting({
              complete: (res) => {
                if (res.authSetting["scope.writePhotosAlbum"]) {
                  self.savePoster(img); //授权成功之后执行的方法
                }
              },
            });
          }
        },
        fail(err) {
          console.error('openSetting', err)
        },
      });
    },
    /** 保存图片 */
    savePoster(url) {
      if (!url) return;

      // 转换图片路径
      util.getImageInfo({
        src: util.useHttpsUrl(url)
      }).then(res => {
        // 保存图片到本地
        wx.saveImageToPhotosAlbum({
          filePath: res.path,
          success(result) {
            wx.showToast({
              title: '荣誉证书已保存到系统相册',
              icon: 'none',
              duration: 1500
            })
          },
          fail(rej) {
            console.log('wx.saveImageToPhotosAlbum fail', rej);
          }
        })
      }).catch(err => {
        console.error('getImageInfo', err);
      })
    }

  1. element-UI ,Table组件实现拖拽效果 www.sortablejs.com/index.html

可通过设置拖拽时的透明度来增强拖拽时的效果:

onMove: (evt) => {
  evt.dragged.style="opacity: 0.1;"
},
  1. 对象数组去重:
getTagNameList(list) {
  const copyList = JSON.parse(JSON.stringify(list));
  const nameArr = copyList.map((item) => {
    if (item.secondItemName) return item.secondItemName;
  })
  this.secondClassifyTagName = copyList.filter((item,index) => {
    return nameArr.indexOf(item.secondItemName) === index
  })
}

详情可参考链接地址:  https://www.jb51.net/article/118657.htm
  1. 利用sourceMap 匹配调试

需要在行序列号上添加偏移量5593

44 js判断指定时间距离当前时间时间差

// 判断时间是否超过24小时,指定时间的时间格式为2021-12-21 2:45:12
judgeDate (date) {
  if (!date) return
  date = new Date(date.replace(/-/g, '/'))
  const nowDate = new Date()
  const dateDiffer = nowDate.getTime() - date.getTime()
  return (dateDiffer / 3600000) > 24
},
  1. 判断当前时间所处的时间段
judgeVisitTime () {
  const currentTime = dateFtt('yyyy-MM-dd hh:mm', new Date())
  let timeNumber = currentTime.substring(currentTime.length - 5).replace(/:/g, '')
  timeNumber = Number(timeNumber)
  if (timeNumber > 1900) { // 时间段 19:00-00:00
    this.waitReply = false
    this.showReplyTime = true
  } else if (timeNumber > 900) { // 时间段 9:00-19:00
    this.getReply()
  } else { // 时间段 00:00-9:00
    this.waitReply = false
    this.showReplyTime = true
  }
},
  1. 将时间戳转换为标准时间格式
function dateFtt (fmt, date, noZero = true) {
  var o = {
    'M+': date.getMonth() + 1,
    // 月份
    'd+': date.getDate(),
    // 日
    'h+': date.getHours(),
    // 小时
    'm+': date.getMinutes(),
    // 分
    's+': date.getSeconds(),
    // 秒
    'q+': Math.floor((date.getMonth() + 3) / 3),

    /* 季度 */
    S: date.getMilliseconds() // 毫秒

  }
  if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))

  for (var k in o) {
    if (new RegExp('(' + k + ')').test(fmt) && noZero) {
      fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
    } else {
      fmt = fmt.replace(RegExp.$1, o[k])
    }
  }

  return fmt
}
  1. 正则过滤表情包
// 移除emoji
function removeEmoji (str) {
  if (typeof str !== 'string') {
    return ''
  } else {
    return str.replace(/[\uD83C|\uD83D|\uD83E][\uDC00-\uDFFF][\u200D|\uFE0F]|[\uD83C|\uD83D|\uD83E][\uDC00-\uDFFF]|[0-9|*|#]\uFE0F\u20E3|[0-9|#]\u20E3|[\u203C-\u3299]\uFE0F\u200D|[\u203C-\u3299]\uFE0F|[\u2122-\u2B55]|\u303D|[\A9|\AE]\u3030|\uA9|\uAE|\u3030/ig, '')
  }
}
  1. props定义ts
productQuestionList: { // 问答分类
  type: Array as PropType<ProductQuestionItem[]>,
  default: () => []
}
  1. v-for循环遍历,外层要包着一个标签
//可避免节点渲染带来的问题!!!
<div class="parent">
<template v-for="(item,index) in lists" :key="item.id">
    {{item.name}}
</template>
</div>
  1. 当一个 form 元素中只有一个输入框时,在该输入框中按下回车应提交该表单。如果希望阻止这一默认行为,可以在 <el-form> 标签上添加 @submit.native.prevent 。(w3c中有声明)
  2. 关于element ui 按钮点击后,按enter回车,依然会触发点击的处理
//在全局的main.ts中加:

document.onclick = (e: any) => {
  let target = e.target;
  let nodeName = target.nodeName;
  let tagArr = ["SPAN", "I", "BUTTON"];
  // 根据button组件内容 里面包括一个span标签,如果设置icon,则还包括一个i标签,其他情况请自行观察。
  // 所以,在我们点击到button组件上的文字也就是span标签上时,直接执行e.target.blur()不会生效,所以要加一层判断。
  if (tagArr.includes(nodeName)) {
    if (nodeName == "SPAN" || nodeName == "I") {
      target = target.parentNode;
    }
    target.blur();
  }
};
  1. Sortable 拖拽有滚动条时的bug

image.png

53.vue3中拖拽表格的数据处理方法:

//拖拽
const handleRowDrop = (newIndex: number, oldIndex: number) => {
   const tableList = cloneDeep(state.formControl.params)
   state.formControl.params = []
   const currRow = tableList.splice(oldIndex, 1)[0]
   tableList.splice(newIndex, 0, currRow)
   nextTick(() => {
      state.formControl.params = tableList
   })
}
  1. 引用微信公众号文章有时图片会无法显示: 用正则将公众号图片地址中的origincross=“anonymous”属性去掉就能正产显示,but,经过一段时间的观察,发现此举也不一定能将所有的公众号图片修正成正常显示,此时又有了阶段二的处理

tp=webp ------> tp=png

  1. 页面引入组件过多,使用require.content()方法统一引用:
// 如果页面需要导入多个组件,原始写法
import one from '@/components/one '
import two from '@/components/two'
import three from '@/components/three'
...

components:{one,two,three, ...}

// require.context是个webpack的方法,require.context(directory,useSubdirectories,regExp)
`directory` 要检索的目录
`useSubdirectories` 是否检索子目录
`regExp` 匹配文件的正则表达式,一般是文件名

const path = require('path')
const filre = require.context('@/components',true /.vue$/)
const moudles = {}
filre.keys().map( key => {
    const name = path.basename(key,'.vue')
    moudles[name] = filre(key).default||filre(key)
})

components: moudles
  1. 使用vue中this.$attrs属性获取父组件传入子组件的值,如下:
// 父组件
<chlidren title="这是标题" width="80" height="80" />

// 子组件
mounted() {
  console.log(this.$attrs) //{title: "这是标题", width: "80", height: "80"}
},


//如果子组件定义了props,则剔除定义的属性

// 父组件
<chlidren title="这是标题" width="80" height="80" />

// 子组件
props: {
    title: {
        type: String,
        default: ''
    }
}
mounted() {
  console.log(this.$attrs) //{ width: "80", height: "80"}
},
  1. 子组件使用this.$listeners调用父组件的方法,如下:
// 父组件
<chlidren @getList="getList" />

// 子组件
mounted() {
  this.$listeners.getList //调用父组件里面的getList方法
},
  1. 通过parent以及parent以及children方法分别可以访问父组件以及子组件的属性和方法,如下:
//父组件
mounted(){
  console.log(this.$children) 
  //可以拿到 一级子组件的属性和方法
  //所以就可以直接改变 data,或者调用 methods 方法
}

//子组件
mounted(){
  console.log(this.$parent) //可以拿到 parent 的属性和方法
}
  1. 通过ref获取组件实例对象,如下:
// 父组件
<chlidren ref="chlidren"/>

mounted(){
  console.log(this.$refs.chlidren) //可以拿到子组件的实例,就可以直接操作 data 和 methods
}
  1. vue自定义指令directive用法,如下:
Vuedirective('change-color', (el,binding,vnode) => {
    el.style['color'] = binding.vaue
})

// 使用自定义指令
// el : 指令所绑定的元素,可以用来直接操作DOM
// binding: 一个对象,包含指令的很多信息
// vnode: VUE编译生成的虚拟节点
<div v-change-color='color'>我要变成绿色</div>
<script>
export default {
    data () {
        return {
            color: 'green'
        }
    }
}</script>
  1. vue.filter过滤器的使用
// html
<div>{{ timedata | time}}</div>

// 有两种方式定义time过滤器,全局定义;当前页面定义
// 全局定义:
Vue.filter('time', (value) => {
    // 逻辑处理
})

// 当前页面定义
filters: () => {
time: (value)=> {
    // 处理逻辑
  }
}
  1. ts应用泛型接口来定义函数
interface ITest<T> {
    (str: T): number 
}

interface IIlength{
    length:number
}
let a: Itest<any>
a =  function<T extends IIlength>(str:T):number{
    return str.length
}