2021-09-24 日积月累(1)

一、将新添加的字体图标引入项目

1.方案一:

(1) 打开阿里图标库中,字体图标的css代码。
(2) 复制css代码,在项目的css文件夹里新建一个新的css文件,将代码粘贴。
(3) 在 index.css 文件中用 import 关键字 引入新建的css文件。

2.引入阿里图标库的字体图标(推荐!)

(1)先在阿里图标库的项目中点击 font-class 模式,再点击下载至本地,解压后,将所有文件 替换 到项目中。
(2)在main.js中引入字体图标:import './assets/font/iconfont';
(3)使用时,直接在页面中 <i class="iconfont xxx" />。其中,前缀iconfont 是UI在阿里图标库创建项目时自定义的前缀名。

注意:下面几点并非通用属性
(4)使用svg格式的图标:

          <svg class="icon" aria-hidden="true">
            <use xlink:href={`#${index === roleData.activeIndex ? 'iconjingbanren' : 'iconjiaose'}`}/>
          </svg>

(5)用户运营平台,没有下载图标依赖,使用element-plus组件库自带的图标:

  <el-popconfirm icon="el-icon-house">
    <template #reference>
      <el-button>点击</el-button>
    </template>
  </el-popconfirm>

(6)运营平台使用的element-plus依赖版本太低,评分组件的 void-icon 属性不起作用,使用 void-icon-class="el-cion-house"代替。

3.下载图标依赖,使用element-plus提供的图标

(1)npm install @element-plus/icons-vue
(2)main.js中引入

import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

(3)这是svg图标,需要用import 引入。

import { StarFilled } from '@element-plus/icons-vue'

二、滚动条样式

1.方案一

(1)用scss语法 @mixin 定义一个 混入 的样式。

// 滚动条样式
@mixin scroll-style {
  &::-webkit-scrollbar {
    width: 4px;
  }
  &::-webkit-scrollbar-track {
    width: 4px;
    background-color: #FFF;
  }
  &::-webkit-scrollbar-thumb {
    background-color: #B2B8C9;
    background-clip: padding-box;
    min-height: 49px;
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    border-radius: 5px;
  }
  &::-webkit-scrollbar-thumb:hover {
    background-color: #B2B8C9;
  }
  &::-webkit-scrollbar-track:hover {
    background-color: #fff;
  }
}

(2)用 @include 为真正滚动的元素添加混入样式。

        .el-table__body-wrapper{
          @include scroll-style;
        }

(3)名称 "scroll-style" 随意自定义;& 符号取值是真正添加样式的元素 .el-table__body-wrapper,而非scroll-style

2.方案二

(1)less语法中,在公共样式中定义滚动条样式

// 滚动条
.yc-scoll(){
  &::-webkit-scrollbar {
    width:4px;
  }

  &::-webkit-scrollbar-track {
    width: 4px;
    background-color:#FFF;
  }

  &::-webkit-scrollbar-thumb {
    background-color:#B2B8C9;
    background-clip:padding-box;
    min-height:49px;
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    border-radius:5px;
  }

  &::-webkit-scrollbar-thumb:hover{
    background-color:#B2B8C9;
  }
  &::-webkit-scrollbar-track:hover{
    background-color:#E2E4E9;
  }
}

(2)在需要用到的页面引入公共样式,并为真正滚动的元素添加样式

<style lang="less">
  @import '@/assets/style/yc.less';
  .box{
    .yc-scoll();
  }
</style>

三、复制

方案一:点击按钮,将某些内容复制到电脑的剪切板中。

      //HTML按钮
      <el-button type="text" @click="copyBtn(row)">复制链接</el-button>

      // 复制链接的方法
      copyBtn() {
        var input = document.createElement('input') // 直接构建input
        input.value = '123456789' // 设置内容
        document.body.appendChild(input) // 添加临时实例
        input.select() // 选择实例内容
        document.execCommand('Copy') // 执行复制
        this.$baseMessage(
          '复制成功',
          'success',
          false,
          'vab-hey-message-success'
        )
        document.body.removeChild(input) // 删除临时实例
      },

