uniapp+uView 实现自定义水印相机,拍完照片给图片加水印,从相册选择图片加水印功能

样式图如上所示


页面分为取景框和拍照完成后预览,本功能设计到,公共上传组件,相机也页面,获取定位地址,页面中如何用该上传组件

UI实现
取景界面分为上下两个部分,上部分为camera取景框组件,下部分为操作区域。

取景框组件上的关闭和水印,以及拍完照片后的略缩图展示需要通过cover-view和cover-image来展示。

页面中的引用

 <my-camera :maxCount="5" :name="1" :editUrl="fileList1" @filesSelected="handleFilesSelected"  @upload="handleUpload" />
   import myCamera from '@/components/camera/index.vue'
 components: {
      myCamera,
    },
     data() {
      return {
        fileList1: [],
        
        }
      }
      //删除图片
      handleFilesSelected(index, name) {
        this[`fileList${name}`].splice(index, 1);
      },
      //图片上传
      handleUpload(data) {
        this[`fileList${data.name}`] = data.fileList
      }


结构代码如下:
 

<view class="u-upload">
    <view class="u-list-item u-preview-wrap" v-for="(item, index) in fileList" :key="index" :style="{
			width: 150,
			height: 150
		}">
      <view class="u-delete-icon" @click="deleteItem(index)" :style="{
					background: '#000'
				}">
        <u-icon class="u-icon" name="close" size="10" color="#ffffff"></u-icon>
      </view>
      <image @click="lookImg(index)" class="u-preview-image" :src="item.url"></image>
      <view class="u-upload__status" v-if="item.status === 'uploading'">
        <view class="u-upload__status__icon">
          <u-icon v-if="item.status === 'failed'" name="close-circle" color="#ffffff" size="25" />
          <u-loading-icon size="22" mode="circle" color="#ffffff" v-else />
        </view>
        <text v-if="item.message" class="u-upload__status__message">{{ item.message }}</text>
      </view>
    </view>
    <view style="display: inline-block;" @click="gotoCamera" v-if="fileList.length < maxCount">
      <slot name="addBtn"></slot>
      <view class="u-list-item u-add-wrap" hover-class="u-add-wrap__hover" hover-stay-time="150" :style="{
					width: 150,
					height: 150
				}">
        <u-icon name="camera" size="30"></u-icon>
      </view>
    </view>
    <u-popup :show="show" mode="bottom" @close="close">
      <view style="height: 260rpx;">
        <view class="popup_item" @click="goCamera()">拍照</view>
        <view class="popup_item" @click="goAlbum()">相册</view>
      </view>
    </u-popup>
    <view style="position: absolute;top: -999999px;">
      <view><canvas :style="{ width: w, height: h }" canvas-id="firstCanvas"></canvas></view>
    </view>
  </view>

 样式css

<style scoped lang="scss">
  .u-upload {
    // @include vue-flex;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
  }

  .u-add-tips {
    margin-top: 20rpx;
    line-height: 40rpx;
  }

  .u-delete-icon {
    position: absolute;
    top: 4rpx;
    right: 4rpx;
    z-index: 10;
    background-color: red;
    border-radius: 100rpx;
    width: 32rpx;
    height: 32rpx;
    // @include vue-flex;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .u-icon {
    // @include vue-flex;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .u-list-item {
    width: 160rpx;
    height: 160rpx;
    overflow: hidden;
    margin: 10rpx;
    background: rgb(244, 245, 246);
    position: relative;
    border-radius: 10rpx;
    /* #ifndef APP-NVUE */
    display: flex;
    /* #endif */
    align-items: center;
    justify-content: center;
  }

  .u-preview-wrap {
    border: 1px solid rgb(235, 236, 238);
  }

  .u-preview-image {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 10rpx;
  }

  .popup_item {
    width: 100%;
    height: 130rpx;
    text-align: center;
    line-height: 130rpx;
    border-bottom: 1px solid #eaeef1;
  }

  .u-upload__status {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: rgba(0, 0, 0, 0.5);
    @include flex(column);
    align-items: center;
    justify-content: center;
  }

  .u-upload__status__icon {
    position: relative;
    z-index: 1;
  }

  .u-upload__status__message {
    font-size: 12px;
    color: #FFFFFF;
    margin-top: 5px;
  }
</style>

