uni-app中添加自定义相机(微信小程序+app)

news2024/10/22 17:02:50

一、微信小程序中

微信小程序中可以直接使用camera标签,这个标签不兼容app,官方文档

		<camera
          device-position="back"
          flash="off"
          :style="{ height: lheight + 'px', width: lwidth + 'px' }"
          class="w-full"
        ></camera>

拍照方法:

const takePhoto = () => {
  const ctx = uni.createCameraContext();
  ctx.takePhoto({
    quality: "high",
    success: (res) => {
     console.log(res.tempImagePath);
    },
  });
};

二、uni-app混合开发app中

在app中需要使用live-pusher直播推流组件来实现,官方文档

有两种方法实现,
1、使用live-pusher标签,但是页面需要是nvue平台【推荐使用这种】
2、使用h5plus提供的plus.video.LivePusher来实现,可以使用于vue平台,如果需要在相机上覆盖样式,比如边框之类的比较复杂,实践过后发现获取的图片宽度比相机宽度大,获取快照时间过长,大概需要2秒

1、live-pusher标签

1.1、使用标签形式需要把页面设置为nvue后缀,如果是vue后缀的可以显示相机但是无法使用uni.createLivePusherContext来获取 live-pusher 上下文对象,在拍照的时候无法进入回调函数,所以无法获取拍照的图片

1.2、需要自定义样式可以使用cover-view等来实现

1.3、如果是vue平台添加了一个nvue页面导致运行项目是报警告[plugin:vite:nvue-css],如下:
在这里插入图片描述
解决办法:在app.vue引入公共css文件外添加#ifndef APP-PLUS-NVUE条件

// #ifndef APP-PLUS-NVUE
@import "uview-plus/index.scss";
/*每个页面公共css */
@import "colorui/main.css";
//#endif

1.4、在vue3中通过下面获取直播组件的上下文(其他正常把选项式改为组合式):

const content = getCurrentInstance().proxy;
clivePusher.value = uni.createLivePusherContext('livePusher', content);

实例代码vue2写法【仅供参考,根据实际需求修改】:

<template>
  <view style="position: relative;height: 100vh; width:100%;display: flex;flex-direction: column;">
    <u-navbar :fixed="false" title="拍照" :autoBack="true"></u-navbar>
    <view :style="{height:lheight +'px',width:lwidth+'px'}" class="carema_css">
      <image v-if="imgsrc" mode="aspectFit" :src="imgsrc" :style="{height:lheight +'px',width:lwidth+'px'}" ></image>
      
        <live-pusher @statechange="statechange" :style="{height:lheight+'px'}" id='livePusher' ref="livePusher" class="livePusher" url=""
          mode="FHD" :muted="true" :enable-camera="true" :auto-focus="true" :beauty="1" whiteness="2"
          aspect="9:16"
        >
        </live-pusher>
        <cover-view v-if="!imgsrc" class="cover_view" :style="{height:lheight+'px',width:lwidth+'px'}">
          <cover-view class="cover_css flec-center"
           :style="{
             width:(lwidth - borderSize.left - borderSize.right - 4)+'px',
             height:(lheight-borderSize.top-borderSize.bottom -4)+'px',
             marginTop:borderSize.top+'px',
             marginLeft:borderSize.left+'px',
             marginRight:borderSize.right+'px',
             marginBottom:borderSize.bottom+'px',
             }"
            >
            <text class="covertext">请把单据放在边框内拍照{{imgsrc}}</text>
          </cover-view>
        </cover-view>
        </view>
        <view class="btn-css" :style="{width:lwidth+'px'}">
          <view class="has_imgsrc" :style="{width:lwidth+'px'}" v-if="!imgsrc">
              <view style="width:52px;">
                <u-icon
                  name="photo-fill"
                  color="white"
                  size="28"
                  @click="openimage"
                ></u-icon>
              </view>
              <view style="width:52px;">
                <view
                  class="btn-takePhoto"
                  type="primary"
                  @click="snapshot"
                >
                  <u-icon name="camera" color="#067FFF" size="54rpx"></u-icon>
                </view>
              </view>
              <view style="width:52px;"></view>
             </view>
             <view v-else style="padding: 0 66rpx;display: flex;justify-content: space-between;flex-direction: row;">
               <view
                 @click="snapshotAgainPusher"
               >
                 <text class="comfirmimg_text">
                   重新拍照
                 </text>
               </view>
               <!-- <view
                 @click="cut_img"
               >
                 <text class="comfirmimg_text">
                   裁剪
                 </text>
               </view> -->
               <view>
                 <text class="comfirmimg_text" @click="submit">
                   确认上传
                 </text>
               </view>
          </view>
        </view>
    </view>
