|
|
@@ -0,0 +1,216 @@
|
|
|
+<template>
|
|
|
+ <view v-if="visible" class="preview-mask" @touchstart.stop @touchmove.stop>
|
|
|
+ <view class="preview-header" :style="{ paddingTop: safeTop + 'px' }">
|
|
|
+ <view class="left" @touchstart.stop="close">‹</view>
|
|
|
+ <view class="title">图片预览</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="preview-body">
|
|
|
+ <image v-if="watermarkSrc" class="preview-image" :src="watermarkSrc" mode="scaleToFill" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="preview-footer" :style="{ paddingBottom: safeBottom + 'px' }">
|
|
|
+ <view class="btn" @touchstart.stop="$emit('retake')">重拍</view>
|
|
|
+ <view class="btn primary" @touchstart.stop="download">下载照片</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 隐藏 canvas -->
|
|
|
+ <canvas
|
|
|
+ canvas-id="watermarkCanvas"
|
|
|
+ :style="{
|
|
|
+ width: canvasWidth + 'px',
|
|
|
+ height: canvasHeight + 'px',
|
|
|
+ position: 'fixed',
|
|
|
+ left: '-9999px',
|
|
|
+ }"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, watch } from 'vue'
|
|
|
+import { getCurrentInstance } from 'vue'
|
|
|
+
|
|
|
+import dayjs from 'dayjs'
|
|
|
+
|
|
|
+import { useSaveToAlbum } from '@/composables/photo/useSaveToAlbum'
|
|
|
+
|
|
|
+const visible = defineModel<boolean>('visible', { default: false })
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ src: string
|
|
|
+ address: string
|
|
|
+}>()
|
|
|
+
|
|
|
+defineEmits<{
|
|
|
+ (e: 'retake'): void
|
|
|
+}>()
|
|
|
+
|
|
|
+const system = uni.getSystemInfoSync()
|
|
|
+const safeTop = system.safeAreaInsets?.top ?? 0
|
|
|
+const safeBottom = system.safeAreaInsets?.bottom ?? 0
|
|
|
+
|
|
|
+const watermarkSrc = ref('')
|
|
|
+const canvasWidth = ref(0)
|
|
|
+const canvasHeight = ref(0)
|
|
|
+
|
|
|
+const close = () => {
|
|
|
+ visible.value = false
|
|
|
+}
|
|
|
+
|
|
|
+const { saveToAlbum } = useSaveToAlbum()
|
|
|
+
|
|
|
+const download = () => {
|
|
|
+ if (!watermarkSrc.value) return
|
|
|
+ saveToAlbum(watermarkSrc.value)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 核心:生成水印图
|
|
|
+ */
|
|
|
+
|
|
|
+const instance = getCurrentInstance()
|
|
|
+const generateWatermark = (fileUrl: string, address: string) => {
|
|
|
+ console.log('fileUrl', fileUrl)
|
|
|
+ console.log('address', address)
|
|
|
+
|
|
|
+ return new Promise<string>((resolve, reject) => {
|
|
|
+ uni.getImageInfo({
|
|
|
+ src: fileUrl,
|
|
|
+ success(res) {
|
|
|
+ canvasWidth.value = res.width
|
|
|
+ canvasHeight.value = res.height
|
|
|
+
|
|
|
+ const ctx = uni.createCanvasContext('watermarkCanvas', instance)
|
|
|
+
|
|
|
+ // 原图
|
|
|
+ ctx.drawImage(fileUrl, 0, 0, res.width, res.height)
|
|
|
+
|
|
|
+ const scale = res.width / 375
|
|
|
+ const padding = 12 * scale
|
|
|
+ const fontSize = 12 * scale
|
|
|
+ const lineHeight = 18 * scale
|
|
|
+
|
|
|
+ const textList = [address, dayjs().format('YYYY/MM/DD HH:mm:ss')]
|
|
|
+
|
|
|
+ ctx.setFontSize(fontSize)
|
|
|
+ const textWidth = Math.max(...textList.map((t) => ctx.measureText(t).width))
|
|
|
+
|
|
|
+ const boxWidth = textWidth + padding * 2
|
|
|
+ const boxHeight = lineHeight * textList.length + padding
|
|
|
+
|
|
|
+ const x = padding
|
|
|
+ const y = res.height - boxHeight - padding
|
|
|
+
|
|
|
+ ctx.setFillStyle('rgba(0,0,0,0.45)')
|
|
|
+ ctx.fillRect(x, y, boxWidth, boxHeight)
|
|
|
+
|
|
|
+ ctx.setFillStyle('#fff')
|
|
|
+ textList.forEach((text, i) => {
|
|
|
+ ctx.fillText(text, x + padding, y + padding + lineHeight * (i + 1) - 4)
|
|
|
+ })
|
|
|
+
|
|
|
+ ctx.draw(false, () => {
|
|
|
+ uni.canvasToTempFilePath(
|
|
|
+ {
|
|
|
+ canvasId: 'watermarkCanvas',
|
|
|
+ fileType: 'jpg',
|
|
|
+ success(r) {
|
|
|
+ resolve(r.tempFilePath)
|
|
|
+ },
|
|
|
+ fail: reject,
|
|
|
+ },
|
|
|
+ instance
|
|
|
+ )
|
|
|
+ })
|
|
|
+ },
|
|
|
+ fail: reject,
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * src 或 address 变化时,重新生成水印
|
|
|
+ */
|
|
|
+watch(
|
|
|
+ () => [props.src, props.address],
|
|
|
+ async ([src, address]) => {
|
|
|
+ if (!src || !address) return
|
|
|
+ try {
|
|
|
+ watermarkSrc.value = await generateWatermark(src, address)
|
|
|
+ console.log('watermarkSrc.value', watermarkSrc.value)
|
|
|
+ } catch (error) {
|
|
|
+ console.log('err', error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+)
|
|
|
+watch(visible, (val) => {
|
|
|
+ if (val) {
|
|
|
+ uni.hideTabBar({ animation: false })
|
|
|
+ } else {
|
|
|
+ uni.showTabBar({ animation: false })
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.preview-mask {
|
|
|
+ position: fixed;
|
|
|
+ inset: 0;
|
|
|
+ background: #000;
|
|
|
+ z-index: 9999;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-header {
|
|
|
+ height: 88rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ color: #fff;
|
|
|
+ padding: 0 24rpx;
|
|
|
+ box-sizing: content-box;
|
|
|
+
|
|
|
+ .left {
|
|
|
+ width: 88rpx;
|
|
|
+ font-size: 40rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 32rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.preview-body {
|
|
|
+ height: calc(100vh - 88rpx - 128rpx);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ .preview-image {
|
|
|
+ height: calc(100vh - 88rpx - 128rpx);
|
|
|
+ max-width: 100%;
|
|
|
+ width: 750rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.preview-footer {
|
|
|
+ height: 128rpx;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 48rpx;
|
|
|
+ color: #fff;
|
|
|
+
|
|
|
+ .btn {
|
|
|
+ font-size: 32rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .primary {
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|