【tracking.js】前端人脸识别框架 tracking.js 活体检测/拍照在 vue2 的使用-程序员宅基地

技术标签: Vue  前端  vue.js  javascript  开发语言  ecmascript  

Tracking.js 是一个独立的JavaScript库,用于跟踪从相机实时收到的数据。跟踪的数据既可以是颜色,也可以是人,也就是说我们可以通过检测到某特定颜色,或者检测一个人体/脸的出现与移动,来触发JavaScript 事件。它是非常易于使用的API,具有数个方法和事件(足够使用了)。

做项目要用到活体检测和拍照的

实现效果

image

活体检测组件

包需到下载 tracking-min.js 和 face-min.js 压缩文件,自行百度。

image

点击查看活体检测组件代码
<template>
  <el-dialog
    :visible="modalVisible"
    width="681px"
    custom-class="compaines-dialog"
    title="Complete Registration"
    class="face-dialog"
    v-dialogDrag
    :close-on-click-modal="false"
    @close="close"
    :append-to-body="true"
  >
    <div class="face" v-loading="faceloading">
      <p class="big-title">Liveness detection</p>
      <!-- <p v-if="steps === 'open' || steps === 'screen'">
        Please place your face into the outlined area,we will take the photo
        automatically!
      </p> -->
      <p v-if="steps === 'open' || steps === 'screen'">
        {
    { faceTips[imgList.length] }}
      </p>
      <div
        v-if="steps === 'screen' && imgList.length > 0"
        style="text-align: center; font-size: 30px"
      >
        <svg-icon :iconClass="faceLocation[imgList.length]" />
      </div>
      <p v-if="steps === 'end'">
        <span v-if="faceOk" class="success">Successful!</span>
        <span v-else class="fail">Failed!</span>
      </p>
      <div class="video-container" v-show="steps !== 'end'">
        <video
          id="video"
          preload
          autoplay
          loop
          muted
          width="295"
          height="345"
          :style="reverse ? 'transform:rotateY(180deg);' : ''"
        ></video>
        <canvas id="canvas" width="295" height="345"></canvas>
        <canvas
          id="shortCut"
          width="295"
          height="345"
          style="opacity: 0"
        ></canvas>
        <canvas
          id="canvas1"
          width="295"
          height="345"
          style="opacity: 0"
        ></canvas>
        <i @click="reverseVideo" v-show="steps === 'screen'">
          <svg-icon iconClass="icon-camera" class="icon-camera" />
        </i>
      </div>
      <div v-show="steps === 'end'" style="text-align: center">
        <img v-if="faceOk" src="@/icons/img/face-success.png" alt="" />
        <img v-else src="@/icons/img/face-fail.png" alt="" />
      </div>
      <div class="btns">
        <div class="add-num w300" @click="start" v-if="steps === 'open'">
          TURN CAMERA ON
        </div>
        <div
          class="retake-btn w300"
          style="margin-right: 0"
          @click="close"
          v-if="steps === 'screen'"
        >
          TURN CAMERA OFF
        </div>
        <div
          class="retake-btn w300"
          @click="tryAgain"
          v-if="steps === 'end' && !faceOk"
        >
          TRY AGAIN
        </div>
        <div
          class="add-num w300"
          @click="finish"
          v-if="steps === 'end' && faceOk"
        >
          FINISH
        </div>
        <p class="tips" v-if="steps === 'screen'">
          (To complete the liveness detection your camera must remain on)
        </p>
      </div>
      <div class="imgs" v-show="false">
        <p>未保存图片</p>

        <p>已保存图片</p>
        <div id="img"></div>
      </div>
    </div>
  </el-dialog>
</template>
<script>
import './tracking-min.js'
import './face-min.js'
import { livenessCheck } from '@modules/kyc/api/system/system'
import debounce from 'lodash/debounce'

