bug
This commit is contained in:
parent
c021e982c6
commit
ba09440728
|
|
@ -108,11 +108,26 @@
|
||||||
<text class="submit-btn-text">{{ submitting ? '提交中...' : '提交故障' }}</text>
|
<text class="submit-btn-text">{{ submitting ? '提交中...' : '提交故障' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 离屏 canvas 用于水印绘制(APP端需要在屏幕内才能渲染) -->
|
||||||
|
<canvas
|
||||||
|
canvas-id="watermarkCanvas"
|
||||||
|
:style="{
|
||||||
|
position: 'fixed',
|
||||||
|
left: '0px',
|
||||||
|
top: '0px',
|
||||||
|
width: canvasW + 'px',
|
||||||
|
height: canvasH + 'px',
|
||||||
|
opacity: 0,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
zIndex: -1
|
||||||
|
}"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive, getCurrentInstance, nextTick } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { addFault } from '@/services/trunk'
|
import { addFault } from '@/services/trunk'
|
||||||
import { addWatermark } from '@/utils/watermark'
|
import { addWatermark } from '@/utils/watermark'
|
||||||
|
|
@ -121,6 +136,9 @@ const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
||||||
const photoList = ref([])
|
const photoList = ref([])
|
||||||
const cableId = ref('')
|
const cableId = ref('')
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
|
const canvasW = ref(100)
|
||||||
|
const canvasH = ref(100)
|
||||||
|
const instance = getCurrentInstance()
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
faultTime: '',
|
faultTime: '',
|
||||||
|
|
@ -288,14 +306,25 @@ async function handleSubmit() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 水印处理
|
// 水印处理
|
||||||
const watermarkText = `${form.faultTime} ${form.personnel}`
|
const watermarkLines = [
|
||||||
|
`${form.faultTime} ${form.personnel}`,
|
||||||
|
`经度:${form.longitude} 纬度:${form.latitude}`
|
||||||
|
]
|
||||||
const watermarkedPhotos = []
|
const watermarkedPhotos = []
|
||||||
for (const photo of photoList.value) {
|
for (const photo of photoList.value) {
|
||||||
try {
|
try {
|
||||||
const result = await addWatermark(photo, watermarkText)
|
const result = await addWatermark(photo, watermarkLines, {
|
||||||
|
canvasId: 'watermarkCanvas',
|
||||||
|
proxy: instance.proxy,
|
||||||
|
setSize(w, h) {
|
||||||
|
canvasW.value = w
|
||||||
|
canvasH.value = h
|
||||||
|
}
|
||||||
|
})
|
||||||
watermarkedPhotos.push(result)
|
watermarkedPhotos.push(result)
|
||||||
|
// 每张图处理完后等一下,让 canvas 状态重置
|
||||||
|
await nextTick()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 水印失败则使用原图
|
|
||||||
watermarkedPhotos.push(photo)
|
watermarkedPhotos.push(photo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,141 @@
|
||||||
/**
|
/**
|
||||||
* 在照片左下角叠加水印文字
|
* 在照片左下角叠加多行水印文字
|
||||||
* @param {string} imagePath - 原始图片路径
|
* @param {string} imagePath - 原始图片路径
|
||||||
* @param {string} text - 水印文字(如 "2025/06/15 12:00 张三")
|
* @param {string|string[]} lines - 水印文字,字符串或字符串数组
|
||||||
* @returns {Promise<string>} 带水印的临时文件路径(H5 返回 base64 dataURL)
|
* @param {object} [canvasCtx] - APP端传入 { canvasId, proxy, setSize }
|
||||||
|
* @returns {Promise<string>} 带水印的图片路径,失败时返回原图
|
||||||
*/
|
*/
|
||||||
export function addWatermark(imagePath, text) {
|
export function addWatermark(imagePath, lines, canvasCtx) {
|
||||||
return new Promise((resolve, reject) => {
|
if (typeof lines === 'string') {
|
||||||
// #ifdef APP-PLUS
|
lines = [lines]
|
||||||
uni.getImageInfo({
|
}
|
||||||
src: imagePath,
|
|
||||||
success(imgInfo) {
|
|
||||||
const width = imgInfo.width
|
|
||||||
const height = imgInfo.height
|
|
||||||
const bitmap = new plus.nativeObj.Bitmap('watermark')
|
|
||||||
bitmap.load(imagePath, () => {
|
|
||||||
const canvas = new plus.nativeObj.View('watermarkView', {
|
|
||||||
left: '0px', top: '0px',
|
|
||||||
width: width + 'px', height: height + 'px'
|
|
||||||
})
|
|
||||||
canvas.drawBitmap(bitmap, {}, { left: '0px', top: '0px', width: width + 'px', height: height + 'px' })
|
|
||||||
const fontSize = Math.max(Math.floor(width * 0.03), 14)
|
|
||||||
const padding = Math.floor(fontSize * 0.8)
|
|
||||||
const textX = padding
|
|
||||||
const bgHeight = fontSize + padding * 2
|
|
||||||
canvas.drawRect(
|
|
||||||
{ color: 'rgba(0,0,0,0.4)' },
|
|
||||||
{ left: '0px', top: (height - bgHeight) + 'px', width: width + 'px', height: bgHeight + 'px' }
|
|
||||||
)
|
|
||||||
canvas.drawText(text, {
|
|
||||||
left: textX + 'px',
|
|
||||||
top: (height - bgHeight + padding) + 'px',
|
|
||||||
width: (width - textX * 2) + 'px',
|
|
||||||
height: fontSize + 'px'
|
|
||||||
}, { size: fontSize + 'px', color: '#ffffff' })
|
|
||||||
const tempPath = `_doc/watermark_${Date.now()}.jpg`
|
|
||||||
canvas.toBitmap(tempPath, {}, () => {
|
|
||||||
bitmap.clear()
|
|
||||||
resolve(tempPath)
|
|
||||||
}, (err) => {
|
|
||||||
bitmap.clear()
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
}, (err) => reject(err))
|
|
||||||
},
|
|
||||||
fail: reject
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef H5
|
return new Promise((resolve) => {
|
||||||
|
// 超时保护:10秒
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
console.warn('[watermark] timeout')
|
||||||
|
resolve(imagePath)
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
|
_doWatermark(imagePath, lines, canvasCtx)
|
||||||
|
.then((r) => { clearTimeout(timer); resolve(r || imagePath) })
|
||||||
|
.catch((e) => { clearTimeout(timer); console.warn('[watermark]', e); resolve(imagePath) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function _doWatermark(imagePath, lines, canvasCtx) {
|
||||||
|
// #ifdef H5
|
||||||
|
return _h5(imagePath, lines)
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
return _app(imagePath, lines, canvasCtx)
|
||||||
|
// #endif
|
||||||
|
// eslint-disable-next-line no-unreachable
|
||||||
|
return Promise.resolve(imagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== H5 ==========
|
||||||
|
// #ifdef H5
|
||||||
|
function _h5(imagePath, lines) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.crossOrigin = 'anonymous'
|
img.crossOrigin = 'anonymous'
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
const width = img.naturalWidth
|
try {
|
||||||
const height = img.naturalHeight
|
const w = img.naturalWidth, h = img.naturalHeight
|
||||||
const canvas = document.createElement('canvas')
|
const c = document.createElement('canvas')
|
||||||
canvas.width = width
|
c.width = w; c.height = h
|
||||||
canvas.height = height
|
const ctx = c.getContext('2d')
|
||||||
const ctx = canvas.getContext('2d')
|
ctx.drawImage(img, 0, 0, w, h)
|
||||||
ctx.drawImage(img, 0, 0, width, height)
|
_stamp(ctx, w, h, lines, false)
|
||||||
const fontSize = Math.max(Math.floor(width * 0.03), 14)
|
resolve(c.toDataURL('image/jpeg', 0.9))
|
||||||
const padding = Math.floor(fontSize * 0.8)
|
} catch (e) { reject(e) }
|
||||||
const bgHeight = fontSize + padding * 2
|
|
||||||
ctx.fillStyle = 'rgba(0,0,0,0.4)'
|
|
||||||
ctx.fillRect(0, height - bgHeight, width, bgHeight)
|
|
||||||
ctx.fillStyle = '#ffffff'
|
|
||||||
ctx.font = `${fontSize}px sans-serif`
|
|
||||||
ctx.textBaseline = 'middle'
|
|
||||||
ctx.fillText(text, padding, height - bgHeight / 2)
|
|
||||||
resolve(canvas.toDataURL('image/jpeg', 0.9))
|
|
||||||
}
|
}
|
||||||
img.onerror = (err) => reject(err || new Error('图片加载失败'))
|
img.onerror = reject
|
||||||
img.src = imagePath
|
img.src = imagePath
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ
|
|
||||||
// 小程序端:跳过水印,直接返回原图
|
|
||||||
// resolve(imagePath)
|
|
||||||
// #endif
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// ========== APP ==========
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
function _app(imagePath, lines, cc) {
|
||||||
|
if (!cc || !cc.canvasId || !cc.proxy) {
|
||||||
|
return Promise.reject(new Error('missing canvasCtx'))
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.getImageInfo({
|
||||||
|
src: imagePath,
|
||||||
|
success(info) {
|
||||||
|
try {
|
||||||
|
// 限制 canvas 宽度 ≤ 1200,避免内存问题
|
||||||
|
let dw = info.width, dh = info.height
|
||||||
|
if (dw > 1200) { const r = 1200 / dw; dw = 1200; dh = Math.round(info.height * r) }
|
||||||
|
|
||||||
|
// 更新页面 canvas 尺寸
|
||||||
|
if (typeof cc.setSize === 'function') cc.setSize(dw, dh)
|
||||||
|
|
||||||
|
// 等 DOM 刷新(200ms 足够 APP webview 更新)
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
const ctx = uni.createCanvasContext(cc.canvasId, cc.proxy)
|
||||||
|
ctx.drawImage(imagePath, 0, 0, dw, dh)
|
||||||
|
_stamp(ctx, dw, dh, lines, true)
|
||||||
|
|
||||||
|
let done = false
|
||||||
|
const exp = () => {
|
||||||
|
if (done) return
|
||||||
|
done = true
|
||||||
|
uni.canvasToTempFilePath({
|
||||||
|
canvasId: cc.canvasId,
|
||||||
|
x: 0, y: 0, width: dw, height: dh,
|
||||||
|
destWidth: info.width, destHeight: info.height,
|
||||||
|
fileType: 'jpg', quality: 0.9,
|
||||||
|
success: (r) => resolve(r.tempFilePath),
|
||||||
|
fail: reject
|
||||||
|
}, cc.proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.draw(false, () => setTimeout(exp, 200))
|
||||||
|
// 兜底
|
||||||
|
setTimeout(exp, 1200)
|
||||||
|
} catch (e) { reject(e) }
|
||||||
|
}, 200)
|
||||||
|
} catch (e) { reject(e) }
|
||||||
|
},
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 canvas context 上绘制水印条
|
||||||
|
* @param {boolean} isUni - true 用 uni canvas API,false 用 H5 canvas API
|
||||||
|
*/
|
||||||
|
function _stamp(ctx, w, h, lines, isUni) {
|
||||||
|
const fs = Math.max(Math.floor(w * 0.03), 14)
|
||||||
|
const pad = Math.floor(fs * 0.8)
|
||||||
|
const lh = fs + pad
|
||||||
|
const bgH = lh * lines.length + pad
|
||||||
|
|
||||||
|
if (isUni) {
|
||||||
|
ctx.setFillStyle('rgba(0,0,0,0.4)')
|
||||||
|
ctx.fillRect(0, h - bgH, w, bgH)
|
||||||
|
ctx.setFillStyle('#ffffff')
|
||||||
|
ctx.setFontSize(fs)
|
||||||
|
lines.forEach((t, i) => {
|
||||||
|
// uni canvas 的 fillText y 是文字顶部基线
|
||||||
|
ctx.fillText(t, pad, h - bgH + pad + lh * i)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = 'rgba(0,0,0,0.4)'
|
||||||
|
ctx.fillRect(0, h - bgH, w, bgH)
|
||||||
|
ctx.fillStyle = '#ffffff'
|
||||||
|
ctx.font = `${fs}px sans-serif`
|
||||||
|
ctx.textBaseline = 'middle'
|
||||||
|
lines.forEach((t, i) => {
|
||||||
|
ctx.fillText(t, pad, h - bgH + pad / 2 + lh * i + fs / 2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user