</template>
<script>
  import { uploadImg } from "../../config/request.js";
    export default {
      data() {
        return {
          camerastate: false, //相机准备好了
          poenCarmeInterval:null,//打开相机的轮询
          context:null,
          imgsrc:'',
          borderSize: {
            top: null,
            bottom: null,
            right: null,
            left: null,
          },
          proportion: null,
          lheight: null,
          lwidth: null,
          navbarHeight: null,
          border_size_init: 10, // 默认边框宽度
        }
      },
      onLoad(option) {
        uni.setStorageSync("from_uploadimg_page", true);
        // W_H_proportion:宽和高的比例值,宽300,高400,传0.75,不传显示默认边框宽度
        this.proportion = Number(option?.W_H_proportion) || null;
        this.init_W_h()
      },
        onReady() {
          this.liveInit()
        },
        onShow: function () {
          console.log("App Show");
          this.snapshotAgainPusher()
        },
        onHide: function () {
          console.log("App Hide");
        },
        beforeUnmount() {
          // 页面退出时销毁scanWin
          console.log("beforeUnmount");
          this.close();
        },
        methods: {
          cut_img(){
            uni.navigateTo({
              url: "/components/customPhotography/imgCut?img="+this.imgsrc,
            });
          },
          liveInit(){
            // 注意:需要在onReady中 或 onLoad 延时
            this.context = uni.createLivePusherContext("livePusher", this);
            this.switchCamera()
            this.startPreview()
          },
          // TODO 后期需要加上ocr识别后才能上传
          async submit() {
            const img_code = await this.imgUpload();
            if (img_code) {
              uni.$emit("img_upload_img", {
                img_code,
                temporary_img: this.imgsrc,
              });
              uni.navigateBack(-1);
            }
            // uni.$on("img_upload_img", function (data) {});获取图片code
          },
          async imgUpload() {
            uni.showLoading({
              title: "图片正在上传",
              mask: true,
            });
            const { data, code } = await uploadImg(this.imgsrc);
            uni.hideLoading();
            if (code === 200) {
              return data;
            } else {
              return false;
            }
          },
          openimage() {
            const this_ = this;
            uni.chooseImage({
              count: 1,
              sizeType: ["compressed"], // original 原图,compressed 压缩图,默认二者都有,compressed手机端选照片会压缩图片size
              sourceType: ["album"], // album 从相册选图,camera 使用相机,默认二者都有
              success: function (res) {
                this_.imgsrc = res.tempFilePaths[0];
              },
              fail: function (e) {
                console.log(e);
              },
              complete: function () {},
            });
          },
          init_W_h() {
            const this_ = this;
            uni.getSystemInfo({
              success(res) {
                this_.navbarHeight = res.model.indexOf("iPhone") !== -1 ? 44 : 48;
                console.log(res);
                const canUseHeight =
                  res.screenHeight - res.statusBarHeight - this_.navbarHeight - 86; // 相机总的可用高度
                const canUseWidth = res.screenWidth; // 相机总的可用宽度
                const calculatesize = this_.calculateDimensions(
                  // 减去默认的两个边框长度(保证边框不会跟手机边框重叠)
                  canUseWidth - this_.border_size_init * 2,
                  canUseHeight - this_.border_size_init * 2,
                  this_.proportion
                ); // 计算出边框的宽高
                this_.lheight = canUseHeight;
                this_.lwidth = canUseWidth;
                this_.borderSize = {
                  top: (canUseHeight - calculatesize.height) / 2,
                  bottom: (canUseHeight - calculatesize.height) / 2,
                  right: (canUseWidth - calculatesize.width) / 2,
                  left: (canUseWidth - calculatesize.width) / 2,
                };
                console.log(canUseHeight);
              },
            });
          },
          calculateDimensions(maxWidth, maxHeight, aspectRatio) {
            if (!aspectRatio) {
              return {
                width: maxWidth,
                height: maxHeight,
              };
            }
            // 根据比例计算可能的宽度和高度
            let possibleWidth = maxWidth;
            let possibleHeight = maxWidth / aspectRatio;
            // 检查高度是否超过了最大高度
            if (possibleHeight > maxHeight) {
              // 如果超过了,则以最大高度为基准,重新计算宽度
              possibleHeight = maxHeight;
              possibleWidth = maxHeight * aspectRatio;
            }
            // 返回计算后的宽度和高度
            return {
              width: possibleWidth,
              height: possibleHeight,
            };
          },
          snapshotAgainPusher(){
            const _this = this
            this.imgsrc = ''
            this.$nextTick(()=>{
              _this.context = uni.createLivePusherContext("livePusher", _this);
              	console.log(data.livePusher, "???");
              	_this.startPreview();
              	_this.poenCarme();
            })
          },
          //轮询打开
          poenCarme(){
            const _this = this
            //#ifdef APP-PLUS
            if (plus.os.name == 'Android') {
              this.poenCarmeInterval = setInterval(function() {
                console.log(_this.camerastate);
                if (!_this.camerastate) _this.startPreview();
              }, 2500);
            }
            //#endif
          },
          start: function() {
              this.context.start({
                  success: (a) => {
                      console.log("livePusher.start:" + JSON.stringify(a));
                  }
              });
          },
          close: function() {
              this.context.close({
                  success: (a) => {
                      console.log("livePusher.close:" + JSON.stringify(a));
                  }
              });
          },
          snapshot: function() {
            const this_ = this
              this.context.snapshot({
                  success: (e) => {
                    this_.imgsrc = e.message.tempImagePath
                  }
              });
          },
          stop: function() {
              this.context.stop({
                  success: (a) => {
                      console.log(JSON.stringify(a));
                  }
              });
          },
          switchCamera: function() {
              this.context.switchCamera({
                  success: (a) => {
                      console.log("livePusher.switchCamera:" + JSON.stringify(a));
                  }
              });
          },
          startPreview: function() {
              this.context.startPreview({
                  success: (a) => {
                      console.log("livePusher.startPreview:" + JSON.stringify(a));
                  }
              });
          },
          stopPreview: function() {
            const _this = this
              this.context.stopPreview({
                  success: (a) => {
                    _this.camerastate = false; //标记相机未启动
                      console.log("livePusher.stopPreview:" + JSON.stringify(a));
                  }
              });
          },
          //状态
          statechange(e) {
            const _this = this
            //状态改变
            console.log('状态改变',e);
            if (e.detail.code == 1007) {
              _this.camerastate = true;
            } else if (e.detail.code == -1301) {
              _this.camerastate = false;
            }
          },
        }
    }