方案二:使用插件 copy-to-clipboard

import copy from 'copy-to-clipboard';

const str = 'abc'
copy(str)

四、下载图片到本地

https://www.jianshu.com/p/cdbd87541b30

downloadIamge(imgsrc, name) {//下载图片地址和图片名
  var image = new Image();
  // 解决跨域 Canvas 污染问题
  image.setAttribute("crossOrigin", "anonymous");
  image.onload = function() {
    var canvas = document.createElement("canvas");
    canvas.width = image.width;
    canvas.height = image.height;
    var context = canvas.getContext("2d");
    context.drawImage(image, 0, 0, image.width, image.height);
    var url = canvas.toDataURL("image/png"); //得到图片的base64编码数据
  
    var a = document.createElement("a"); // 生成一个a元素
    var event = new MouseEvent("click"); // 创建一个单击事件
    a.download = name || "photo"; // 设置图片名称
    a.href = url; // 将生成的URL设置为a.href属性
    a.dispatchEvent(event); // 触发a的单击事件
  };
  image.src = imgsrc;
},

五、下载 后端处理好的.xlsx文件到本地

1. 如果后端传的是base64格式的内容,首先将base64转换为 blob对象。
    // base64转换blob对象
    toBLob(data) {
      const raw = window.atob(data);
      const rawLength = raw.length;
      const uInt8Array = new Uint8Array(rawLength);
      for (let i = 0; i < rawLength; i += 1) {
        uInt8Array[i] = raw.charCodeAt(i);
      }
      return new Blob([uInt8Array]);
    },

转换之后应该是一个blob对象


1.PNG
2. 根据blob对象生成.xlsx表格并自动下载。入参res就是blob对象。
    // 下载.xlsx文件
    fileDownload(res) {
      // 创建blob对象
      const blod = new Blob([res], {
        type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      });
      const fileName = `${this.activeCustonerName}_${this.formatTime(new Date())}`; // 这里的文件名需要根据实际情况修改!!!
      // 创建a标签
      const elink = document.createElement('a');
      elink.style.display = 'none';
      elink.setAttribute('download', fileName);
      elink.href = URL.createObjectURL(blod);
      document.body.appendChild(elink);
      elink.click();
      window.URL.revokeObjectURL(elink.href); // 释放URL 对象
      document.body.removeChild(elink); // 移除a标签
    },
3. 如果后端返的不是base64,而是直接是文档流,则接口请求时需带上responseType: 'blob'。
  // 模板下载
  downloadTemplate: (orgId) => axios.get(`/api/admin/organization/detail/import/template/${orgId}`, { responseType: 'blob' }),

从接口获取的值会被直接转化为Blob对象。

六、keep-alive结合vue3

https://juejin.cn/post/6844903846901186574

1.路由占位符的配置

(1)对需要被缓存的列表页 所对应的 <router-view /> 进行修改。
(2)web端项目,通常是在Home组件中,因为Home组件包括了左侧菜单和 右侧路由页面。
(3)将路由占位符修改为:

          <router-view v-slot="{ Component }">
            <keep-alive :include="include">
              <component :is="Component" />
            </keep-alive>
          </router-view>

(4)这是vue3写法,其中include属性可以是 数组 或配置函数。
(5)在Home组件中 监听 $route 的路由变化,实时修改include数组的值,从而决定哪些组件被缓存。

    data() {
      return {
        include: [],
      };
    },
    watch: {
      $route: {
        handler: function (to, from) {
          // 如果 要 to(进入) 的页面是需要 keepAlive 缓存的,把 name push 进 include数组
          if (to?.meta.keepAlive) {
            !this.include.includes(to.name) && this.include.push(to.name);
          }
          // 如果 要 form(离开) 的页面是 keepAlive缓存的,
          // 再根据 deepth 来判断是前进还是后退
          // 如果是后退
          if (from?.meta.keepAlive && to.meta.deepth <= from.meta.deepth) {
            const index = this.include.indexOf(from.name);
            index !== -1 && this.include.splice(index, 1);
          }
        },
        immediate: true,
      },
    },
