uniappx 安卓拍照,添加水印后比例正常

其中还包含了图片数据在storage中和本地目录保存的逻辑

javascript 复制代码
<template>
	<!-- #ifdef APP -->
	<view style="position: relative;">
		<scroll-view class="page-scroll-view">
		<!-- #endif -->
			<view class="uni-common-mt">
				<view class="uni-list list-pd" style="padding: 15px;">
					{{date}}
					<view class="uni-flex" style="margin-bottom: 10px;">

						<view style="margin-left: auto;">
							<text class="click-t">{{ previewImgList.length }}/{{ count }}</text>
						</view>
					</view>
					<view class="uni-flex" style="flex-wrap: wrap;">
						<view class="uni-flex">
							<view v-for="(item, index) in previewImgList" :key="index" class="uni-uploader__input-box"
								style="border: 0;">
								<image style="width: 104px; height: 104px;" :src="item.path" :data-src="item.path"
									@tap="previewImage(index)">
								</image>
								<text class="status-text">{{item.status}}</text>
								<image src="/static/plus.png" class="image-remove" @click="removeImage(index)"></image>
							</view>
						</view>
						<image class="uni-uploader__input-box" @tap="chooseImage" src="/static/plus.png"></image>
					</view>
				</view>
				<button style="width: 160px;" @click="startUpload">开始上传</button>
			</view>
			<canvas id="canvas" ref="canvasRef" class="canvas-element"> </canvas>
		<!-- #ifdef APP -->
		</scroll-view>
	</view>
	<!-- #endif -->
</template>