export default {
  name: 'testTracking',
  model: {
    prop: 'formData',
    event: 'input',
  },
  props: {
    modalVisible: {
      type: Boolean,
      default: false,
    },
    formData: {
      type: Object | String,
    },
    option: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      saveArray: {},
      imgView: false,
      timer: true,
      steps: 'open',
      imgList: [],
      faceTips: [
        'Please place your face into the outlined area,we will take the photo automatically!',
        'Look to the left',
        'Look to the right',
        'Tilt your head up',
        'Tilt your head down',
      ],
      faceLocation: [
        '',
        'arrow-left',
        'arrow-right',
        'arrow-up',
        'arrow-down',
        '',
      ],
      faceloading: false,
      faceOk: false,
      startPhoto: false,
      reverse: true,
    }
  },
  created() {
    this.start = debounce(this.start, 500)
  },
  methods: {
    // 打开摄像头
    start() {
      window.navigator.mediaDevices
        .getUserMedia({
          video: true,
        })
        .then((stream) => {
          this.openVideo()
        })
        .catch((err) => {
          this.$message.error('Cannot capture user camera')
        })
    },
    openVideo() {
      let that = this
      this.steps = 'screen'
      this.startPhoto = true
      let canvas = document.getElementById('canvas')
      let context = canvas.getContext('2d')
      let tracker = new tracking.ObjectTracker('face')
      tracker.setInitialScale(4)
      tracker.setStepSize(2)
      tracker.setEdgesDensity(0.1)
      this.trackerTask = tracking.track('#video', tracker, { camera: true })
      tracker.on('track', function (event) {
        context.clearRect(0, 0, canvas.width, canvas.height)
        event.data.forEach(function (rect) {
          if (that.reverse) {
            context.strokeRect(
              295 - rect.x - rect.width,
              rect.y,
              rect.width,
              rect.height
            )
          } else {
            context.strokeRect(rect.x, rect.y, rect.width, rect.height)
          }
          context.strokeStyle = '#fff'
          context.fillStyle = '#fff'
          that.saveArray.x = rect.x
          that.saveArray.y = rect.y
          that.saveArray.width = rect.width
          that.saveArray.height = rect.height
        })
      })
      this.timer = true
      this.setPhotoInterval()
    },
    setPhotoInterval() {
      const countFun = () => {
        setTimeout(() => {
          if (this.timer && this.startPhoto) {
            countFun()
            if (this.reverse) {
              if (
                this.saveArray.x < 150 &&
                this.saveArray.y < 150 &&
                this.saveArray.width > 150 &&
                this.saveArray.height > 150
              ) {
                this.getPhoto()
              }
            } else {
              if (
                295 - this.saveArray.x - this.saveArray.width < 150 &&
                this.saveArray.y < 150 &&
                this.saveArray.width > 150 &&
                this.saveArray.height > 150
              ) {
                this.getPhoto()
              }
            }
          }
        }, 1000)
      }
      countFun()
    },
    // 获取人像照片
    getPhoto() {
      try {
        let video = document.getElementById('video')
        let cut = document.getElementById('shortCut')
        let context2 = cut.getContext('2d')
        context2.drawImage(video, 0, 0, 295, 345)
        this.keepImg()
      } catch (error) {}
    },
    // 将canvas转化为图片
    convertCanvasToImage(canvas) {
      let image = new Image()
      image.src = canvas.toDataURL('image/png')
      return image
    },
    //将base64转换为文件,dataurl为base64字符串,filename为文件名(必须带后缀名,如.jpg,.png)
    dataURLtoFile(dataurl, filename) {
      let arr = dataurl.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new File([u8arr], filename, { type: mime })
    },
    // 保存图片
    keepImg() {
      //先保存完整的截图
      let cut = document.getElementById('shortCut')
      let context = cut.getContext('2d')
      //从完整截图里面,截取左侧294x345大小的图片,添加到canvas1里面
      let imgData = context.getImageData(0, 0, 294, 345)
      let canvas1 = document.getElementById('canvas1')
      let context1 = canvas1.getContext('2d')
      context1.putImageData(imgData, 0, 0)
      let img = document.getElementById('img')
      //把canvas1里面的294x345大小的图片保存
      let photoImg = document.createElement('img')
      photoImg.src = this.convertCanvasToImage(canvas1).src
      img.appendChild(photoImg)
      this.imgList.push(
        this.dataURLtoFile(
          this.convertCanvasToImage(canvas1).src,
          `person${this.imgList.length}.jpg`
        )
      )
      this.timer = false
      //捕捉成功后停顿3秒,再捕捉下一张,捕捉5张后上传文件
      if (this.imgList.length === 5) {
        this.sendImages()
      } else {
        setTimeout(() => {
          this.timer = true
          this.setPhotoInterval()
        }, 3000)
      }
    },
    sendImages() {
      let formData = new FormData()
      this.imgList.forEach((item) => {
        formData.append('files', item)
      })
      this.faceloading = true
      formData.append('actionId', this.formData.id)
      livenessCheck(formData, { isUpload: true })
        .then((res) => {
          if (res.success) {
            this.faceOk = true
          } else {
            this.faceOk = false
          }
          this.faceloading = false
          this.steps = 'end'
        })
        .catch((err) => {
          this.openVideo()
          this.faceloading = false
          this.imgList = []
        })
    },
    tryAgain() {
      this.imgList = []
      this.openVideo()
    },
    finish() {
      this.closeFace()
      this.$emit('Liveness check success')
      this.$emit('close', 'success')
    },
    clearCanvas() {
      let c = document.getElementById('canvas')
      let c1 = document.getElementById('canvas1')
      let cxt = c.getContext('2d')
      let cxt1 = c1.getContext('2d')
      cxt.clearRect(0, 0, 581, 436)
      cxt1.clearRect(0, 0, 581, 436)
    },
    closeFace() {
      try {
        this.startPhoto = false
        this.timer = false
        this.imgList = []
        this.clearCanvas()
        // 关闭摄像头
        let video = document.getElementById('video')
        video.srcObject.getTracks()[0].stop()
        // 停止侦测
        this.trackerTask.stop()
      } catch (error) {}
    },
    close() {
      this.closeFace()
      if(this.faceOk){
        this.$emit('close', 'success')
      }else{
        this.$emit('close')
      }
    },
    reverseVideo() {
      this.reverse = !this.reverse
    },
  },
  watch: {},
}
</script>
<style lang="scss">
@import './face.scss';
</style>