</script>
<style scoped lang="scss">
  .has_imgsrc{
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    align-items: center;
  }
  .covertext {
    background: rgba(51, 51, 51, 0.4);
    color: #f9f9f9;
    padding: 3px;
    font-size: 14px;
  }
  .flec-center{
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .carema_css{
    // border:3px solid red;
    position: relative;
  }
  .cover_view{
    width:100%;
    height: 100%;
    position: absolute;
    top: 0px;
    left: 0px;
    z-index:99999;
    box-shadow: inset 0 0 0 2px #000;
  }
  .cover_css{
    text-align: center;
    color:white;
    // border:2px solid #fefefe;
    border-right:1.5px dashed #fefefe;
    border-top:1.49999px dashed #fefefe;
    border-bottom:1.49999px dashed #fefefe;
    border-left:1.5px dashed #fefefe;
    border-radius: 4px;
  }
.flex-ctr-full {
  height: 100vh;
  display: flex;
  flex-direction: column;
}
.border-corner {
  position: absolute;
  width: 40rpx;
  height: 40rpx;
  border-top: 3px solid #fff;
  border-left: 3px solid #fff;
  // border-top-left-radius: 14rpx;
}
.borderlt {
    top: 0px;
    left: 0px;
  }
.borderrt {
    top: 0px;
    right: 0px;
    transform: rotate(90deg);
  }
.borderlb {
    bottom: 0px;
    left: 0px;
    transform: rotate(270deg);
  }
.borderrb {
    bottom: 0px;
    right: 0px;
    transform: rotate(180deg);
  }
.covertext {
  background: rgba(51, 51, 51, 0.4);
  color: #f9f9f9;
  padding-left: 5px;
  font-size: 14px;
}

.btn-css {
  height: 86px;
  width: 100%;
  background: rgba(36, 36, 36, 0.75);
  // padding: 0 66rpx;
  justify-content: center;
}
.btn-icon {
    color: #fff;
    font-size: 56rpx;
    margin-right: 50rpx;
  }
.comfirmimg_text {
    color: #ffffff;
    font-size: 16px;
    height:51px;
    line-height: 51px;;
  }
  .btn-takePhoto {
    z-index: 250;
    width: 96rpx;
    height: 96rpx;
    padding: 22rpx;
    border-radius: 100%;
    background-color: #ffffff;
    // transform: translateY(-50%);
    box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 12%);
  }