<script>
	// import dayjs from 'dayjs'
	// 高清适配(Retina 屏 / 高像素密度屏幕) 的核心逻辑,解决 Canvas 绘制内容模糊的问题
	// function hidpi(canvas : UniCanvasElement) {
	// 	const context = canvas.getContext("2d")!;
	// 	const dpr = uni.getWindowInfo().pixelRatio;
	// 	canvas.width = canvas.offsetWidth * dpr;
	// 	canvas.height = canvas.offsetHeight * dpr;
	// 	context.scale(dpr, dpr);
	// }
	type ImageList = {
		id : number,
		updateTime : string
		path : string
		status : string
	}
	export default {
		data() {
			return {
				// 响应式数据
				// date: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
				date: "2026-01-01",
				previewImgList: [] as ImageList[],
				count: 9,
				isContinue: false,
				// canvas相关
				canvas: null as UniCanvasElement | null,
				canvasContext: null as CanvasContext | null,
				renderingContext: null as CanvasRenderingContext2D | null,
				// #ifndef MP
				basePath: uni.env.USER_DATA_PATH,
				copyToBasePath: uni.env.USER_DATA_PATH,
				globalTempPath: uni.env.CACHE_PATH,
				globalRootPath: uni.env.SANDBOX_PATH,
				globalUserDataPath: uni.env.USER_DATA_PATH,
				// #endif
				// 照片存-文件读取路径/*  */
				localFilePath: `${uni.env.USER_DATA_PATH}/path`,
				// 创建文件管理器
				fileSystemManager: null as FileSystemManager | null,
				uploadTask: null as UploadTask | null,
			}
		},
		onLoad() {
			this.fileSystemManager = uni.getFileSystemManager()

			// console.log('fileSystemManager',this.fileSystemManager)
			// console.log('uni.env.USER_DATA_PATH', uni.env.USER_DATA_PATH, uni.env.CACHE_PATH)
			// 清空调试数据专用
			// if(this.fileSystemManager!==null){
			// 	this.fileSystemManager.rmdirSync(this.basePath as string,true)
			// }

			// uni.removeStorageSync('waitUploadImg')
			// HBuilderX 4.25+
			// 异步调用方式, 跨平台写法
			uni.createCanvasContextAsync({
				id: 'canvas',
				component: this,
				success: (context : CanvasContext) => {
					this.canvasContext = context;
					this.renderingContext = context.getContext('2d')!;  //Canvas 2D上下文
					this.canvas = this.renderingContext.canvas;
					// console.log('this.renderingContext', this.renderingContext)
				}
			})
		},
		onShow() {
			this.previewImgList = []
			// 获取缓存,存入imgList,展示图片
			// 1.读取storage中的元数据,--避免漏传\重复上传(校验状态:成功跳过,失败重置校验次数,更改为待上传,检查文件是否存在)
			// 	uni.setStorageSync('waitUploadImg', [{ id: '11', updateTime: '10:20', status: 'success', path: `${this.basePath}/img1.png` }, { id: '22', updateTime: '10:22', status: 'error', path: `${this.basePath}/img.png` }])
			uni.getStorage({
				key: "waitUploadImg",
				success: (res : GetStorageSuccess) => {
					if (res.data !== null) {
						// 从 storage 获取的是 UTSJSONObject 数组
						const stor = res.data as UTSJSONObject[]
						// 转换为 ImageList 数组
						const imageListArray : ImageList[] = stor.map((item : UTSJSONObject) => {
							// 根据你的 ImageList 结构进行转换
							return {
								id: item['id'] as number,
								updateTime: item['updateTime'] as string,
								path: item['path'] as string,
								status: item['status'] as string,
							} as ImageList
						})

						this.previewImgList.push(...imageListArray)
						// this.previewImgList = imageListArray
						console.log('获取storage--onShow', this.previewImgList)
					}
				},
				fail: () => {

				},
				complete: (res : any) => {
					// console.log('complete', res)
				}
			})

			// 请求接口,获取文件列表
			setTimeout(() => {
				this.previewImgList.concat([] as ImageList[])
			}, 300)

		},
		onUnload() {

			console.log('onUnload')
		},
		onHide() {

			console.log('隐藏页面', uni.getStorageSync('waitUploadImg'))
			this.uploadTask?.abort()
			// 根据业务逻辑判断是否删除
			// 根据元数据的success数据删除文件管理器中的文件,然后删除元数据中成功的数据
			const success = this.previewImgList.filter(i => i.status == 'success')
			if (success.length > 0) {


				// 删除文件
				success.forEach(i => {
					this.fileSystemManager!.removeSavedFile({
						filePath: i.path,
						success: () => {

						},
						fail: () => {

						}
					})
				})

				// 跟新元数据

				const unSuccess = this.previewImgList.filter(i => i.status != 'success')
				uni.setStorageSync('waitUploadImg', unSuccess)

				console.log('移除成功的缓存', uni.getStorageSync('waitUploadImg'))
			}
		},
		methods: {
			/**
			   * 绘制水印核心逻辑(原图+文字水印+LOGO水印)
			   */
			getTimeString() : string {
				const d = new Date();
				const h = d.getHours() > 9 ? d.getHours().toString() : '0' + d.getHours();
				const m = d.getMinutes() > 9 ? d.getMinutes().toString() : '0' + d.getMinutes();
				return h + ":" + m;
			},
			async drawWatermark(imageInfo : GetImageInfoSuccess) {
				uni.createCanvasContextAsync({
					id: 'canvas',
					component: this,
					success: (context : CanvasContext) => {
						this.canvasContext = context;
						this.renderingContext = context.getContext('2d')!;  //Canvas 2D上下文
						this.canvas = this.renderingContext.canvas;
						console.log('this.renderingContext', this.renderingContext)

						let width = imageInfo['width'] as number
						let height = imageInfo['height'] as number

						const path = imageInfo['path'] as string
						const canvasDom = uni.getElementById('canvas') as UniCanvasElement

						if (this.canvas !== null) {
							const dpr = uni.getWindowInfo().pixelRatio;
							console.log('width', width, width / dpr)
							console.log('height', height, height / dpr)
							this.canvas.width = width / dpr;
							this.canvas.height = height / dpr;
							// this.canvas.width = 300 ;
							// this.canvas.height = 300 ;
							// this.renderingContext.scale(3,3)
							canvasDom.style.setProperty('width', width / dpr + 'px');
							canvasDom.style.setProperty('height', height / dpr + 'px');
						}


						setTimeout(() => {
							const ctx = canvasDom.getContext('2d')!;
							console.log('canvasDomSize', width, height, ctx)
							try {
								// 清空画布
								ctx.clearRect(0, 0, width, height);

								// 1. 绘制原图到Canvas
								let image = this.canvasContext!.createImage();
								image.src = path
								image.onload = () => {
									ctx.drawImage(image, 0, 0, width, height);

									const fontSize = width / 25;
									ctx.font = "bold " + fontSize + "px sans-serif";
									ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
									ctx.textAlign = "right";
									ctx.textBaseline = "bottom";

									const text = "Watermark " + this.getTimeString();
									const padding = fontSize;

									ctx.fillText(text, width - padding, height - padding);
									console.log('ctx is', ctx)
									setTimeout(() => {
										canvasDom!.takeSnapshot({
											success: (snapshotRes) => {
												// console.log("水印成功:", snapshotRes);
												// uni.getImageInfo({
												// 	src: snapshotRes.tempFilePath,
												// 	success: (r1) => {
												// 		console.log('截图成功后图片信息', r1)
												// 		console.log('截图成功后Canvas信息', this.renderingContext)
												// 	}
												// })

												this.saveFile(snapshotRes.tempFilePath)
												// 1.把this.imgList的数据存储到storage中,成功一个从storage中移除一个
												uni.setStorageSync('waitUploadImg', this.previewImgList)



											},
											fail: (err) => {
												console.error("截图失败", err);
											}
										});

									}, 300)
									// uni.showToast({ title: '水印添加成功', icon: 'success' });
								}


							} catch (err) {
								console.error('绘制水印失败:', err);
								uni.showToast({ title: '水印添加失败', icon: 'none' });
							}
						}, 0)
					}
				})
			},



			removeImage(index : number) {
				this.previewImgList.splice(index, 1)
			},

			chooseImage() {
				if (this.previewImgList.length >= this.count) {
					uni.showToast({
						position: "bottom",
						title: `已经有 ${this.count} 张图片了,请删除部分图片之后重新选择`
					})
					return
				}

				uni.chooseImage({
					sourceType: ['camera'],
					sizeType: ['compressed'],
					count: this.count - this.previewImgList.length,
					success: (res) => {
						// 文件的临时路径,在应用本次启动期间可以正常使用,如需持久保存,需在主动调用 uni.saveFile,在应用下次启动时才能访问得到。
						const imagePath = res.tempFilePaths[0];
						uni.getImageInfo({
							src: imagePath,
							success: (r) => {
								// 2.canvas绘制水印,并合成图片
								this.drawWatermark(r);
							}
						})
					},
					fail: (err) => {
						console.log("err: ", JSON.stringify(err))
						uni.showToast({
							title: `choose image error.code:${err.errCode};message:${err.errMsg}`,
							position: "bottom"
						})
					}
				})
			},

			/**
			 * 开始/继续上传图片
			 */
			startUpload() {
				this.handleBatchUpload(this.previewImgList)
			},
			async handleBatchUpload(dataList : ImageList[]) {
				const list = dataList.filter(i => (i.status == 'error' || i.status == 'ready')) as ImageList[];
				for (const i of list) {
					const index = this.previewImgList.findIndex(j => j.id == i.id)
					try {
						const uploadRes = await this.upload(i.path as string, index, { time: i.updateTime }) as boolean;
						if (uploadRes) {
							// 上传成功 --更新上传状态,持久化存储
							if (index != -1) {
								this.previewImgList[index].status = 'success'
							}

						} else {
							// 上传失败
							if (index != -1) {
								this.previewImgList[index].status = 'error'
							}
						}
						// 即时更新元数据
						uni.setStorage({
							key: 'waitUploadImg',
							// data:JSON.stringify(this.previewImgList)
							data: this.previewImgList

						})
					} catch (error) {
						console.log('error', error)
					}
				}

				// 所有文件上传完成后的逻辑
				uni.showToast({ title: '批量上传完成', icon: 'success' });

			},
			upload(path : string, index : number, params : UTSJSONObject) {
				console.log('upload', path)
				return new Promise((resolve, reject) => {
					this.uploadTask = uni.uploadFile({
						url: 'http://192.168.101.220:5071/lmy/upload',
						filePath: path,
						formData: { testPr: params.time },
						name: 'file',
						success: (result : UploadFileSuccess) => {
							console.log('上传成功', result)
							// 再根据响应值判断是否上传成功
							resolve(true)
						}, fail: (result : UploadFileFail) => {
							console.log('上传失败', result)
							resolve(false)
						},
						complete: (res : any) => {
							console.log('上传完成complete', res)
							this.uploadTask = null
						}
					})
					this.uploadTask.onProgressUpdate((result : OnProgressUpdateResult) => {
						console.log('上传进度result', result.progress)
						// 	// 监听上传进度--标记状态为上传中

						if (result.progress > 0 && result.progress < 1) {
							if (index != -1) {
								this.previewImgList[index].status = 'uploading'
							}
						}
					})

				})

			},
			saveFile(tempPath : string) {
				// 创建文件夹
				// 保存文件到用户目录 保存成功后临时路径会失效
				const pathArr = tempPath.split('/')
				const fileName = pathArr[pathArr.length - 1]
				if (this.fileSystemManager !== null) {

					function saveFn() {
						this.fileSystemManager!.saveFile({
							tempFilePath: `${tempPath}`,
							filePath: `${this.localFilePath}/${fileName}`,
							success: (res1 : SaveFileSuccessResult) => {
								console.log('res1', res1)
								this.previewImgList.push({
									id: Math.random(),  //后面改-英文数字随机32位id
									updateTime: this.getTimeString(),  //new Date().getTime()
									status: 'ready',  //准备(待)上传ready,上传中uploading,上传成功success,上传失败error
									path: `${this.localFilePath}/${fileName}`
								} as ImageList)

							}, fail: (res2 : FileSystemManagerFail) => {
								console.log('res2', res2)
							},
						} as SaveFileOptions)
					}

					this.fileSystemManager.readdir({
						dirPath: `${this.localFilePath}`,
						success: (res : ReadDirSuccessResult) => {
							console.log('文件管理器路径存在666', res)
							saveFn()
						},
						fail: () => {
							this.fileSystemManager!.mkdirSync(`${this.localFilePath}`, true)
							saveFn()
						}
					})


				}

			},
			/**
			 * 预览指定索引的图片
			 * @param index 图片索引
			 */
			previewImage(index : number) {
				uni.previewImage({
					current: index,
					urls: this.previewImgList.map(i => i.path)
				})
			}
		},
		unmounted() {
			// 组件卸载时的清理逻辑
		}
	}