拍照组件代码

点击查看拍照组件代码
<template>
  <el-dialog
    :visible="modalVisible"
    width="681px"
    custom-class="compaines-dialog"
    title="Complete Registration"
    class="face-dialog"
    @close="close"
  >
    <div class="face" v-loading="faceloading">
      <p class="big-title" v-if="steps !== 'save'">Take a profile picture</p>
      <p class="big-title success" v-if="steps === 'save'">Complete!</p>
      <p v-if="steps === 'open'">
        Please turn on your camera and center your face within the below guides.
      </p>
      <p v-if="steps === 'take'">Position your face within the outlined area,click the button and the image will be saved as your profile picture!</p>
      <p v-if="steps === 'save'">
        Happy with your picture? If not, you can take another one.
      </p>
      <div class="video-container">
        <video
          id="video"
          preload
          autoplay
          loop
          muted
          width="295"
          height="345"
          :style="reverse ? 'transform:rotateY(180deg);' : ''"
        ></video>
        <canvas id="canvas" width="295" height="345"></canvas>
        <canvas id="shortCut" v-show="false"></canvas>
        <img :src="imgSrc" alt="" v-show="steps === 'save'" />
        <i @click="reverseVideo" v-show="steps==='take'">
          <svg-icon iconClass="icon-camera" class="icon-camera" />
        </i>
      </div>
      <div class="btns">
        
        <div class="add-num w300" @click="start" v-if="steps === 'open'">
          TURN CAMERA ON
        </div>
        <div class="add-num w300" @click="getPhoto" v-if="steps === 'take'">
          SAVE THE IMAGE
        </div>

        <div class="retake-btn" @click="openVideo" v-if="steps === 'save'">
          RETAKE
        </div>
        <div class="add-num" @click="keepImg" v-if="steps === 'save'">
          USE PICTURE
        </div>
        <p class="tips" v-if="steps === 'take'">
          (To take a profile picture your camera must remain on)
        </p>
        <div class="imgs" v-show="false">
          <canvas
            id="canvas1"
            width="295"
            height="345"
            v-show="steps === 'save'"
          ></canvas>

          <p>save images</p>
          <div id="img"></div>
        </div>
      </div>
    </div>
  </el-dialog>