2.路由对象的配置

(1)给每个 渲染到Home组件的路由对象,添加meta属性。
(2)需要被缓存的列表页路由,keepAlive设置为 true,deepth设置为 中间值。

{
    path: '/grabOverdue',
    name: 'GrabOverdue',
    title: '列表页',
    component: () => import('@/views/survey-web-grab//overdue/index.vue'),
    meta: {
      keepAlive: true,
      deepth: 1,
    },
  },

(3)详情页路由,keepAlive设置为 false,deepth设置为 最大值。

{
    path: '/surveyMySurvey/&detail/:id',
    name: 'SurveyMySurveyDetail',
    title: '详情页',
    component: () => import('@/views/web-data-survey/survey-follow/index.vue'),
    meta: {
      keepAlive: false,
      deepth: 2,
    },
  },

(4)其余同级别路由,keepAlive设置为 false,deepth设置为 最小值。

{
    path: '/grabTaskPool',
    name: 'GrabTaskPool',
    title: '其他页面',
    component: () => import('@/views/survey-web-grab/task-pool/index.vue'),
    meta: {
      keepAlive: false,
      deepth: 0,
    },
  },
3.被缓存的 列表页 的配置

(1)被缓存的列表页对应的路由组件 必须有name属性,属性值和 路由对象的name属性 保持一致。

1.PNG

(2)从详情页回到列表页时,使用的是被缓存的页面,所以不会触发onMounted等钩子函数。
(3)如果想在回来时请求某些接口数据,可以在 onActivated() 钩子函数中执行。
(4)onActivated()钩子函数只有被缓存的页面才会调用。并且,无论是直接刷新页面,或是从详情页回到列表页时,都会调用。最好和onMounted一起使用,避免出现初始化时没调接口的问题。

4.详情页的配置

从详情页回到列表页,使用 $router.push() 方法。

七、请求截断-axios

在axios中是通过 axios.CancelToken.source() 方法取消请求。https://blog.csdn.net/wopelo/article/details/79802585
(1)在配置axios的js文件中,引入CancekToken;
(2)自定义数组AxiosRequestArr, 用于存放 可取消的请求的配置项;
(3)在请求拦截器中将所有 cancel属性为true 的请求的配置项,放到AxiosRequestArr 数组中;
(4)定义方法CancelRequest,取消所有请求清空AxiosRequestArr 数组;
(5)在请求方法页面中,给每个需要取消请求的方法,添加 cancel:true属性,并在 发送本次请求前取消上次的请求。

import axios from 'axios';
//(1)引入CancekToken
const CancelToken = axios.CancelToken;
//(2)定义存放 可取消请求的配置项 的数组
const AxiosRequestArr: any = [];

axios.interceptors.request.use(
  (config) => {
     const dynamicConfig = config;
    //(3)向数组中添加 可取消请求的配置项
    if (dynamicConfig.cancel) {
      config.cancelToken = new CancelToken((cancel) => {
        AxiosRequestArr.push({
          type: dynamicConfig.url,
          cancel,
        });
      });
    }
    return config;
  },
  (err) => {}
);

//(4)定义方法:取消所有请求并清空数组
const CancelRequest = () => {
  AxiosRequestArr.forEach((item: any, index: any) => {
    item.cancel();
    delete AxiosRequestArr[index];
  });
};
export { CancelRequest };// 导出方法供请求方法页面使用
import axios from '../index';
import { CancelRequest } from '../index'; //导入自定义方法,用于取消所有请求

  myTableList: (tag: number, params: any) => {
    //(5)发起本次请求前,先取消上次请求;将这个请求标识为可取消的请求
    CancelRequest();
    return axios.post(`/api/research/main/my/list/${tag}`, clearEmpty(params), { cancel: true });
  },