</script>

<style>
	.page-scroll-view {
		max-height: 1000px;
	}

	.uni-flex {
		display: flex;
	}

	.uni-uploader__input-box {
		margin: 5px;
		width: 104px;
		height: 104px;
		border: 1px solid #D9D9D9;
	}

	.uni-uploader__input {
		position: absolute;
		z-index: 1;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		opacity: 0;
	}

	.image-remove {
		transform: rotate(45deg);
		width: 25px;
		height: 25px;
		position: absolute;
		top: 0;
		right: 0;
		border-radius: 13px;
		background-color: rgba(200, 200, 200, 0.8);
	}

	.status-text {
		position: absolute;
		bottom: 0;
		right: 50%;
		transform: translateX(-50%);
		background-color: rgba(200, 200, 200, 0.8);
		color: #fff;
	}

	.item_width {
		width: 130px;
	}

	.crop-option {
		margin-left: 11px;
		margin-right: 11px;
		border-radius: 11px;
		background-color: #eee;
		transition-property: height, margin-bottom;
		transition-duration: 200ms;
	}

	.canvas-element {
		/* 	position: absolute;
		left: 1000px;
		bottom: 0;
		min-width: 375px;
		min-height: 500px; */
	}
</style>
相关推荐
野生风长2 小时前
从零开始的C语言:文件操作与数据管理(下)(fseek,ftell,rewind,文件的编译和链接)
android·java·c语言·开发语言·visual studio
2501_916007472 小时前
Xcode 在 iOS 上架中的定位,多工具组合
android·macos·ios·小程序·uni-app·iphone·xcode
游戏开发爱好者82 小时前
uni-app 项目在 iOS 上架过程中常见的问题与应对方式
android·ios·小程序·https·uni-app·iphone·webview
2501_915106322 小时前
iOS 抓包工具在不同场景的实际作用
android·macos·ios·小程序·uni-app·cocoa·iphone
草莓熊Lotso2 小时前
C++ 异常完全指南:从语法到实战,优雅处理程序错误
android·java·开发语言·c++·人工智能·经验分享·后端
モンキー・D・小菜鸡儿2 小时前
Android BottomSheetBehavior 使用详解
android·kotlin
帅得不敢出门2 小时前
Android Framework不弹窗设置默认sim卡
android·java·framework
技术摆渡人2 小时前
RK3588 边缘 AI 深度开发指南:从 Android NNAPI 源码到 LLM 大模型性能调优
android·人工智能
sinat_384241093 小时前
从零开始打造一个 Android 音乐播放器(Kotlin + Jetpack Compose)
android·开发语言·kotlin