公共上传组件数据的绑定

 import {
    props
  } from '../../uni_modules/uview-ui/libs/mixin/mixin';
  import getCurrentLngAndLat from "@/utils/getAddressLocation.js"
  export default {
    name: "myCamera",
    props: {
      maxCount: {
        type: Number,
        default: 1
      },
      name: {
        type: String,
        required: true
      },
      editUrl: {
        type: Array,
        default: () => {
          []
        }
      },
      type: {
        type: String,
        default: () => 'order'
      },
      customStyle: {
        type: Object,
        default: () => {}
      }
    },
    data() {
      return {
        fileList: [],
        show: false,
        w: '',
        h: '',
        nowTime: '', //日期
        nowTime2: '', //时间
        nowWeek: '', // 周几
        address: '', //当前地址信息
      }
    },

公共组件部分js实现:分为拍照与相册中上传,包括删除图片,预览图片,保存图片到相册

 watch: {
      editUrl: {
        immediate: true,
        handler(newVal, oldVal) {
          if (this.editUrl[0]?.url === '' || this.editUrl[0]?.url === null) {
            this.fileList = [];
          } else {
            this.fileList = this.editUrl
          }
        }
      }
    },
    methods: {
      goCamera() {
        uni.$u.route(`/pages/camera/camera?name=${this.name}`);
        this.close();
      },
      async goAlbum() {
        this.close();
        this.getTime();
        const res1 = await getCurrentLngAndLat();
        this.address = res1.currentSignAddress;
        const res = await this.chooseFile();
        this.afterRead(res)
      },
      //选择图片
      chooseFile() {
        return new Promise((resolve, reject) => {
          uni.chooseImage({
            count: this.maxCount ? this.maxCount : 1, //默认9
            sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
            sourceType: ['album'], //从相册选择
            success: res => resolve(this.formatImage(res)),
          });
        })
      },
      //处理图片
      formatImage(res) {
        return res.tempFiles.map(item => ({
          ...this.pickExclude(item, ['path']),
          type: 'image',
          url: item.path,
          thumb: item.path,
          size: item.size,
        }))
      },
      pickExclude(obj, keys) {
        // 某些情况下,type可能会为
        if (!['[object Object]', '[object File]'].includes(Object.prototype.toString.call(obj))) {
          return {}
        }
        return Object.keys(obj).reduce((prev, key) => {
          if (!keys.includes(key)) {
            prev[key] = obj[key]
          }
          return prev
        }, {})
      },
      close() {
        this.show = false;
      },
      // 跳转到拍照页面
      gotoCamera() {
        this.show = true;
      },
      async afterRead(file) {
        const lists = [].concat(file);
        const fileListLen = this.fileList.length;
        for (let i = 0; i < lists.length; i++) {
          const item = lists[i];
          let waterUrl = await this.addWatermark(item.url);
          this.fileList.push({
            ...item,
            status: "uploading",
            message: "上传中"
          });
          try {
            const result = await this.uploadFilePromise(waterUrl);
            const uploadedItem = Object.assign({}, item, {
              status: "success",
              message: "",
              url: result
            });
            this.fileList.splice(fileListLen + i, 1, uploadedItem);
          } catch (error) {
            const failedItem = Object.assign({}, item, {
              status: "fail",
              message: "上传失败"
            });
            this.fileList.splice(fileListLen + i, 1, failedItem);
          }
        }
        this.$emit("upload", {
          name: this.name,
          fileList: this.fileList
        });
      },
      //上传图片
      uploadFilePromise(url) {
        let host = "";
        // #ifndef H5
        let envVs = uni.getAccountInfoSync().miniProgram.envVersion;
        if (envVs === 'release') {
          host = uni.getStorageSync('normal_url')
        } else if (envVs === 'trial') {
          host = uni.getStorageSync('exp_url')
        } else if (envVs === 'develop') {
          host = uni.getStorageSync('test_url')
        }
        // #endif
        return new Promise((resolve, reject) => {
          uni.uploadFile({
            url: host + '上传图片接口的url', // 仅为示例,非真实的接口地址
            filePath: url,
            name: 'file',
            formData: {
              type: 'order',
              lat: 23.129163,
              lng: 113.264435
            },
            header: {
              Authorization: 'Bearer ' + uni.getStorageSync('token')
            },
            success: (res) => {
              setTimeout(() => {
                let arr = JSON.parse(res.data)
                resolve(arr.data?.url)
              }, 1000)
            },
          })
        })
      },
      // 删除图片
      deleteItem(index) {
        let that = this;
        uni.showModal({
          title: '提示',
          content: '您确定要删除此张照片吗?',
          success: async (res) => {
            if (res.confirm) {
              this.$emit("filesSelected", index, this.name);
            }
          }
        });
      },
      //查看图片
      lookImg(index) {
        let photoList = this.editUrl.map(item => {
          return item.url;
        });
        // 预览图片
        uni.previewImage({
          current: index,
          urls: photoList,
        });
      },
      //添加水印
      async addWatermark(img) {
        var that = this
        let res1 = await new Promise((resolve, reject) => {
          uni.getImageInfo({
            src: img,
            success: (res) => {
              that.w = res.width / 3 + 'px';
              that.h = res.height / 3.01 + 'px';
              let ctx = uni.createCanvasContext('firstCanvas', this); /** 创建画布 */
              //将图片src放到cancas内,宽高为图片大小
              ctx.drawImage(img, 0, 0, res.width / 3, res.height / 3);
              // ctx.setFontSize(18);
              ctx.setFontSize(Math.floor(res.width * 0.04));
              ctx.setFillStyle('#fff');
              ctx.setTextAlign('center');
              let textToWidth = (res.width / 3) * 0.5;
              let textToHeight = (res.height / 3) * 0.86;
              ctx.fillText(that.nowTime2, textToWidth, textToHeight);
              ctx.setFontSize(Math.floor(res.width * 0.015));
              let textToHeight2 = (res.height / 3) * 0.91;
              ctx.fillText(that.nowTime + ' ' + that.nowWeek, textToWidth, textToHeight2);
              ctx.setFontSize(Math.floor(res.width * 0.01));
              let textToHeight3 = (res.height / 3) * 0.95;
              ctx.fillText(' 📍 ' + that.address, textToWidth, textToHeight3);
              ctx.draw(false, () => {
                // 将画布转化为图片
                uni.canvasToTempFilePath({
                  canvasId: 'firstCanvas',
                  success: (res) => {
                    resolve(res.tempFilePath);
                  },
                  fail: (err) => {
                    reject(err);
                  }
                }, this);
              });
            }
          });
        });
        return (res1);
      },
       //获取时间
      getTime: function() {
        var weekarr = new Array("日", "一", "二", "三", "四", "五", "六");
        var date = new Date(),
          year = date.getFullYear(),
          month = date.getMonth() + 1,
          day = date.getDate(),
          hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(),
          minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(),
          second = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
        month >= 1 && month <= 9 ? (month = "0" + month) : "";
        day >= 0 && day <= 9 ? (day = "0" + day) : "";
        var timer = year + '-' + month + '-' + day;
        var timer3 = hour + ':' + minute + ':' + second;
        var timer2 = hour + ':' + minute
        this.nowTime = timer;
        this.nowTime2 = timer2;
        this.nowTime3 = timer3;
        this.nowWeek = '星期' + weekarr[date.getDay()];
      },

    }
  

相机页面的全部代码:(相机页面给图片加水印与选择图片加水印可以抽离出一个公共的方法,时间紧任务重我就没。。。)

<template>
  <view>
    <camera :device-position="device" :flash="flash" @error="error"
      :style="{ width: '100%',position: 'relative', height: getHeight + 'px' }">
      <cover-view class="topBox">
        <cover-view class="topItem">{{nowWeek}}</cover-view>
        <cover-view class="topItem">{{nowTime +' '+nowTime2}}</cover-view>
        <cover-view class="topItem">{{address}}</cover-view>
      </cover-view>
      <cover-image @click="xzBtn" class="xzImg" src="https://cdn.zhoukaiwen.com/xz.png"></cover-image>
      <cover-view class="cameraBtn" @click="takePhoto">
        <cover-view class="cameraBtn2"></cover-view>
      </cover-view>
    </camera>
    <view style="position: absolute;top: -999999px;">
      <view><canvas :style="{ width: w, height: h }" canvas-id="firstCanvas"></canvas></view>
    </view>
  </view>
</template>

<script>
  import getCurrentLngAndLat from "@/utils/getAddressLocation.js"
  export default {
    data() {
      return {
        getHeight: '200',
        device: 'back', //前置或后置摄像头,值为front, back
        flash: 'off', //闪光灯,值为auto, on, off
        nowTime: '', //日期
        nowTime2: '', //时间
        nowWeek: '', // 周几
        address: '', //当前地址信息
        city: '',
        district: '',
        name: '',
        imgList: [],
        imgListData: '',
        rreportShow: false, //选择照片备注内容弹窗
        src: '',
        w: '',
        h: ''
      }
    },
    async onLoad(options) {
      const that = this;
      uni.getSystemInfo({
        success: function(res) {
          that.getHeight = res.windowHeight;
        }
      });
      this.getTime();
      const res = await getCurrentLngAndLat();
      this.address = res.currentSignAddress;
      this.name = +options.name
    },
    methods: {
      xzBtn() {
        if (this.device == 'front') {
          this.device = 'back'
        } else {
          this.device = 'front'
        }
      },
      // 点击拍照
      takePhoto() {
        var that = this;
        if (this.imgList.length < 3) {
          const ctx = uni.createCameraContext();
          ctx.takePhoto({
            quality: 'high',
            success: (ress) => {
              var tempImagePath = ress.tempImagePath;
              // 获取图片信息
              uni.getImageInfo({
                src: ress.tempImagePath,
                success: res => {
                  that.w = res.width / 3 + 'px';
                  that.h = res.height / 3.01 + 'px';
                  let ctx = uni.createCanvasContext('firstCanvas'); /** 创建画布 */
                  //将图片src放到cancas内,宽高为图片大小
                  ctx.drawImage(ress.tempImagePath, 0, 0, res.width / 3, res.height / 3);
                  ctx.setFontSize(18);
                  ctx.setFillStyle('#FFFFFF');
                  ctx.setTextAlign('center');
                  // 		// ctx.rotate(30 * Math.PI / 180);//0.03
                  let textToWidth = (res.width / 3) * 0.5;
                  let textToHeight = (res.height / 3) * 0.9;
                  ctx.fillText(that.nowTime2, textToWidth, textToHeight);
                  ctx.setFontSize(12);
                  let textToHeight2 = (res.height / 3) * 0.94;
                  ctx.fillText(that.nowTime + ' ' + that.nowWeek, textToWidth, textToHeight2);
                  ctx.setFontSize(7);
                  let textToHeight3 = (res.height / 3) * 0.98;
                  ctx.fillText(' 📍 ' + that.address, textToWidth, textToHeight3);
                  ctx.draw(false, () => {
                    setTimeout(() => {
                      uni.canvasToTempFilePath({
                        canvasId: 'firstCanvas',
                        success: async (res1) => {
                          tempImagePath = res1.tempFilePath;
                          const result = await this.uploadFilePromise(tempImagePath, {
                            lat: 23.129163,
                            lng: 113.264435
                          })
                          let pages = getCurrentPages();
                          let prevPage = pages[pages.length - 2];
                          // 将数据返回上一个页面
                          prevPage.$vm[`fileList${this.name}`].push({
                            url: result
                          })
                          // setTimeout(() => {
                          uni.navigateBack();
                          // }, 500)
                        }
                      });
                    }, 500);
                  });
                }
              });
            },
            fail: (err) => {
              console.log(err, 'errhhhhhhhhhhhh')
            }
          });
        } else {
          uni.showToast({
            title: '最大上传3张照片',
            duration: 2000,
            icon: 'none'
          });
        }
      },
      uploadFilePromise(url, lngLat) {
        // 初始化请求配置项
        let host = "";
        // #ifndef H5
        let envVs = uni.getAccountInfoSync().miniProgram.envVersion;
        if (envVs === 'release') {
          host = uni.getStorageSync('normal_url')
        } else if (envVs === 'trial') {
          host = uni.getStorageSync('exp_url')
        } else if (envVs === 'develop') {
          host = uni.getStorageSync('test_url')
        }
        // #endif
        return new Promise((resolve, reject) => {
          uni.showLoading({
            title: '上传中'
          });
          uni.uploadFile({
            url: host + '上传图片接口url', // 仅为示例,非真实的接口地址
            filePath: url,
            name: 'file',
            formData: {
              type: 'order',
              lat: lngLat.lat,
              lng: lngLat.lng,
            },
            header: {
              Authorization: 'Bearer ' + uni.getStorageSync('token')
            },
            success: (res) => {
              // setTimeout(() => {
              let arr = JSON.parse(res.data)
              if (res.data.code == 0) {
                uni.$showMsg(res.data.message, 'none')
                uni.hideLoading();
              } else {
                uni.hideLoading();
                uni.$showMsg('上传成功', 'none')
                resolve(arr.data?.url)
              }
              // }, 1000)
            },
          })
        })
      },

      getTime: function() {
        var weekarr = new Array("日", "一", "二", "三", "四", "五", "六");
        var date = new Date(),
          year = date.getFullYear(),
          month = date.getMonth() + 1,
          day = date.getDate(),
          hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(),
          minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(),
          second = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
        month >= 1 && month <= 9 ? (month = "0" + month) : "";
        day >= 0 && day <= 9 ? (day = "0" + day) : "";
        var timer = year + '-' + month + '-' + day;
        var timer3 = hour + ':' + minute + ':' + second;
        var timer2 = hour + ':' + minute
        this.nowTime = timer;
        this.nowTime2 = timer2;
        this.nowTime3 = timer3;
        this.nowWeek = '星期' + weekarr[date.getDay()];
      },
      error(e) {
        console.log(e.detail);
      },
    }
  }
</script>

<style lang="scss">
  .topBox {
    width: 750rpx;
    box-sizing: border-box;
    // padding-bottom: 200rpx;
    // padding-left: 30rpx;
    color: #EEEEEE;
    font-size: 34rpx;
    position: absolute;
    bottom: 20%;
    /* 设置小盒子距离大盒子底部的距离为0 */
    left: 50%;
    /* 设置小盒子距离大盒子左侧的距离为50%,即水平居中 */
    transform: translateX(-50%);
    text-align: center;

    /* 通过transform将小盒子水平居中 */
    .topItem {
      width: 100%;
      white-space: pre-wrap;
      margin-bottom: 15rpx;
    }
  }

  .xzImg {
    width: 52rpx;
    height: auto;
    position: absolute;
    right: 44rpx;
    bottom: 160rpx;
  }


  .cameraBtn {
    width: 120rpx;
    height: 120rpx;
    line-height: 120rpx;
    border: 6rpx #FFFFFF solid;
    border-radius: 50%;
    padding: 8rpx;
    position: absolute;
    left: calc(50% - 60rpx);
    bottom: 120rpx;
  }

  .cameraBtn2 {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background-color: #FFFFFF;
    text-align: center;
    color: #007AFF;
  }
</style>

获取当前地址的方法(getLocation接口记得在微信公众平台申请一下)

// 获取当前经纬度
const getCurrentLngAndLat = () => {
  return new Promise((resolve, reject) => {
    uni.getLocation({
      type: 'wgs84',
      success: res => {
        let lng = res.longitude.toString()
        let lat = res.latitude.toString()
        uni.request({
          method: 'GET',
          url: `https://apis.map.qq.com/ws/geocoder/v1/?location=${lat},${lng}&key=你的key`,
          success: address => {
            const { standard_address } = address.data.result.formatted_addresses
            resolve({
              lng,
              lat,
              currentSignAddress: standard_address
            })
          },
          fail: errAddress => {
            reject({ message: errAddress.message })
          }
        })
      },
      fail: err => {
        getCurrentAddress()
      }
    })
  })
}
// 授权地理位置
const getCurrentAddress = () => {
  uni.authorize({
    scope: 'scope.userLocation',
    success() {
      getCurrentLngAndLat()
    },
    fail(err) {
      uni.showModal({
        title: '请您授权地理位置',
        content: '如需正常使用此小程序打卡功能,请您按确定并在设置页面授权地理位置授权',
        confirmText: '确定',
        success: res => {
          if (res.confirm) {
            uni.openSetting({
              success: function(res) {
                if (res.authSetting['scope.userLocation']) {
                  uni.showToast({
                    title: '授权成功',
                    icon: 'none'
                  })
                  getCurrentLngAndLat()
                } else {
                  // 不允许
                  uni.showToast({
                    title: '授权失败',
                    icon: 'none'
                  })
                }
              },
              fail: err => {
                console.log('err:', err)
              }
            })
          } else { //取消
            uni.showToast({
              title: '取消授权',
              icon: 'none'
            })
          }
        },
        fail: err => {
          console.log('err:', err)
        }
      })
    }
  })
}


export default getCurrentLngAndLat

代码参考:uniapp自定义水印相机_vue+uniapp 拍照+水印-CSDN博客 

  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值