注意:同一页面内被截断的请求应当在 同一步骤发起,因为每次请求前,取消了 全部未完成的请求。 (尝试在本次请求前,只中断同类型请求)

八、样式

1.display:table-row样式

父元素设置这个样式后,内外边距都会失效。 border-collapse:separate; border-spacing:10px 50px; 来设置每个单元格之间的距离。

2.absolute定位

虽然absolute定位的子元素 脱离了标准流,但是依然可以 让父元素具有滚动条:
当父元素有高度,absolute定位的子元素的高度更大时,可以给父元素设置overflow:auto,父元素具有滚动条。

3.scss

在scss中用global{}修改antd样式;也可以在某个类名里加.abc{:global{xxx}},global中的内容只在这个类名里生效。

九、方法

1.Object.assing()

将源对象(sourse)的属性添加到目标对象(target)中

Object(target,sourse)

const target = {a:1, b:2}
const sourse = {a:10, c:3}
Object.assign(target,sourse)
console.log(target) // {a:10, b:2, c:3}
console.log(sourse) // {a:10, c:3}

十、项目引入mock

1.基于Easy Mock平台已经配置好的内容。(不是我能搞定的)
2.在平台上创建新的项目:http://172.18.255.8:9999/
1.PNG

2.PNG
3.项目中,对需要mock的接口进行改造。如果页面中传递了mock=true参数,则接口地址在最前面加上"/mock"。
3.PNG
4.vue.confing.js中添加对于mock接口的本地反向代理。

① 一个项目可以添加多个反向代理,比如对 "/api" 的反向代理、对 "/mock/api" 的反向代理。
② 接口实际请求用的 基础地址是 http://localhost:8080,然后拼接上"/mock/api/xxx"。
"/mock/api"配置对象 就是要把接口地址中,"/mock/api"之前的 基础地址 替换为 "target"属性值。
④ target属性值就是 真实的基础地址。对于"/api"配置对象而言,要请求的是 后端基础地址,对于"/mock/api"而言要请求的是 mock基础地址。

4.PNG

十一、自定义图片上传-粘贴上传

1.Html元素

(1)根据base64格式的图片数组,循环渲染已上传的图片
(2)一个input框,通过监听input框的paste事件,获取到粘贴板中的图片信息。

<div class="upload-pic-wrapper">
    // 1.pictures数组用于存放base64格式的图片数据,base64格式的图片赋值给img标签,可以直接显示。
    <div class="pic" v-for="(it, index) in pictures" :key="index">
      <img :src="it" alt="" />
     </div>
     //  2.paste事件,获取到粘贴的内容;change事件,清空input框输入的内容,只用来粘贴上传;
     <a-input
        v-model:value="uselessValue"
        @paste="paste"
        @change="uselessValue = ''"
     />
</div>
2. js方法

(1)通过getAsFile方法,将粘贴板中的图片信息转为file文件。
(2)通过自定义方法,将file文件转为base64格式,用于本地展示。

      // 图片转base64,入参是file格式的数据
      const getBase64 = (file) => {
        return new Promise((resolve, reject) => {
          const reader = new FileReader();
          reader.readAsDataURL(file);
          reader.onload = () => resolve(reader.result);
          reader.onerror = (error) => reject(error);
        });
      };
1.PNG

(3)通过formData构造函数,将file文件转为formData格式,用于接口传参。

      // 图片粘贴上传
      const paste = async (e) => {
        const it = e.clipboardData.items[0]; // 1.获取剪切板中的有效数据
        const pasteFile = it.getAsFile(); // 2.生成file文件
        // 3.对上传的文件进行大小和类型限制
        if (it.kind !== 'file' || !fileType.includes(it.type)) {
          proxy.$message.error('请上传后缀为.png或.jpeg图片文件');
          return;
        }
        if (pasteFile.size / 1048576 > 2) {
          proxy.$message.error('文件不能超过2M');
          return;
        }
        const pic = await getBase64(pasteFile); // 4.根据file文件生成base64图片,用于本地展示
        // 5.根据file文件生成formData格式的数据,用于接口请求
        const form = new FormData();
        form.append('file', pasteFile);
        // 6.调用上传图片的接口
        ......  
      };
