| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 | <template>	<!-- #ifdef APP -->	<scroll-view style="flex:1">	<!-- #endif -->		<!--    <camera style="width: 100%; height: 300px;" :resolution="'high'" :mode="'scanCode'" @scancode="handleScanCode">    </camera> -->	<view class="my-page">		<swiper class="swiper-box my-swiper" :current="swiperDotIndex" :indicator-dots="true">			<swiper-item v-for="item in data">				<view class="swiper-item" @tap="previewSingleImage(item.image)">					<image class="my-image" :src="item.image" mode="aspectFill" />				</view>				<view>					<text class="demo-view-label">{{ item?.name ?? ''}}</text>				</view>			</swiper-item>		</swiper>		<view class="camera-scan-code-table">			<view class="camera-scan-code-table-pair" v-if="joinRecord.imgname!=''">				<view class="camera-scan-code-table-pair-label">					<text>已存图片</text>				</view>				<view class="camera-scan-code-table-pair-value" v-for="itemName in joinRecord.imgname.split(',')"   >										<text class="txt">{{ itemName }}</text>				</view>			</view>			<view class="camera-scan-code-table-pair">				<view class="camera-scan-code-table-pair-label">					<text>待存图片</text>				</view>				<view class="camera-scan-code-table-pair-value">										<text class="txt">{{ dyImgName+"-"+minAvailableNumber+".jpg"}}</text>				</view>			</view>		</view>		<view class="camera-scan-code-back-wrap">			<button class="btn block bg-blue lg round" @click="saveImage" style="min-width: 250px;">				保存图片			</button>		</view>		<view class="camera-scan-code-back-wrap">			<button type="default" class="btn bg-blue round" @click="navigateBack">返回拍照</button>		    <button type="default" class="btn bg-blue round" @click="navigateExit">退出拍照</button>		    		</view>    </view>    <!-- 隐藏的canvas用于清晰度检测 -->	<canvas id="canvas" style="position: absolute; left: -9999px; top: -9999px; width: 300px; height: 300px;"></canvas>	<!-- #ifdef APP -->	</scroll-view>	<!-- #endif --></template><script lang="uts">	type CameraScanCodeResult = {		type : string | null;		result : string | null;	}	type ImageItem = {		image : string,		name : string,		checked : boolean	}	import { getJoinList, TaskJoinRecord, updateData } from '@/api/work';	import { BitmapUtils } from '@/nativeplugins/opencv/android/uts/BitmapUtils.uts'		export default {		data() {			return {				result: null as CameraScanCodeResult | null,				swiperDotIndex: 0 as number,				path: '',				recordId: 0,				imgArrLen: 0,				joinRecord: {					sxid : 0,					pdid : 0,					photographpoint : '',					photographdescription : '',					photourl : '',					photourlSpl : [],					imgname : '',					exampleurl : '',					exampleurlSpl : [],					photoname : '',					fk_qcRecord : '',					fk_prodcode : '',					prodno : '',					processStep : '',					workorder:'',					invname: '',				} as TaskJoinRecord,				num: 0,				maxcount: 0,				dyImgName: '',				minAvailableNumber: 1,				data: [{					image: '/static/images/banner/banner01.png',					name: 'banner01.png',					checked: true				}] as ImageItem[],				canvas: null as UniCanvasElement | null,				canvasContext: null as CanvasContext | null,				renderingContext: null as CanvasRenderingContext2D | null,			}		},		onBackPress() {			console.log(this.recordId, this.num, this.joinRecord.pdid);			// 覆盖系统默认的返回行为,返回到指定页面			//PhotoRecord?pdid=			uni.navigateTo({				url: `/pages/work/download/PhotoRecord?pdid=${this.joinRecord.pdid}&senum=${this.num}`,				// 修改动画方向为从左到右退回				animationType: 'slide-in-left', // 使用从左到右滑出的动画效果				animationDuration: 300 // 动画持续时间,单位ms			})			// 返回true表示拦截默认返回行为			return true		},		onLoad(options) {			this.path = options?.['path'] ?? ''			let recordId = options?.['recordId'] ?? ''			let num = options?.['num'] ?? ''			if (num != '') {				this.num = parseInt(num);			}			let maxcount = options?.['maxcount'] ?? ''			if (this.path != '') {				this.data = []				this.data.push({ image: this.path, name: '', checked: true })			}			if (recordId != '') {				this.recordId = parseInt(recordId)				getJoinList('app_task_photo as r', 'app_task_info as i', 'r.*,i.*', 'r.pdid=i.pdid', 'sxid', recordId, null).then((res : UTSJSONObject) => {					let dataList = res?.['data'] as UTSJSONObject[] ?? Array<UTSJSONObject>()					if (dataList != null && dataList.length > 0) {						dataList.forEach(item => {							if (item != null) {								let record = JSON.parse<TaskJoinRecord>(item.toJSONString());								console.log(record);								if (record != null) {									this.dyImgName = record.workorder + record.invname;									this.joinRecord = record									this.imgArrLen = record.imgname.indexOf(",") > -1 ? record.imgname.split(",").length : (record.imgname != '' ? 1 : 0)									// 计算最小可用编号									this.calculateMinAvailableNumber();								}							}						});					}				});			}			if (num != '') {				this.num = parseInt(num)			}			if (maxcount != '') {				this.maxcount = parseInt(maxcount)			}						// 异步调用方式, 跨平台写法			uni.createCanvasContextAsync({			  id: 'canvas',			  component: this,			  success: (context : CanvasContext) => {			    this.canvasContext = context;			    this.renderingContext = context.getContext('2d')!;			    this.canvas = this.renderingContext!.canvas;			  }			})		},		onReady() {		// 确认canvas元素可正常使用		// UTS中不支持uni.createCanvasContext,改用其他方式验证		console.log('Canvas元素已准备就绪');		},		methods: {			navigateBack() {				//uni.navigateBack()				uni.navigateTo({					url: `/pages/work/download/TaskCamera?id=${this.recordId}&num=${this.num}`				})			},			navigateExit() {				uni.navigateTo({					url: `/pages/work/download/DownloadDetail?id=${this.joinRecord.pdid}`				})			},			checkboxChange() {				console.log("选择图片名称")			},			chooseImage() {				uni.chooseImage({					sourceType: ['album'],					success: (res) => {						//this.data = res.tempFilePaths; // 获取选中的图片路径						this.data = []						for (let i = 0; i < res.tempFilePaths.length; i++) {							this.data.push({ image: res.tempFilePaths[i], name: '', checked: true })						}					},					fail: (err) => {						console.error('选择图片失败', err);					}				});			},			async saveImage() {				// 保存图片到数据库的函数				const saveImageToDatabase = () => {					let updateImgs = ''					let updateNames = ''					// 动态生成最小的可用图片编号					const getMinAvailableNumber = () => {						if (this.imgArrLen === 0) return 1;						// 从已有的图片名称中提取编号						const existingNumbers = new Set<number>();						const imgNames = this.joinRecord.imgname.split(',');						imgNames.forEach((name : string) => {							if (name != '') {								// 提取图片名称中的数字部分								const match = name.match(/-(\d+)\.jpg$/);								if (match != null && match[1] != null) {									const numStr = match[1] as string;									existingNumbers.add(parseInt(numStr));								}							}						});						// 查找1到所需图片数量之间最小的未使用编号						for (let i = 1; i <= 10; i++) {							if (!existingNumbers.has(i)) {								return i;							}						}						// 如果1到所需数量都被使用了,则使用当前数量+1						return this.imgArrLen + 1;					};					const minAvailableNumber = getMinAvailableNumber();					const newImgName = this.dyImgName + "-" + minAvailableNumber + ".jpg";									const relativePath = `/uploadImgs/${newImgName}`					if (this.imgArrLen == 0) {						updateImgs = relativePath						updateNames = newImgName;					} else {						updateImgs = this.joinRecord.photourl + "," + relativePath						updateNames = this.joinRecord.imgname + "," + newImgName;					}					let updatedData = "imgname='" + updateNames + "',photourl='" + updateImgs + "'"										updateData('app_task_photo', updatedData, 'sxid', this.recordId.toString()).then((res : UTSJSONObject) => {						let data = res?.['data'] as boolean ?? false						if (data != null && data== true) {							uni.showToast({								title: "保存成功!",							});							const newPath = `${uni.env.USER_DATA_PATH}/uploadImgs/${newImgName}`							this.renameFile(this.path, newPath)							uni.navigateTo({								url: `/pages/work/download/PhotoRecord?pdid=${this.joinRecord.pdid}&senum=${this.num}`,								// 修改动画方向为从左到右退回								animationType: 'slide-in-left', // 使用从左到右滑出的动画效果								animationDuration: 300 // 动画持续时间,单位ms							})						}					});				}				//保存图片进入相册文件				if (this.path == '') {					uni.showToast({						title: '没有拍照文件',						icon: 'error',						duration: 2000					});					return;				}								// 检测图片清晰度				try {					uni.showLoading({ title: '检测图片清晰度...' });					this.renderingContext!.clearRect(0, 0, 1000, 1000)					let image = this.canvasContext!.createImage();					image.src = this.path					let score = 0					let level = ""					image.onload = () => {					  					  this.renderingContext?.drawImage(image, 0, 0, image.width, image.height);					  console.log(image.width)					  console.log(image.height)					  setTimeout(() => {					    try {						  // 将canvas内容转换为base64格式						  const base64Data = this.canvasContext!.toDataURL();						  const result = BitmapUtils.checkImageClarityFromBase64(base64Data)						  console.log(result)						  console.log("清晰度检测结果得分:", result?.['score']);						  score = result?.['score'] as number						  level = result?.['level'] as string					      uni.hideLoading();						  const customThreshold = 3000;						  const isClearWithCustomThreshold = score > customThreshold;						  if (!isClearWithCustomThreshold) {						  	// 不清晰的图片,提示用户						  	uni.showModal({						  		title: '提示',						  		content: `图片清晰度检测结果:${level}\n图片可能不够清晰,是否继续保存?`,						  		success: (res) => {						  			if (!res.confirm) {						  				// 用户取消保存						  				return;						  			}						  			// 用户确认后执行保存流程						  			saveImageToDatabase();						  		},						  		fail: () => {						  			// 模态框失败时继续保存						  			saveImageToDatabase();						  		}						  	});						  } else {						  	// 图片清晰,直接保存						  	saveImageToDatabase();						  }						  					    } catch (error) {					      console.error("转换图片为Base64失败:", error);					    }					  }, 100); 					}								} catch (error) {					uni.hideLoading();					console.error('图片清晰度检测失败:', error);					// 检测失败时继续保存流程					uni.showToast({						title: '清晰度检测失败,继续保存',						icon: 'none',						duration: 2000					});					saveImageToDatabase();				}				return;			},			previewSingleImage(imageUrl : string) {				uni.previewImage({					urls: [imageUrl], // 需要预览的图片链接列表					current: 0, // 当前显示图片的索引					indicator: 'number', // 图片指示器样式					loop: false // 是否可循环预览				});			},			renameFile(oldPath:string, newPath:string) {			    // 先拷贝文件到新路径			    uni.getFileSystemManager().copyFile({			        srcPath: oldPath,			        destPath: newPath,			        success: function () {			            // 可选:删除原文件,如果你需要释放空间的话			            uni.getFileSystemManager().unlink({			                filePath: oldPath,			                success: function () {			                    console.log('原文件已删除');			                },			                fail: function (unlinkErr) {			                    console.error('删除原文件失败', unlinkErr);			                }			            });			            console.log('文件重命名成功');			        },			        fail: function (copyErr) {			            console.error('拷贝文件失败', copyErr);			        }			    });			},						handleScanCode(ev : UniCameraScanCodeEvent) {				const deatil = ev.detail;				this.result = {					type: deatil.type,					result: deatil.result				} as CameraScanCodeResult			},			// 计算最小的可用图片编号			calculateMinAvailableNumber() {				if (this.imgArrLen === 0) {					this.minAvailableNumber = 1;					return;				}				// 从已有的图片名称中提取编号				const existingNumbers = new Set<number>();				const imgNames = this.joinRecord.imgname.split(',');				imgNames.forEach((name : string) => {					if (name != '') {						// 提取图片名称中的数字部分						const match = name.match(/-(\d+)\.jpg$/);						if (match != null && match[1] != null) {							const numStr = match[1] as string;							existingNumbers.add(parseInt(numStr));						}					}				});				// 查找1到所需图片数量之间最小的未使用编号				for (let i = 1; i <= 10; i++) {					if (!existingNumbers.has(i)) {						this.minAvailableNumber = i;						return;					}				}				// 如果1到所需数量都被使用了,则使用当前数量+1				this.minAvailableNumber = this.imgArrLen + 1;			},		}	}</script><style>	.camera-scan-code-back-wrap {		display: flex;		justify-content: center;		align-items: center;		flex-direction: row;	}	.camera-scan-code-table {		background-color: white;		margin-top: 20px;	}	.camera-scan-code-table-pair {		height: 100px;		flex-direction: column;		justify-content: space-between;		align-items: center;	}	.camera-scan-code-table-pair-label {		flex-grow: 1;		margin-left: 15px;	}	.camera-scan-code-table-pair-value {		flex-grow: 2;		flex-direction: column;	}	.camera-scan-code-table-pair-value .txt {		font-size: 14px;	}	.camera-scan-code-table-top-line {		border-top: 1px solid #eee;	}		.my-page {		display: flex;		flex-direction: column;		box-sizing: border-box;		background-color: #fff;		/* #ifndef APP-ANDROID */		min-height: 100%;		/* #endif */		height: 100%;	}	.swiper {		height: 400px;	}	.swiper-box {		height: 300px;	}	.swiper-item {		/* #ifndef APP-NVUE */		display: flex;		/* #endif */		flex-direction: column;		justify-content: center;		align-items: center;		color: #fff;		height: 400px;		line-height: 400px;	}	.btn {		/*margin-top: 30px; */		height: 45px;		margin: 20px 20px;	}</style>
 |