</template>
<script>
import './tracking-min.js'
import './face-min.js'
import { individualPhoto } from '@modules/kyc/api/system/system'
import debounce from 'lodash/debounce'
export default {
  name: 'testTracking',
  model: {
    prop: 'formData',
    event: 'input',
  },
  props: {
    modalVisible: {
      type: Boolean,
      default: false,
    },
    formData: {
      type: Object,
    },
    option: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      saveArray: {},
      imgView: false,
      timer: null,
      steps: 'open',
      imageFile: {},
      faceloading: false,
      btnLoading: false,
      imgSrc: '',
      reverse: true,
    }
  },
  created() {
    this.start = debounce(this.start, 500)
  },
  methods: {
    // 打开摄像头
    start() {
      window.navigator.mediaDevices
        .getUserMedia({
          video: true,
        })
        .then((stream) => {
          this.openVideo()
        })
        .catch((err) => {
          this.$message.error('Cannot capture user camera')
        })
    },
    openVideo() {
      let that = this
      this.steps = 'take'
      let saveArray = {}
      let canvas = document.getElementById('canvas')
      let context = canvas.getContext('2d')
      let tracker = new tracking.ObjectTracker('face')
      tracker.setInitialScale(4)
      tracker.setStepSize(1.5)
      tracker.setEdgesDensity(0.1)
      this.imgSrc = ''
      this.trackerTask = tracking.track('#video', tracker, { camera: true })
      tracker.on('track', function (event) {
        context.clearRect(0, 0, canvas.width, canvas.height)
        event.data.forEach(function (rect) {
          if (that.reverse) {
            context.strokeRect(
              295 - rect.x - rect.width,
              rect.y,
              rect.width,
              rect.height
            )
          } else {
            context.strokeRect(rect.x, rect.y, rect.width, rect.height)
          }
          context.strokeStyle = '#fff'
          context.fillStyle = '#fff'
          saveArray.x = rect.x
          saveArray.y = rect.y
          saveArray.width = rect.width
          saveArray.height = rect.height
        })
      })
    },
    // 获取人像照片
    getPhoto() {
      let video = document.getElementById('video')
      let can = document.getElementById('shortCut')
      can.width = video.videoWidth
      can.height = video.videoHeight
      let context2 = can.getContext('2d')
      if (this.reverse) {
        context2.scale(-1, 1)
        context2.translate(-video.videoWidth, 0)
      }
      context2.drawImage(video, 0, 0, video.videoWidth, video.videoHeight)
      this.imgSrc = this.convertCanvasToImage(can).src
      this.steps = 'save'
      this.clearCanvas()
      // 停止侦测
      this.trackerTask.stop()
      // 关闭摄像头
      video.srcObject.getTracks()[0].stop()
      // this.imgView = true
    },
    // 截屏
    screenshot() {
      this.getPhoto()
    },
    // 将canvas转化为图片
    convertCanvasToImage(canvas) {
      let image = new Image()
      image.src = canvas.toDataURL('image/png')
      return image
    },
    //将base64转换为文件,dataurl为base64字符串,filename为文件名(必须带后缀名,如.jpg,.png)
    dataURLtoFile(dataurl, filename) {
      let arr = dataurl.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new File([u8arr], filename, { type: mime })
    },
    // 保存图片
    keepImg() {
      // let can = document.getElementById('shortCut')
      // let context = can.getContext('2d')
      // let imgData = context.getImageData(0, 0, 294, 345)
      // let canvas1 = document.getElementById('canvas1')
      // let context1 = canvas1.getContext('2d')
      // context1.putImageData(imgData, 0, 0)
      // let img = document.getElementById('img')
      // let photoImg = document.createElement('img')
      // photoImg.src = this.convertCanvasToImage(canvas1).src
      // img.appendChild(photoImg)

      // this.imageFile = this.dataURLtoFile(
      //   this.convertCanvasToImage(canvas1).src,
      //   'person.jpg'
      // )
      this.imageFile = this.dataURLtoFile(this.imgSrc, 'person.jpg')
      let formData = new FormData()
      formData.append('file', this.imageFile)
      this.faceloading = true
      individualPhoto(formData, { isUpload: true })
        .then((res) => {
          if (res.success) {
            this.faceloading = false
            this.closeFace()
            this.$emit('close', 'success')
            this.$store
              .dispatch('GetBusinessUser')
              .then((res) => {})
              .catch((err) => {})
          } else {
            this.openVideo()
            this.faceloading = false
            this.imgList = []
          }
        })
        .catch((err) => {
          this.openVideo()
          this.faceloading = false
          this.imgList = []
        })
    },
    clearCanvas() {
      let c = document.getElementById('canvas')
      let c1 = document.getElementById('canvas1')
      let cxt = c.getContext('2d')
      let cxt1 = c1.getContext('2d')
      cxt.clearRect(0, 0, 581, 436)
      cxt1.clearRect(0, 0, 581, 436)
    },
    closeFace() {
      try {
        this.steps = 'open'
        this.clearCanvas()
        // 关闭摄像头
        let video = document.getElementById('video')
        video.srcObject.getTracks()[0].stop()
        // 停止侦测
        this.trackerTask.stop()
      } catch (error) {}
    },
    close() {
      this.closeFace()
      this.$emit('close')
    },
    reverseVideo() {
      this.reverse = !this.reverse
    },
  },
  watch: {
    faceView(v) {
      if (v == false) {
        this.closeFace()
      }
    },
  },
  destroyed() {
    // clearInterval(this.timer)
  },
}
</script>
<style lang="scss">
@import './face.scss';
</style>