3.文件和普通参数同时传递

如果接口入参包含了文件和其他参数,则需要把所有参数都放到formData对象中。

const form = new FormData() // 创建formData格式的对象===>form,这就是要传给后端的数据
form.append('file',file) // 向form中添加文件
form.appdne('id',id) // 向form中添加id

十二、vue3中h函数的使用

1.引入h函数

import {h} from 'vue'

2.h函数接收三个参数:节点类型、配置对象、节点内容。
① 节点类型:div、span等任意标签。
② 配置对象:可以是class类名、其他属性、方法等。
③ 节点内容:可以是字符串;也可以是一个 数组,数组中就是 子节点。

const btnClick = () => {
  const myDiv = h('div', [
                h('span', '存在'),
                h(
                  'span',
                  { 
                    class: 'yc-verify-yes',
                    onClick:()=>{}
                  }, 
                  '审核不通过'),
                h('span', '的映射,点击确定,将退回至处理人修改'),
              ])
  Modal.confirm({
     content: myDiv
  });

}

十三、动态添加的表单实现校验

一组可以增加或删除的卡片,每个卡片中有多个表单元素,此时也可以用一个<Form></Form>标签包裹所有卡片。
点击增加按钮,一次增加一个下拉选择框、一个input框

const state = reactive({
  form:{
    dataLinks:[{dataType:1, url: ''},{dataType:2, url: ''}]
  }
})
        <el-form>
          <div v-for="(item,index) in form.dataLinks" :key="index" class="myUrl"> // 一、循环的数组:form.dataLinks
            <el-form-item
              label="数据类型:"
              label-width="81px"
              :prop="`dataLinks.${index}.dataType`"  // 二、绑定的props:dataLinks数组中第index个元素的dataType属性
              :rules="{ required: true, message: '请选择数据类型', trigger: 'change' }" // 三、绑定单独的校验规则
            >
              <el-select v-model="item.dataType" style="width: 120px;margin-right: 20px"> // 四、下拉框绑定item中的dataType属性
                <el-option
                  v-for="it in dataTypeOptions"
                  :key="it.value"
                  :label="it.label"
                  :value="it.value"
                />
              </el-select>
            </el-form-item>
            <el-form-item
              label="数据链接:"
              :prop="`dataLinks.${index}.url`"  // 二、绑定的props:dataLinks数组中第index个元素的url属性
              :rules="{ required: true, message: '请输入数据链接并以http开头', trigger: 'change' }" // 三、绑定单独的校验规则
            >
              <el-input
                v-model="item.url"  // 四、输入框绑定item中的url属性
                @blur="limitUrl(index)"
                :maxlength="1000"
                placeholder="请输入数据链接并以http开头"
                style="width:440px;margin-right: 30px"
              />
            </el-form-item>
          </div>
        </el-form>

十四、计算文本长度

/**
 * 计算文本宽度
 * @param {*} text String 文案
 * @param {*} options Object 样式
 * @returns 宽度 Number
 */
const getWordLength = (text, options = {}) => {
  const { size = 14, family = 'Microsoft YaHei', weight = 400 } = options;
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  ctx.font = `${weight} ${size}px ${family}`;
  const metrics = ctx.measureText(text);
  const actual = Math.abs(metrics.actualBoundingBoxLeft) + Math.abs(metrics.actualBoundingBoxRight);
  return Math.max(metrics.width, actual);
};

十五、下载文件时让用户选择下载路径

1.直接用a链接的 download 下载属性即可。