.level-left {
  display: flex;
  justify-content: flex-start;
}
.level-right {
  display: flex;
  justify-content: flex-end;
}
.level {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}
</style>



2、使用plus.video.LivePusher

2.1、这种方法可以在vue页面中使用,自定义样式需要plus.webview.create添加一个html页面来实现,在项目根目录下添加一个hybrid目录,在目录下添加html页面

在这里插入图片描述

 this.scanWin = plus.webview.create(
          "/hybrid/html/faceTip.html?" + params,
          "",
          {
            top: that.navbarHeight + 44 + "px",
            background: "transparent",
            height: that.lheight + "px",
            width: that.lwidth + "px",
          }
        );

2.2、拍照获取页面的时候获取的图片是反转的,需要使用plus.zip.compressImage来翻转图片

plus.zip.compressImage(
        {
          src: imgPath,
          dst: imgPath,
          overwrite: true,
          quality: 40,
          rotate: 270,
        },
        (zipRes) => {
        //获取到正确的图片
        }
        }

2.3、获取的图片宽度会比相机的大,如果有解决办法欢迎分享。
2.4、使用快照snapshot获取图片时间很长,如果有解决办法欢迎分享。
示例【仅供参考,根据实际需求修改】:
相机组件:

<template>
  <view class="flex-ctr-full">
    <u-navbar :fixed="false" title="拍照" :autoBack="true"></u-navbar>
    <view class="flex-1 relative">
      <image
        v-if="imgsrc"
        class="select-img w-full h-full"
        mode="aspectFit"
        :src="imgsrc"
      ></image>
    </view>
    <view class="btn-css">
      <view class="level h-full">
        <view v-if="!imgsrc" class="level-left w-65px">
          <u-icon
            name="photo-fill"
            color="white"
            size="28"
            @click="openimage"
          ></u-icon>
        </view>
        <view
          v-else
          class="level-left w-80px color-white comfirmimg_text"
          :class="!imgsrc ? 'visibility-hidden' : ''"
          @click="snapshotAgainPusher"
        >
          重新拍照
        </view>
        <view
          :class="imgsrc ? 'visibility-hidden' : ''"
          class="btn-takePhoto level-item mt-12px"
          type="primary"
          @click="takePhoto"
        >
          <u-icon name="camera" color="#067FFF" size="54rpx"></u-icon>
        </view>

        <view class="level-right" :class="!imgsrc ? 'visibility-hidden' : ''">
          <text class="comfirmimg_text btn-collection" @click="submit">
            确认上传
          </text>
        </view>
      </view>
    </view>
  </view>