样式文件

点击查看样式代码
.face {
  p {
    text-align: center;
    font-size: 16px;
    color: #333333;
  }

  .big-title {
    margin-top: 36PX;
    font-size: 24PX;
    font-weight: 600;
    color: #030229;
    text-align: center;

  }

  .success {
    color: #47BFAF;
  }

  .fail {
    color: #C81223;
  }

  .video-container {
    background: url(~@/icons/img/face.png);
    background-size: cover;
    position: relative;
    width: 295PX;
    height: 345PX;
    border-radius: 4%;
    overflow: hidden;
    margin: 0 auto;
    margin-top: 34PX;

    img,
    video,
    #canvas,
    #shortCut,
    #canvas1 {
      position: absolute;
    }

    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    video{
      object-fit: cover;
    }
    .icon-camera{
      position: absolute;
      font-size: 32px;
      bottom: 20px;
      right: 20px;
      cursor: pointer;
    }
  }

  .btns {
    padding: 10PX;
    text-align: center;
    margin-top: 6PX;

    .tips {
      font-size: 14PX;
      color: #666;
      margin-top: 16PX;

      line-height: 24PX;
    }
  }

  .imgs {
    padding: 10PX;

    p {
      font-size: 16PX;
    }
  }

  .add-num {
    display: inline-block;
    font-size: 16PX;
    padding: 12PX 14PX;
    margin-right: 4PX;
    border-radius: 4PX;
    color: #ffffff;
    background: #47BFAF;
    cursor: pointer;
  }

  .retake-btn {
    width: 145PX;
    display: inline-block;
    font-size: 16PX;
    padding: 12PX 14PX;
    margin-right: 24PX;
    border-radius: 4PX;
    color: #828282;
    background: #E7E7E7;
    cursor: pointer;
  }

  .w300 {
    width: 300PX;
  }
}