<a href="#" download >下载</a>

2.不嫌麻烦可以用这个:
① 点击按钮调用接口,后端返回blob文件流,前端将blob文件流下载到本地。详情见 第五项
② 如果想要点击按钮后打开文件选择框,让用户选择下载路径的话,就要这样:⬇⬇⬇

    // 监听按钮点击事件
   async handleClick() {
      const handle = await this.fileSaveAs("Hello File Access Api") // 1.打开文件选择框。参数只是个提示信息
      await this.writeFile(handle, this.downloadFile) // 2.向文件中写入数据。
    },
    // 1.自定义方法:调用showSaveFilePicker方法打开文件选择框并传入相关配置项。(会生成一个空文件)
    async fileSaveAs(description) {
      const opts = {
        types: [
          {
            description: description,
            accept: { 
            "text/plain": [".txt"], 
            "application/octet-stream":[".bin"],
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":[".xlsx"] ,
            "application/vnd.ms-excel":[".xls"],
            "application/msword":[".doc"],
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document":[".docx"],
            "application/vnd.ms-powerpoint":[".ppt"],
            },
          },
        ],
      };
      return await window.showSaveFilePicker(opts);
    },
    // 2.在第一步的基础上向空文件中写入数据。(contents可以是文件流)
    async writeFile(fileHandle,contents) {
      const writable = await fileHandle.createWritable();
      await writable.write(contents);
      await writable.close();
    },

很详细的讲解:https://juejin.cn/post/7057802776805392392

十六、前端实现导出excel表格

三种方式:https://juejin.cn/post/7280006996572586043
选择第二种vue-json-excel:https://blog.csdn.net/m0_59023970/article/details/123427008

十七、浏览器调用摄像头

核心方法:window.navigator.mediaDevices.getUserMedia()
1.实例演示:https://developer.mozilla.org/zh-CN/docs/Web/API/Media_Capture_and_Streams_API/Taking_still_photos#%E6%BC%94%E7%A4%BA

2.语法规范:https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia#%E8%A7%84%E8%8C%83

  <div class="contentarea">
    <div class="camera">
      <video id="video">视频流目前不可用。</video>
      <button id="startbutton"></button>
    </div>
    <canvas id="canvas"> </canvas>
  </div>

<script>
    const width = 320; // 视频和画布的宽
    let height = 0; // 视频和画布的高
    let video = null;
    let canvas = null;
    let photo = null;
    let startbutton = null; // 拍照按钮

    // 如果当前处于 iframe 内,则隐藏内容并显示一个按钮,用于返回浏览器窗口
    function showViewLiveResultButton() {
      if (window.self !== window.top) {
        document.querySelector(".contentarea").remove();
        const button = document.createElement("button");
        button.textContent = "返回浏览器窗口";
        document.body.append(button);
        button.addEventListener("click", () => window.open(location.href));
        return true;
      }
      return false;
    }

    // 将视频流初始化
    function startup() {
      if (showViewLiveResultButton()) {
        return;
      }
      video = document.getElementById("video");
      canvas = document.getElementById("canvas");
      photo = document.getElementById("photo");
      startbutton = document.getElementById("startbutton");
      if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        // 先关闭所有摄像头
        if(window.stream){
          window.stream.getTracks().forEach(track => {
            track.stop();
          })
        }
        // 获取视频流
        navigator.mediaDevices
        .getUserMedia({ video: { facingMode: { exact: "environment" } }, audio: false })
        .then((stream) => {
          video.srcObject = stream;
          video.play();
        })
        .catch((err) => {
          console.error("未找到可用摄像头:", err);
          alert("未找到可用摄像头:", err)
        });
      }else {
        console.log("视频流初始化错误,未找到mediaDevices属性,navigator: ", navigator);
        alert(`视频流初始化错误`)
      }
      // 视频流加载完成事件监听
      video.addEventListener(
        "canplay",
        (ev) => {
          $(".contentarea").removeClass("hide") // 显示video视频
          height = video.videoHeight / (video.videoWidth / width);
          if (isNaN(height)) {
            height = width / (4 / 3);
          }
          video.setAttribute("width", width);
          video.setAttribute("height", height);
          canvas.setAttribute("width", width);
          canvas.setAttribute("height", height);
        },
        false,
      );
      // 拍照按钮点击事件
      startbutton.addEventListener( "click",  (ev) => {
          takepicture();
          ev.preventDefault();
        },
        false,
      );
      clearphoto();
    }

    // 清空上次拍照的内容并将新照片显示到页面
    function clearphoto() {
      const context = canvas.getContext("2d");
      context.fillStyle = "#AAA";
      context.fillRect(0, 0, canvas.width, canvas.height);
      const data = canvas.toDataURL("image/png");
      photo.setAttribute("src", data);
      imgData = new FormData().append("img", data)
    }

    // 将canvas内容作为照片
    function takepicture() {
      const context = canvas.getContext("2d");
      if (width && height) {
        canvas.width = width;
        canvas.height = height;
        context.drawImage(video, 0, 0, width, height);
        const data = canvas.toDataURL("image/png");
        photo.setAttribute("src", data);
        imgData = new FormData().append("img", data)
      } else {
        clearphoto();
      }
    }