</template>
<script>
import { uploadImg } from "../../config/request.js";
export default {
  data() {
    return {
      imgsrc: "",
      pusher: null,
      scanWin: null,
      snapshotTimeoutNumber: 3000,
      faceInitTimeout: null,
      snapshTimeout: null,
      screenHeight: 0,
      topStatusHeight: 0,
      borderSize: {
        top: null,
        bottom: null,
        right: null,
        left: null,
      },
      proportion: null,
      lheight: null,
      lwidth: null,
      navbarHeight: null,
      border_size_init: 20, // 默认边框宽度
      imgurl: "",
    };
  },
  onLoad(option) {
    uni.setStorageSync("from_uploadimg_page", true);
    // W_H_proportion:宽和高的比例值,宽300,高400,传0.75,不传显示默认边框宽度
    this.proportion = Number(option?.W_H_proportion) || null;
    this.init_W_h();
    let that = this;
    uni.getSystemInfo({
      success: function (e) {
        console.log(e);
        that.screenHeight = e.windowHeight;
        that.topStatusHeight = e.screenHeight - e.windowHeight + "px";
      },
    });

    //#ifdef APP-PLUS
    this.faceInit();
    //#endif
  },
  onHide() {
    // this.scanWin.close();
    this.faceInitTimeout && clearTimeout(this.faceInitTimeout);
    this.snapshTimeout && clearTimeout(this.snapshTimeout);
  },
  methods: {
    openimage() {
      const this_ = this;
      uni.chooseImage({
        count: 1,
        sizeType: ["compressed"], // original 原图,compressed 压缩图,默认二者都有,compressed手机端选照片会压缩图片size
        sourceType: ["album"], // album 从相册选图,camera 使用相机,默认二者都有
        success: function (res) {
          this_.imgsrc = res.tempFilePaths[0];
          this_.imgurl = res.tempFilePaths[0];
          this_.scanWin.close();
          this_.pusher.close();
        },
        fail: function (e) {
          console.log(e);
        },
        complete: function () {},
      });
    },
    init_W_h() {
      const this_ = this;
      uni.getSystemInfo({
        success(res) {
          this_.navbarHeight = res.statusBarHeight;
          console.log(res);
          const canUseHeight =
            res.screenHeight - res.statusBarHeight - this_.navbarHeight - 86; // 相机总的可用高度
          const canUseWidth = res.screenWidth; // 相机总的可用宽度
          const calculatesize = this_.calculateDimensions(
            // 减去默认的两个边框长度(保证边框不会跟手机边框重叠)
            canUseWidth - this_.border_size_init * 2,
            canUseHeight - this_.border_size_init * 2,
            this_.proportion
          ); // 计算出边框的宽高
          this_.lheight = canUseHeight;
          this_.lwidth = canUseWidth;
          this_.borderSize = {
            top: (canUseHeight - calculatesize.height) / 2,
            bottom: (canUseHeight - calculatesize.height) / 2,
            right: (canUseWidth - calculatesize.width) / 2,
            left: (canUseWidth - calculatesize.width) / 2,
          };
        },
      });
    },
    calculateDimensions(maxWidth, maxHeight, aspectRatio) {
      if (!aspectRatio) {
        return {
          width: maxWidth,
          height: maxHeight,
        };
      }
      // 根据比例计算可能的宽度和高度
      let possibleWidth = maxWidth;
      let possibleHeight = maxWidth / aspectRatio;
      // 检查高度是否超过了最大高度
      if (possibleHeight > maxHeight) {
        // 如果超过了,则以最大高度为基准,重新计算宽度
        possibleHeight = maxHeight;
        possibleWidth = maxHeight * aspectRatio;
      }
      // 返回计算后的宽度和高度
      return {
        width: possibleWidth,
        height: possibleHeight,
      };
    },
    faceInit() {
      let that = this;
      uni.showLoading({
        title: "加载中",
        mask: true,
      });
      this.faceInitTimeout = setTimeout(() => {
        this.pusherInit();
        const params = `height=${that.lheight}&width=${that.lwidth}&top=${this.borderSize.top}&left=${this.borderSize.left}&right=${this.borderSize.right}&bottom=${this.borderSize.bottom}`;
        this.scanWin = plus.webview.create(
          "/hybrid/html/faceTip.html?" + params,
          "",
          {
            top: that.navbarHeight + 44 + "px",
            background: "transparent",
            height: that.lheight + "px",
            width: that.lwidth + "px",
          }
        );
        setTimeout(() => {
          this.scanWin.show();
        }, 200);
      }, 200);
      uni.hideLoading();
    },
    pusherInit() {
      let that = this;
      const pages = getCurrentPages(); // 获取当前页面栈
      const page = pages[pages.length - 1]; // 获取当前页面的对象
      const currentWebview = page.$getAppWebview();
      console.log(
        that.screenHeight - 50 + "px",
        that.lheight + "px",
        that.navbarHeight,
        that.topStatusHeight
      );
      this.pusher = plus.video.createLivePusher("livepusher", {
        url: "",
        top: that.navbarHeight + 44 + "px",
        left: "0px",
        width: that.lwhite + "px",
        height: that.lheight + "px",
        position: "absolute",
        aspect: "3:4",
        "z-index": 999,
      });
      currentWebview.append(this.pusher);
      // this.pusher.switchCamera(); //换为前置摄像头
      this.pusher.preview();
      uni.hideLoading();
    },
    //拍照
    takePhoto() {
      let that = this;
      uni.showLoading({
        title: "照片生成中",
        mask: true,
      });
      // this.snapshTimeout = setTimeout(() => {
      console.log(this.pusher);
      this.pusher.snapshot(
        (res) => {
          console.log("走到这啦2", res);
          const src = res.tempImagePath;
          that.imgurl = res.tempImagePath;
          that.getImage(src);
        },
        (err) => {
          console.log("拍照失败", err);
          uni.showToast({
            title: "拍照失败",
          });
        }
      );
      // }, 3000);
    },
    // 重拍
    snapshotAgainPusher() {
      this.faceInit(); //全部重新加载
      this.imgsrc = "";
    },
    getImage(imgPath) {
      let that = this;
      plus.zip.compressImage(
        {
          src: imgPath,
          dst: imgPath,
          overwrite: true,
          quality: 40,
          rotate: 270,
        },
        (zipRes) => {
          that.imgsrc = zipRes.target;
          that.scanWin.close();
          uni.hideLoading();
          uni.showToast({
            title: "照片已生成",
            duration: 1000,
            success() {},
          });
          that.pusher.close();
        },
        function (error) {
          uni.showToast({
            title: "照片生成失败",
          });
        }
      );
    },
    // TODO 后期需要加上ocr识别后才能上传
    async submit() {
      const img_code = await this.imgUpload();
      if (img_code) {
        uni.$emit("img_upload_img", {
          img_code,
          temporary_img: this.imgurl,
        });
        uni.navigateBack(-1);
      }
      // uni.$on("img_upload_img", function (data) {});获取图片code
    },
    async imgUpload() {
      uni.showLoading({
        title: "图片正在上传",
        mask: true,
      });
      const { data, code } = await uploadImg(this.imgurl);
      uni.hideLoading();
      console.log(data, code);
      if (code === 200) {
        return data;
      } else {
        return false;
      }
    },
  },
  beforeUnmount() {
    // 页面退出时销毁scanWin
    console.log("beforeUnmount");
    this.scanWin.close();
  },
};
</script>