.face-dialog {
  .el-dialog__body {
    padding-top: 0;
    padding-bottom: 15px;
  }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/IAIPython/article/details/130571231

智能推荐

2021-09-15 WPF上位机 15-属性绑定(数据格式化)_wpf 自定义属性绑定 格式化 实现-程序员宅基地

文章浏览阅读3.2w次。<Window x:Class="Zhaoxi.BindingStudy.DataFormatStudy.DataFormatStudyWin" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.._wpf 自定义属性绑定 格式化 实现

[常用办公软件] wps怎么自动生成目录?wps自动生成目录的设置教程_wps目录自动生成-程序员宅基地

文章浏览阅读1.1w次,点赞3次,收藏5次。转载请说明来源于"厦门SEO"本文地址:http://www.96096.cc/Article/160880.html常用办公软件  WPS Office是由金山软件股份有限公司开发的一款针对个人永久免费的办公软件,在我们的日常生活和工作中,WPS Office比起微软Microsoft Office来说在文字上的处理会更深入国人用户的人心,熟悉操作WPS的办公小技巧,能够更高效的提高我们的工作效率,今天小编要为大家分享的是WPS怎么自动生成目录?快来一起看看WPS自动生成目录的设置教程吧。_wps目录自动生成

web项目-程序员宅基地

文章浏览阅读7.4k次,点赞2次,收藏19次。web项目是指服务端部署在服务器上,客户端使用浏览器通过网络传输进行访问获取数据的项目。通常我们看见的应用页面网站等等都可以称之为web项目。 在web项目的开发中可分为web前端开发和web后端开发 web前端:即是客户端能看得见碰得着得东西。包括Web页面结构、页面样式外观以及Web层面得交互展现。 前端特点:页面视觉效果良好(客户第一)、Web页面交互流畅(..._web项目

关于java操作excel导入导出三种方式_java导出excel的三种方法-程序员宅基地

文章浏览阅读5.6k次,点赞8次,收藏67次。java操作关于导入导出Excel的多种方式_java导出excel的三种方法

Windows系统环境变量path详解_windows path-程序员宅基地

文章浏览阅读1.1w次,点赞10次,收藏21次。Windows path系统变量编辑_windows path

Hadoop基础教程-第13章 源码编译(13.2 Hadoop2.7.3源码编译)_hadoop2.7.3-src源码下载-程序员宅基地

文章浏览阅读512次。第13章 源码编译13.2 Hadoop2.7.3源码编译13.2.1下载Hadoop源码包(1)到官网http://hadoop.apache.org/releases.html下载2.7.3的source源码包(2)解压缩tar -zxvf hadoop-2.7.3-src.tar.gz -C /opt1(3)打开解压目录下的BUILDING.txt,编译过程和需要的软件其实就是根据这个文档里..._hadoop2.7.3-src源码下载

随便推点

【智能排班系统】基于AOP和自定义注解实现接口幂等性-程序员宅基地

文章浏览阅读884次。使用多种方式实现接口幂等性,通过定义注解方便对方法进行幂等性控制

SpringBoot整合Swagger2 详解_springboot swagger2 开关-程序员宅基地

文章浏览阅读324次。SpringBoot、Swagger2 整合详解_springboot swagger2 开关

spring boot 项目报错 java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized_springboot项目里面报错 the server time zone value ' й-程序员宅基地

文章浏览阅读2.8w次,点赞96次,收藏115次。报错说是时区不对因为mysql-connection-java版本导致时区的问题。pom.xml:控制台报错信息:java.sql.SQLException: The server time zone value ‘�й���׼ʱ��’ is unrecognized or represents more than one time zone. You must configure ei..._springboot项目里面报错 the server time zone value ' й

最全Android Kotlin 学习路线(Kotlin 从入门、进阶到实战)_kotlin学习-程序员宅基地

文章浏览阅读4.2k次。Kotlin 是由 jetBrains 开发的一门现代多平台应用的静态编程语言,Kotlin 代码即可以编译成 Java 字节码,又可以编译成 JavaScript,Kotlin 是开源的,源码在这。Kotlin 包含了大量的语法糖,在编码的时候,会大大的简化我们的代码量及工作效率。且相比传统的 Java 语言,Kotlin 种大量的简写,可以减少很多用Java 必须要写的样板代码,减少大量的 if…else 等嵌套,减少大量接口的实现,代码结构也会更加清晰。_kotlin学习

【前端素材】推荐优质新鲜绿色蔬菜商城网站设计Harmic平台模板(附源码)-程序员宅基地

文章浏览阅读753次,点赞30次,收藏21次。在线绿色新鲜果蔬商店网站是指一个专门销售新鲜、绿色、有机水果和蔬菜的电子商务平台。这类网站旨在为消费者提供方便、快捷的购买渠道,同时确保他们能够购买到高质量、新鲜的产品。

elementui表格添加fixed之后样式异常_element table fixed 样式异常-程序员宅基地

文章浏览阅读1k次。最近写项目碰到一个bug 大概就是一个表格组件两个页面都会使用 组件中表格的某些列就用v-if控制了 表格的首尾列都用了fixed 然后就发生了bug 如下图 具体原因不明看过很多网上的办法 有在fixed的列绑定key的 也有使用doLayout()的 测了都没用 最后在一个前端交流群里一位大佬给出的办法 实测有效.el-table__header, .el-table__body, .el-table__footer { width: 100%; tab_element table fixed 样式异常