</script>

十八、前端扫码识别二维码

1.在原生js中使用html-qrcode,直接下载html5-qrcode.min.js压缩包,并引入该文件。
2.html-qrcode的git地址:https://github.com/mebjas/html5-qrcode/blob/master/examples/html5/index.html
3.也可参照以下示例:https://github.com/mebjas/html5-qrcode/blob/master/examples/html5/index.html

    <div class="qrcode-cointer" style="height:calc(100% - 80px);overflow: auto">
      <button class="search_btn" id="sm_btn" onclick="getCameras()">扫码查询</button>
      <button class="search_btn search_btn_two" id="cx_btn" onclick="codeSearch()">查询</button>
      <div class="qrcode">
        <div id="reader"></div>
      </div>
    </div>

<script src="./html5-qrcode.min.js"></script>
<script>
  let html5QrCode = null;
  // 获取摄像头权限
  const getCameras = () => {
      Html5Qrcode.getCameras()
      .then((devices) => {
        // 返回的是你的摄像设备列表,手机有两个摄像头,电脑返回一个摄像头
        // 初始化扫描程序,在这里需要传递标签的id,第二个参数用来控制识别类型
          html5QrCode = new Html5Qrcode("reader");
          start();
      })
      .catch((err) => {
          html5QrCode = new Html5Qrcode("reader")
      })
    }
    // 开始扫描相机给的二维码
    const start = () => {
      html5QrCode.start(
      // environment后置摄像头 user前置摄像头 也可以传递获取摄像头时的id
      // 也可以是这样的{ deviceId: { exact: cameraId} }
      { facingMode: "environment" },
      {
          fps: 20, // 可选,每秒帧扫描二维码
          qrbox: { width: 250, height: 250 }, // 可选,如果你想要有界框UI
          aspectRatio: 1.777778 // 可选,视频馈送需要的纵横比,(4:3--1.333334, 16:9--1.777778, 1:1--1.0)传递错误的纵横比会导致视频不显示
      },
      (decodedText, decodedResult) => {
        // 成功的回调函数
        alert(decodedText)
        if(decodedText.includes("http")) {
          stop();
          window.location.href= decodedText;
        }else if(!decodedText.includes("http")){
          stop();
          window.location.href = base_url + "?code=" + decodedText
        }
      })
      // 这里应该还有一个错误回调函数(没有识别到的时候会执行,太频繁了,没写)
    }
    // 停止摄像头
    const stop = () => {
      if(html5QrCode){
        html5QrCode.stop().then((suc) => {
          console.log("关闭摄像头")
        })
      }
    }
</script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容