<style scoped lang="scss">
.cover {
  position: absolute;
  z-index: 200;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
.flex-ctr-full {
  height: 100vh;
  display: flex;
  flex-direction: column;
}
.border-corner {
  position: absolute;
  width: 40rpx;
  height: 40rpx;
  border-top: 3px solid #fff;
  border-left: 3px solid #fff;
  // border-top-left-radius: 14rpx;

  &.borderlt {
    top: 0px;
    left: 0px;
  }

  &.borderrt {
    top: 0px;
    right: 0px;
    transform: rotate(90deg);
  }

  &.borderlb {
    bottom: 0px;
    left: 0px;
    transform: rotate(270deg);
  }

  &.borderrb {
    bottom: 0px;
    right: 0px;
    transform: rotate(180deg);
  }
}

.covertext {
  background: rgba(51, 51, 51, 0.4);
  color: #f9f9f9;
  padding-left: 5px;
  font-size: 14px;
}

.btn-css {
  height: 86px;
  line-height: 86px;
  width: 100%;
  background: rgba(36, 36, 36, 0.75);
  padding: 0 66rpx;

  .btn-icon {
    color: #fff;
    font-size: 56rpx;
    margin-right: 50rpx;

    &.btn-collection {
      margin-left: 50rpx;
      margin-right: 0;
    }
  }

  .comfirmimg_text {
    padding: 14rpx;
    color: #f9f9f9;
    font-size: 16px;
  }
  .btn-takePhoto {
    z-index: 250;
    width: 96rpx;
    height: 96rpx;
    padding: 22rpx;
    border-radius: 100%;
    background-color: #ffffff;
    // transform: translateY(-50%);
    box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 12%);
  }
  .level-left,
  .level-right {
    flex-basis: auto;
    flex-grow: 0;
    flex-shrink: 0;
    display: flex;
  }
}
.level-left {
  justify-content: flex-start;
}
.level-right {
  justify-content: flex-end;
}
.level {
  display: flex;
  justify-content: space-between;
}
</style>

自定义样式的覆盖文件faceTip.html:
头部需要添加页面根据设备进行缩放

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title></title>
    <script>
      function getURLParameter(name) {
        // 使用window.location.search获取URL中的查询字符串
        var query = window.location.search.substring(1);
        // 使用new URLSearchParams创建一个查询字符串参数的实例
        var vars = query.split("&");
        for (var i = 0; i < vars.length; i++) {
          var pair = vars[i].split("=");
          if (pair[0] == name) {
            return pair[1];
          }
        }
        return (false);
      }

      // 调用函数获取特定参数
      // var paramsValue = {}
      // paramsValue.top = getURLParameter('top');
    <style>
      .facecontent {
        height: 100%;
        position: absolute;
        width: 100%;
        text-align: center;
      }

      .cover {
        height: calc(100% - 100px);
        width: calc(100% - 100px);
        text-align: center;
        padding: 40px;
        display: flex;
        justify-content: center;
        align-items: center;
        border: 3px dashed #f9f9f9;
        border-radius: 20px;
      }

      .covertext {
        background: rgba(51, 51, 51, 0.4);
        color: #f9f9f9;
        font-size: 15px;
        padding: 4px;
      }
    </style>
  </head>
  <body>
    <div class="facecontent" id="facecontent_id">
      <div class="cover" id="cover_border">
        <div class="covertext">请把单据放在边框内拍照</div>
      </div>
    </div>
  </body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220934.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3【实战】 渲染 md 文件(markdown语法 .md后缀的文件)

