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

一、微信小程序中

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

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

拍照方法:

cpp 复制代码
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条件

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

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

javascript 复制代码
<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 :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" @statechange="statechange" @netstatus="netstatus" @error = "error"
        >
        </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>
                 <text class="comfirmimg_text" @click="submit">
                   确认上传
                 </text>
               </view>
          </view>
        </view>
    </view>
</template>
<script>
  import { uploadImg } from "../../config/request.js";
    export default {
      data() {
        return {
          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()
        },
        beforeUnmount() {
          // 页面退出时销毁scanWin
          console.log("beforeUnmount");
          this.close();
        },
        methods: {
          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];
                this_.stopPreview();
              },
              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(){
            this.imgsrc = ''
            this.startPreview()
          },
          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() {
              this.context.stopPreview({
                  success: (a) => {
                      console.log("livePusher.stopPreview:" + JSON.stringify(a));
                  }
              });
          }
        }
    }
</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页面

javascript 复制代码
 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来翻转图片

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

2.3、获取的图片宽度会比相机的大,如果有解决办法欢迎分享。

2.4、使用快照snapshot获取图片时间很长,如果有解决办法欢迎分享。

示例【仅供参考,根据实际需求修改】:

相机组件:

javascript 复制代码
<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:

头部需要添加页面根据设备进行缩放

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>
相关推荐
井眼3 小时前
微信小程序-prettier 格式化
微信小程序·小程序
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
Random_index4 小时前
#Uniapp篇:支持纯血鸿蒙&发布&适配&UIUI
uni-app·harmonyos
qq_17448285755 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序
wqq_9922502775 小时前
springboot基于微信小程序的食堂预约点餐系统
数据库·微信小程序·小程序
初遇你时动了情12 小时前
uniapp 城市选择插件
开发语言·javascript·uni-app
licy__12 小时前
微信小程序登录注册页面设计(小程序项目)
微信小程序·小程序
小小黑00714 小时前
uniapp+vue3+ts H5端使用Quill富文本插件以及解决上传图片反显的问题
uni-app·vue
草字14 小时前
uniapp input限制输入负数,以及保留小数点两位.
java·前端·uni-app
那就举个栗子!14 小时前
多传感器融合slam过程解析【大白话版】
数码相机