1. 安装相关插件 npm i unplugin-vue-markdown markdown-it-prism prism unhead/vue2. 添加配置 src/main.ts // 给 md 文件创建头部 import { createHead } from unhead/vue // md 文件中代码高亮的样式 import prismjs/themes/prism.css // 自定义 md 文件的样式 import /as…

【JAVA面试题】什么是Springboot的自动配置以及注意事项

文章目录 强烈推荐核心概念&#xff1a;自动配置的关键特点&#xff1a;示例&#xff1a; 需要注意的点1.默认配置可能不适合所有场景2.Bean 冲突与覆盖3.应用启动慢的问题4.过度依赖自动配置5.安全性问题6.依赖冲突与版本兼容7.过多不必要的自动配置8.调试困难 专栏集锦 强烈推…

.net framework 3.5sp1安装错误卡住不动怎么解决

解决 .NET Framework 3.5 SP1 安装错误卡住的问题&#xff0c;可以尝试以下几种方法&#xff1a; 1.使用 DISM 工具&#xff1a; 将下载的 NetFx3.cab 文件放置在 C:\Windows 文件夹下。 以管理员身份打开命令提示符&#xff0c;输入以下命令&#xff1a; dism /online /En…

【web前端设计】jquery图标动画特效

学习目标 学习web前端设计技术&#xff08;HTML、css、JavaScript、jQuery等&#xff09;&#xff0c;综合运用技术&#xff0c;将其与HTML元素结合&#xff0c;设计样式、监听事件、添加动画等&#xff0c;给用户呈现出更好的视觉交互效果。本文主要学习分页按钮自动放大、元…

HCIP-HarmonyOS Application Developer 习题(十二)

&#xff08;多选&#xff09;1、声明式开发范式的转场动画包含以下哪几种类型? A、页面间转场 B、应用间转场 C、共享元素转场 D、组件内转场 答案&#xff1a;ACD 分析&#xff1a; &#xff08;多选&#xff09;2、公共事件服务为应用程序提供哪些能力。 A、取消发布公共…

IPMA能力基础线是什么?项目管理工具有哪些应用场景?

作为资深团队管理者&#xff0c;常常面临如何提升团队项目管理能力的挑战。其实在现代的项目管理世界中&#xff0c;有许多标准和模型帮助我们更好地理解项目的复杂性&#xff0c;IPMA&#xff08;International Project Management Association&#xff09;能力基础线就是其中…

iOS静态库(.a)及资源文件的生成与使用详解(Swift版本)

引言 在 iOS 开发中&#xff0c;开发者常常需要将一些功能模块封装成可重用的库&#xff0c;以便在多个项目中共享使用。除了常见的Framework&#xff08;动态库/静态库&#xff09;&#xff0c;静态库&#xff08;.a文件&#xff09;也是一种非常实用的封装方式。静态库在编译…

Java全栈经典面试题剖析4】JavaSE高级 -- 包装类,String, 类方法

目录 面试题3.1 什么是自动装箱与拆箱&#xff1f;用什么方式来装箱与拆箱&#xff1f; 面试题3.2 int和Integer有什么区别&#xff1f; 面试题3.3 Integer常量池 面试题3.4 字符串常量池 面试题3.5 这句代码创建了几个对象? String str1 new String("xyz");…

前端拦截302重定向

背景: 根据业务场景需要拦截302做后续的逻辑处理 尝试一: : axios拦截 、、、、、async created() {// 获取302请求返回的location后手动修改video的src路径let targetSrc;try {await axios.get(this.video).then((res) > {const { headers, status } res;const { locat…

Android 图片相识度比较(pHash)

概述 在 Android 中&#xff0c;要比对两张 Bitmap 图片的相似度&#xff0c;常见的方法有基于像素差异、直方图比较、或者使用一些更高级的算法如 SSIM&#xff08;结构相似性&#xff09;和感知哈希&#xff08;pHash&#xff09;。 1. 基于像素的差异比较 可以逐像素比较…

学习笔记——Test.pwn

前言&#xff1a;笔者也才接触Pwn&#xff0c;写这篇wp&#xff0c;记录目前所得感悟、思考、理解等。 存在错误&#xff0c;或者解释不通的地方&#xff0c;还请提出&#xff0c;已补足笔记的缺陷。 Pwn是什么&#xff1f; 我Pwn掉了你的电脑、我Pwn掉了你的设备…… 通俗的…

重庆大学软件工程考研,难度如何?

C哥专业提供——计软考研院校选择分析专业课备考指南规划 重大软件专业可谓是最好上岸的985院校&#xff01;重庆大学24考研各大学院复试录取情况已出&#xff0c; 我们先说学硕部分&#xff1a; 招生人数&#xff1a; 重庆大学软件工程学硕近几年计划统招人数都不多&#xf…

入选ECCV 2024!浙江大学联合微软亚洲研究院提出统一医学图像预训练框架UniMedI,打破医学数据异构化藩篱

让 AI 在某些条件下具备类似人类的反应能力&#xff0c;从而代替人类高效地从事特定工作&#xff0c;是 AI 领域研究人员孜孜不倦的追求。正如在医学图像和人工智能的交叉领域&#xff0c;基于视觉语言预训练的深度模型 (Visual-Language Pre-training, VLP) 凭借其自动化的特点…

Docker本地镜像发布到阿里云镜像服务的简易指南

1 阿里云容器镜像服务 阿里云容器镜像服务&#xff08;Alibaba Cloud Container Registry&#xff0c;简称ACR&#xff09;是一个为容器镜像、Helm Chart等云原生资产提供安全托管及高效分发的平台。它支持多架构容器镜像&#xff0c;包括Linux、Windows、ARM等&#xff0c;以…

心觉:感恩日记:每天5分钟,重新定义你的人生

​Hi&#xff0c;我是心觉&#xff0c;与你一起玩转潜意识、脑波音乐和吸引力法则&#xff0c;轻松掌控自己的人生&#xff01; 挑战每日一省写作207/1000天 你是否觉得生活节奏太快&#xff0c;总是有做不完的事、解决不完的问题&#xff1f; 有一个简单的方法&#xff0c;…

DEPT_ DECOMPOSED PROMPT TUNING FOR PARAMETER-EFFICIENT FINE-TUNING

论文汇总 当前的问题 (1)Prompt Tuning通常收敛缓慢&#xff0c;并且对初始化敏感&#xff1b; (2)Prompt Tuning延长了输入序列的总长度&#xff0c;从而加剧了计算需求(即训练/推理时间和内存成本)&#xff0c;这是由于Transformer的二次复杂度(Vaswani et al, 2017)。 解…

机器视觉系统硬件组成之工业相机篇

工业相机是一种非常重要的机器视觉器件&#xff0c;它能够将被采集的图像信息通过电路转换成电信号&#xff0c;再通过模数转换器&#xff08;ADC&#xff09;将其转化为数字信号&#xff0c;最后以标准的视频信号输出。工业相机在机器视觉领域得到了广泛应用&#xff0c;包括质…

springboot055服装生产管理的设计与实现(论文+源码)_kaic

毕业设计(论文) 协力服装厂服装生产管理系统 的设计与实现 学生姓名 XXX 学 号 XXXXXXXX 分院名称 XXXXXXXX 专业班级 XXXXX 指导教师 XXXX 填写…

CROss PlatformS (CROPS) 与 Docker

CROPS 是一个开源的、跨平台的开发框架&#xff0c;专为利用 Docker 容器在 Windows、macOS 和 Linux 系统上创建和管理构建主机而设计。它简化了在非 Linux 系统上运行 Yocto 项目及其他基于 Linux 的工具的过程&#xff0c;同时提供了一个可扩展的开发环境&#xff0c;支持多…

基于vue框架的的地铁站智慧管理系统的设计n09jb(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,上班打卡,下班打卡,人员管理,交接班,视频巡检,车辆巡检,车辆管理 开题报告内容 基于Vue框架的地铁站智慧管理系统的设计开题报告 一、研究背景与意义 随着城市化进程的加速&#xff0c;地铁站作为城市交通系统的重要组成部分&am…